additional_tags 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f425aa2c12ec481def256bfa8aab0bbcbd159635314a43f25be436c226b6a0f
4
- data.tar.gz: a8a660768d48a774c25a22a4c3641d1e6a11e2afbe4697b43e28a9782ae16a3b
3
+ metadata.gz: d10d32f78d464486a2921a86274972ec068f3aad16c988f9037e89a433ac4efa
4
+ data.tar.gz: 50833ac552b8b3de0cba5eb5d9710cdbafdf17b3c2d53e742031ed22a52c9e86
5
5
  SHA512:
6
- metadata.gz: 8edc1a8edc5244203a2db5acff0cdd9cecb28765e1aa2967cd6ef17a62426eec82d7b45949b36d813106f5d089bc6646be4b3e03c68f05c36f5b1b16117dfafd
7
- data.tar.gz: d7c2d394c72bd948896b6a0da32528bd92581fa94ab241aaddfb341225fabe572b5e769dc003e2359a0a0f1c234e796bfee1ac69e7af183f3b7ba39f916b0154
6
+ metadata.gz: 837feb0dba819a2191dbd5151f523a3f65aac23918faaa2f93277a0b51c33e4483353c43dd7ae69ae98b43886099fe7f3005b33daafe9d05d4b593ce1866e657
7
+ data.tar.gz: 2d54a07615b231470a074e42ec5f4d275af5b5838338a360e8d7401fa742deeba0b500a1ca94069a3b37240e23947e9110c6b2f6f785ec3517ae1cfc63a66351
@@ -0,0 +1,70 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '35 20 * * 2'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'javascript', 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v2
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v1
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
+
53
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
+ # If this step fails, then you should remove it and run the build manually (see below)
55
+ - name: Autobuild
56
+ uses: github/codeql-action/autobuild@v1
57
+
58
+ # ℹ️ Command-line programs to run using the OS shell.
59
+ # 📚 https://git.io/JvXDl
60
+
61
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62
+ # and modify them (or add more) to build your code if your project
63
+ # uses a compiled language
64
+
65
+ #- run: |
66
+ # make bootstrap
67
+ # make release
68
+
69
+ - name: Perform CodeQL Analysis
70
+ uses: github/codeql-action/analyze@v1
@@ -16,7 +16,7 @@ jobs:
16
16
 
17
17
  - name: Setup Gemfile
18
18
  run: |
19
- touch .enable_dev
19
+ touch .enable_linters
20
20
  sed -i "3isource 'https://rubygems.org'" Gemfile
21
21
 
22
22
  - name: Setup Ruby
@@ -37,3 +37,7 @@ jobs:
37
37
  run: |
38
38
  bundle exec slim-lint app/views
39
39
  if: always()
40
+
41
+ - name: Run Brakeman
42
+ run: |
43
+ bundle exec brakeman -5
@@ -87,6 +87,10 @@ jobs:
87
87
  libpq-dev
88
88
  libmysqlclient-dev
89
89
 
90
+ - name: Setup Gemfile
91
+ run: |
92
+ touch .enable_test
93
+
90
94
  - name: Setup Ruby
91
95
  uses: ruby/setup-ruby@v1
92
96
  with:
@@ -98,6 +102,7 @@ jobs:
98
102
  run: |
99
103
  cp plugins/additional_tags/test/support/database-${{ matrix.db }}.yml config/database.yml
100
104
  cp plugins/additional_tags/test/support/configuration.yml config/configuration.yml
105
+ cp plugins/additionals/test/support/additional_environment.rb config/additional_environment.rb
101
106
 
102
107
  - name: Install Ruby dependencies
103
108
  working-directory: redmine
data/.gitignore CHANGED
@@ -5,6 +5,6 @@ tmp/
5
5
  Gemfile.lock
6
6
  .project
7
7
  .settings/
8
+ .enable_*
8
9
  ._*
9
10
  *.gem
10
- .enable_dev
data/.rubocop.yml CHANGED
@@ -92,6 +92,12 @@ Style/OptionHash:
92
92
  - parameters
93
93
  - settings
94
94
 
95
+ # postgresql and mysql are supported
96
+ # autodetect does not work without database configuration
97
+ Rails/BulkChangeTable:
98
+ Enabled: true
99
+ Database: postgresql
100
+
95
101
  Style/ReturnNil:
96
102
  Enabled: true
97
103
 
@@ -114,3 +120,8 @@ Naming/VariableNumber:
114
120
  Enabled: true
115
121
  Exclude:
116
122
  - 'test/**/*'
123
+
124
+ # see https://github.com/rubocop/rubocop-rails/issues/578
125
+ # redmine does not use load_defaults: https://rails.rubystyle.guide/#config-defaults
126
+ Rails/RedundantPresenceValidationOnBelongsTo:
127
+ Enabled: false
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Additional Tags - Tags for Redmine
2
2
 
3
- [![Rate at redmine.org](https://img.shields.io/badge/rate%20at-redmine.org-blue.svg?style=flat)](https://www.redmine.org/plugins/additional_tags) [![Run Linters](https://github.com/AlphaNodes/additional_tags/workflows/Run%20Linters/badge.svg)](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3A%22Run+Linters%22) [![Run Brakeman](https://github.com/AlphaNodes/additional_tags/workflows/Run%20Brakeman/badge.svg)](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3A%22Run+Brakeman%22) [![Run Tests](https://github.com/AlphaNodes/additional_tags/workflows/Tests/badge.svg)](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3ATests)
3
+ [![Rate at redmine.org](https://img.shields.io/badge/rate%20at-redmine.org-blue.svg?style=flat)](https://www.redmine.org/plugins/additional_tags) [![Run Linters](https://github.com/AlphaNodes/additional_tags/workflows/Run%20Linters/badge.svg)](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3A%22Run+Linters%22) [![Run Tests](https://github.com/AlphaNodes/additional_tags/workflows/Tests/badge.svg)](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3ATests)
4
4
 
5
5
  ## Features
6
6
 
@@ -15,12 +15,12 @@ module AdditionalTagsHelper
15
15
  if AdditionalTags.setting? :active_issue_tags
16
16
  columns[:issue] = { label: l(:label_issue_plural),
17
17
  tag_controller: :issues,
18
- counts: Issue.available_tags.map { |tag| [tag.id, tag.count] }.to_h }
18
+ counts: Issue.available_tags.to_h { |tag| [tag.id, tag.count] } }
19
19
  end
20
20
 
21
21
  if AdditionalTags.setting? :active_wiki_tags
22
22
  columns[:wiki] = { label: l(:label_wiki),
23
- counts: WikiPage.available_tags.map { |tag| [tag.id, tag.count] }.to_h }
23
+ counts: WikiPage.available_tags.to_h { |tag| [tag.id, tag.count] } }
24
24
  end
25
25
 
26
26
  call_hook :helper_additional_manageable_tag_columns, columns: columns
@@ -111,7 +111,7 @@ module AdditionalTagsHelper
111
111
  "background-color: #{tag_bg_color}; color: #{tag_fg_color}"
112
112
  end
113
113
 
114
- tag_name << tag.span("(#{tag_object.count})", class: 'tag-count') if show_count
114
+ tag_name << tag.span(tag_object.count, class: 'tag-count') if show_count
115
115
 
116
116
  content = if link
117
117
  link_to safe_join(tag_name),
@@ -181,30 +181,8 @@ module AdditionalTagsHelper
181
181
  additional_tag_sep(use_colors: options[:use_colors])
182
182
  end
183
183
 
184
- def scope_for_tags_aggr(project: nil, open_issues_only: nil)
185
- Issue.available_tags project: project,
186
- open_issues_only: open_issues_only
187
- end
188
-
189
- def issues_count_group_by_tags(project, tags)
190
- scope = Issue.group_by_status_with_tags project
191
-
192
- issue_counts = {}
193
- tags.each do |tag|
194
- # binding.pry
195
- open_issues = scope[[0, tag.id]] || scope[[false, tag.id]] || 0
196
- closed_issues = scope[[1, tag.id]] || scope[[true, tag.id]] || 0
197
- issue_counts[tag.name] = { open: open_issues,
198
- closed: closed_issues,
199
- total: open_issues + closed_issues,
200
- tag: tag }
201
- end
202
-
203
- issue_counts
204
- end
205
-
206
- def link_to_totals_for_tags(issue_counts:, project:, open_issues_only:)
207
- sum = if issue_counts.blank? || issue_counts.size.zero?
184
+ def link_to_issue_tags_totals(entries:, project:, open_issues_only:)
185
+ sum = if entries.blank? || entries.size.zero?
208
186
  0
209
187
  else
210
188
  query = IssueQuery.new project: project, name: '_'
@@ -220,6 +198,14 @@ module AdditionalTagsHelper
220
198
  status_id: open_issues_only ? 'o' : '*')
221
199
  end
222
200
 
201
+ def issue_tag_status_filter(operator: nil, open_issues_only: false)
202
+ if operator
203
+ { field: :status_id, operator: operator }
204
+ elsif open_issues_only
205
+ { field: :status_id, operator: 'o' }
206
+ end
207
+ end
208
+
223
209
  private
224
210
 
225
211
  def tag_url(tag_name, filter: nil, tag_action: nil, tag_controller: nil, project: nil)
@@ -7,7 +7,7 @@ module AdditionalTagsIssuesHelper
7
7
 
8
8
  if @issue.present? &&
9
9
  (defined?(controller_name) && controller_name == 'issues' && action_name == 'show' || !defined?(controller_name)) &&
10
- User.current.allowed_to?(:view_issues, @project)
10
+ AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, @project)
11
11
 
12
12
  api.array :tags do
13
13
  @issue.tags.each do |tag|
@@ -16,6 +16,16 @@ module AdditionalTagsIssuesHelper
16
16
  end
17
17
  end
18
18
 
19
+ if @time_entry.present? &&
20
+ (defined?(controller_name) && controller_name == 'timelog' && action_name == 'show' || !defined?(controller_name)) &&
21
+ AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, @project)
22
+ api.array :issue_tags do
23
+ @time_entry.issue_tags.each do |tag|
24
+ api.tag id: tag.id, name: tag.name
25
+ end
26
+ end
27
+ end
28
+
19
29
  rc
20
30
  end
21
31
 
@@ -4,7 +4,7 @@ class AdditionalTagsRemoveUnusedTagJob < AdditionalTagsJob
4
4
  def perform
5
5
  # only once a minute to reduce load
6
6
  cache_key = self.class.to_s
7
- return if Rails.cache.read cache_key
7
+ return if Rails.cache.read(cache_key) && !Rails.env.test?
8
8
 
9
9
  Rails.cache.write cache_key, true, expires_in: 60
10
10
  AdditionalTags::Tags.remove_unused_tags
@@ -0,0 +1,19 @@
1
+ = additionals_transform_to_select2 'assignee',
2
+ format_state: 'formatNameWithIcon',
3
+ multiple: true,
4
+ url: assignee_auto_completes_path(project_id: @project)
5
+
6
+ = additionals_transform_to_select2 'author',
7
+ format_state: 'formatNameWithIcon',
8
+ multiple: true,
9
+ url: grouped_users_auto_completes_path(project_id: @project, with_ano: true, with_me: true)
10
+
11
+ = additionals_transform_to_select2 'user_with_me',
12
+ format_state: 'formatNameWithIcon',
13
+ multiple: true,
14
+ url: grouped_users_auto_completes_path(project_id: @project, with_me: true)
15
+
16
+ = additionals_transform_to_select2 'user',
17
+ format_state: 'formatNameWithIcon',
18
+ multiple: true,
19
+ url: grouped_users_auto_completes_path(project_id: @project)
@@ -1,6 +1,3 @@
1
1
  = stylesheet_link_tag 'tags', plugin: 'additional_tags'
2
2
  = javascript_include_tag 'tags', plugin: 'additional_tags'
3
- - if User.current.allowed_to?(:edit_issue_tags, @project, global: true) || \
4
- User.current.allowed_to?(:create_issue_tags, @project, global: true) || \
5
- User.current.allowed_to?(:add_wiki_tags, @project, global: true)
6
- = additionals_library_load :select2
3
+ = additionals_library_load :select2
@@ -1,29 +1,30 @@
1
1
  - tags = manageable_tags
2
2
  - if tags.present?
3
- table.list.issues
4
- thead
5
- tr
6
- th.checkbox.hide-when-print
7
- = check_box_tag 'ids[]', '', false, class: 'toggle-selection',
8
- title: "#{l :button_check_all}/#{l :button_uncheck_all}"
3
+ .autoscroll
4
+ table.list.issues
5
+ thead
6
+ tr
7
+ th.checkbox.hide-when-print
8
+ = check_box_tag 'ids[]', '', false, class: 'toggle-selection',
9
+ title: "#{l :button_check_all}/#{l :button_uncheck_all}"
9
10
 
10
- th = l :field_name
11
- - manageable_tag_columns.each do |_column, column_values|
12
- th = column_values[:label]
13
- th
14
- tbody
15
- - tags.each do |tag|
16
- tr.hascontextmenu id="#{tag.id}"
17
- td.checkbox.hide-when-print
18
- = check_box_tag 'ids[]', tag.id, false, id: nil
19
- td = additional_tag_link tag, link: edit_additional_tag_path(tag)
20
- - manageable_tag_column_values(tag).each do |column|
21
- td = column
22
- td.buttons
23
- = link_to l(:button_edit), edit_additional_tag_path(tag),
24
- class: 'icon icon-edit'
25
- = link_to l(:button_delete), additional_tags_path(ids: tag), method: :delete,
26
- data: { confirm: l(:text_are_you_sure) }, class: 'icon icon-del'
11
+ th = l :field_name
12
+ - manageable_tag_columns.each do |_column, column_values|
13
+ th = column_values[:label]
14
+ th
15
+ tbody
16
+ - tags.each do |tag|
17
+ tr.hascontextmenu id="#{tag.id}"
18
+ td.checkbox.hide-when-print
19
+ = check_box_tag 'ids[]', tag.id, false, id: nil
20
+ td = additional_tag_link tag, link: edit_additional_tag_path(tag)
21
+ - manageable_tag_column_values(tag).each do |column|
22
+ td = column
23
+ td.buttons
24
+ = link_to l(:button_edit), edit_additional_tag_path(tag),
25
+ class: 'icon icon-edit'
26
+ = link_to l(:button_delete), additional_tags_path(ids: tag), method: :delete,
27
+ data: { confirm: l(:text_are_you_sure) }, class: 'icon icon-del'
27
28
  - else
28
29
  p.nodata = l :label_no_data
29
30
 
@@ -0,0 +1,11 @@
1
+ ul.reporting-list.tag-summary
2
+ li.amount-tags
3
+ = l :label_amount_tags
4
+ ' :
5
+ span.value
6
+ = tags.to_a.size
7
+ li.amount-entities-with-tags
8
+ = l :label_amount_entities_with_tags, name: entities_label
9
+ ' :
10
+ span.value
11
+ = totals_link
@@ -1,27 +1,22 @@
1
1
  h3 = block_definition[:label]
2
2
 
3
3
  - open_issues_only = RedminePluginKit.true? settings[:open_issues_only]
4
- - tags = scope_for_tags_aggr project: @project,
5
- open_issues_only: open_issues_only
6
- - issue_counts = issues_count_group_by_tags @project, tags
4
+ - tags = Issue.available_tags project: @project, open_issues_only: open_issues_only
5
+ - counts = AdditionalTags::Tags.entity_group_by scope: Issue.group_by_status_with_tags(@project),
6
+ tags: tags,
7
+ statuses: { true => :closed, false => :open },
8
+ group_id_is_bool: true
7
9
 
8
- ul.reporting-list.tag-summary
9
- li.amount-tags
10
- = l :label_amount_tags
11
- ' :
12
- span.value
13
- = tags.to_a.size
14
- li.amount-entities-with-tags
15
- = l :label_amount_entities_with_tags, name: l(:label_issue_plural)
16
- ' :
17
- span.value
18
- = link_to_totals_for_tags project: @project,
19
- issue_counts: issue_counts,
20
- open_issues_only: open_issues_only
10
+ = render partial: 'common/tag_summary_block',
11
+ locals: { tags: tags,
12
+ entities_label: l(:label_issue_plural),
13
+ totals_link: link_to_issue_tags_totals(entries: counts,
14
+ project: @project,
15
+ open_issues_only: open_issues_only) }
21
16
 
22
17
  - if RedminePluginKit.true? settings[:with_table_of_values]
23
18
  - if tags.present?
24
- - tags = sort_tags_for_list tags
19
+ - tags = sort_tags_for_list tags.to_a
25
20
  table.list.tags
26
21
  thead
27
22
  tr
@@ -38,26 +33,26 @@ ul.reporting-list.tag-summary
38
33
  td.name = additional_tag_link tag,
39
34
  tag_action: 'index',
40
35
  tag_controller: 'issues',
41
- filter: open_issues_only ? { field: :status_id, operator: 'o' } : nil,
36
+ filter: issue_tag_status_filter(open_issues_only: open_issues_only),
42
37
  use_colors: RedminePluginKit.true?(settings[:use_colors])
43
38
  - unless open_issues_only
44
39
  td.value = additional_tag_link tag,
45
40
  tag_action: 'index',
46
41
  tag_controller: 'issues',
47
- filter: { field: :status_id, operator: 'o' },
42
+ filter: issue_tag_status_filter(operator: 'o'),
48
43
  use_colors: false,
49
- name: issue_counts[tag.name][:open]
44
+ name: counts[tag.name][:open]
50
45
  td.value = additional_tag_link tag,
51
46
  tag_action: 'index',
52
47
  tag_controller: 'issues',
53
- filter: { field: :status_id, operator: 'c' },
48
+ filter: issue_tag_status_filter(operator: 'c'),
54
49
  use_colors: false,
55
- name: issue_counts[tag.name][:closed]
50
+ name: counts[tag.name][:closed]
56
51
  td.value = additional_tag_link tag,
57
52
  tag_action: 'index',
58
53
  tag_controller: 'issues',
59
- filter: open_issues_only ? { field: :status_id, operator: 'o' } : nil,
54
+ filter: issue_tag_status_filter(open_issues_only: open_issues_only),
60
55
  use_colors: false,
61
- name: open_issues_only ? issue_counts[tag.name][:open] : issue_counts[tag.name][:total]
56
+ name: open_issues_only ? counts[tag.name][:open] : counts[tag.name][:total]
62
57
  - else
63
58
  p.nodata = l :label_no_data
@@ -26,8 +26,8 @@ ul.tags li { margin: 0.25em 0; }
26
26
 
27
27
  span.additional-tag-label-color {
28
28
  display: inline-block;
29
- margin-bottom: 5px !important;
30
- border-radius: 2px !important;
29
+ margin-bottom: 3px !important;
30
+ border-radius: 0.75rem !important;
31
31
  }
32
32
 
33
33
  td.tags span.additional-tag-label-color {
@@ -37,27 +37,41 @@ td.tags span.additional-tag-label-color {
37
37
  }
38
38
 
39
39
  .additional-tag-label-color {
40
- padding: 2px 4px;
41
- font-size: 10px !important;
40
+ padding: 0 5px;
41
+ line-height: initial;
42
+ font-size: 0.7rem !important;
42
43
  border: 1px solid rgba(0, 0, 0, 0.2);
43
- border-radius: 2px;
44
+ border-radius: 0.75rem;
44
45
  }
45
46
 
46
- .additional-tag-label-color a::before {
47
- font-family: Font Awesome\ 5 Free;
48
- font-size: 0.9em;
49
- font-weight: 900;
50
- content: "\f02b"; /* fas fa-tag */
51
- padding-right: 3px;
47
+ .additional-tag-label-color .tag-count {
48
+ display: inline-block;
49
+ background: #fff;
50
+ color: #1f1f1f;
51
+ padding-right: 5px;
52
+ border-top-right-radius: 0.7rem;
53
+ border-bottom-right-radius: 0.7rem;
54
+ margin-right: -5px;
55
+ padding-left: 3px;
56
+ }
57
+
58
+ .tag-label .tag-count {
59
+ background: #f1f5fa;
60
+ border-radius: 5px;
61
+ padding: 0 5px;
62
+ color: #666;
63
+ border: 1px solid #ddd;
64
+ vertical-align: middle;
52
65
  }
53
66
 
54
67
  .additional-tag-label-color .tag-count,
55
68
  .tag-label .tag-count {
56
- font-size: 0.75em;
69
+ font-size: 0.7rem;
57
70
  margin-left: 0.5em;
58
71
  }
59
72
 
60
- span.additional-tag-label-color:hover a {
73
+ span.additional-tag-label-color:hover a,
74
+ .tag-label .tag-count:hover a {
61
75
  text-decoration: none !important;
62
76
  }
63
77
 
@@ -14,7 +14,7 @@ class MigrateExistingTags < ActiveRecord::Migration[5.2]
14
14
  next if excluded_taggable_types.include? tagging.taggable_type
15
15
 
16
16
  tag = ActsAsTaggableOn::Tag.create! name: old_tag.name if cnt.zero? && tag.nil?
17
- context = tagging.respond_to?('context') && tagging.context.present? ? tagging.context : 'tags'
17
+ context = tagging.respond_to?(:context) && tagging.context.present? ? tagging.context : 'tags'
18
18
 
19
19
  # old data can include dups
20
20
  next if ActsAsTaggableOn::Tagging.exists? tag_id: tag.id,
@@ -13,6 +13,7 @@ module AdditionalTags
13
13
  render_on :view_issues_show_details_bottom, partial: 'issues/tags'
14
14
  render_on :view_issues_sidebar_planning_bottom, partial: 'issues/tags_sidebar'
15
15
  render_on :view_layouts_base_html_head, partial: 'additional_tags/html_head'
16
+ render_on :view_layouts_base_body_bottom, partial: 'additional_tags/body_bottom'
16
17
  render_on :view_wiki_form_bottom, partial: 'tags_form_bottom'
17
18
  render_on :view_wiki_show_bottom, partial: 'tags_show'
18
19
  render_on :view_wiki_show_sidebar_bottom, partial: 'wiki/tags_sidebar'
@@ -12,8 +12,7 @@ module AdditionalTags
12
12
  module InstanceMethods
13
13
  def issue_tags
14
14
  suggestion_order = AdditionalTags.setting(:tags_suggestion_order) || 'name'
15
- @name = (params[:q] || params[:term]).to_s.strip
16
- @tags = Issue.available_tags name_like: @name,
15
+ @tags = Issue.available_tags name_like: build_search_query_term(params),
17
16
  sort_by: suggestion_order,
18
17
  order: (suggestion_order == 'name' ? 'ASC' : 'DESC')
19
18
 
@@ -23,17 +22,17 @@ module AdditionalTags
23
22
  end
24
23
 
25
24
  def wiki_tags
26
- @name = params[:q].to_s
27
- @tags = WikiPage.available_tags project: nil, name_like: @name
25
+ @tags = WikiPage.available_tags project: nil,
26
+ name_like: build_search_query_term(params)
28
27
  render layout: false, partial: 'additional_tag_list', locals: { unsorted: true }
29
28
  end
30
29
 
31
30
  def all_tags
32
31
  return render_403 unless User.current.admin?
33
32
 
34
- @name = params[:q].to_s
33
+ q = build_search_query_term params
35
34
  sql_for_where = "LOWER(#{ActiveRecord::Base.connection.quote_table_name ActsAsTaggableOn.tags_table}.name) LIKE ?"
36
- @tags = ActsAsTaggableOn::Tag.where(sql_for_where, "%#{@name.downcase}%")
35
+ @tags = ActsAsTaggableOn::Tag.where(sql_for_where, "%#{q.downcase}%")
37
36
  .order(name: :asc)
38
37
 
39
38
  render layout: false, partial: 'additional_tag_list', locals: { unsorted: true }
@@ -6,12 +6,16 @@ module AdditionalTags
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ include Additionals::EntityMethodsGlobal
9
10
  include InstanceMethods
10
11
  acts_as_ordered_taggable
11
12
 
12
13
  before_save :prepare_save_tag_change
13
14
  before_save :sort_tag_list
14
15
 
16
+ after_commit :add_remove_unused_tags_job, on: %i[update destroy],
17
+ if: proc { AdditionalTags.setting?(:active_issue_tags) }
18
+
15
19
  alias_method :safe_attributes_without_tags=, :safe_attributes=
16
20
  alias_method :safe_attributes=, :safe_attributes_with_tags=
17
21
 
@@ -26,11 +30,10 @@ module AdditionalTags
26
30
  end
27
31
 
28
32
  def group_by_status_with_tags(project = nil)
29
- Issue.visible(User.current, project: project)
30
- .joins(:status)
31
- .joins(:tags)
32
- .group(:is_closed, 'tag_id')
33
- .count
33
+ visible(User.current, project: project).joins(:status)
34
+ .joins(:tags)
35
+ .group(:is_closed, 'tag_id')
36
+ .count
34
37
  end
35
38
 
36
39
  def available_tags(**options)
@@ -41,10 +44,6 @@ module AdditionalTags
41
44
  tags.joins("JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id")
42
45
  .where(issue_statuses: { is_closed: false })
43
46
  end
44
-
45
- def remove_unused_tags!
46
- AdditionalTagsRemoveUnusedTagJob.perform_later
47
- end
48
47
  end
49
48
 
50
49
  module InstanceMethods
@@ -68,7 +67,7 @@ module AdditionalTags
68
67
  end
69
68
  end
70
69
 
71
- send 'safe_attributes_without_tags=', attrs, user
70
+ send :safe_attributes_without_tags=, attrs, user
72
71
  end
73
72
 
74
73
  # copy_from requires hash for Redmine - works with Ruby 3
@@ -81,15 +80,6 @@ module AdditionalTags
81
80
  end
82
81
  # rubocop: enable Style/OptionHash
83
82
 
84
- def tags_to_journal(old_tags, new_tags)
85
- return if current_journal.blank? || old_tags == new_tags
86
-
87
- current_journal.details << JournalDetail.new(property: 'attr',
88
- prop_key: 'tag_list',
89
- old_value: old_tags,
90
- value: new_tags)
91
- end
92
-
93
83
  private
94
84
 
95
85
  def sort_tag_list
@@ -35,14 +35,16 @@ module AdditionalTags
35
35
  .map { |name| [name, name] }
36
36
  end
37
37
 
38
- def build_subquery_for_tags_field(klass:, operator:, values:, joined_table:, joined_field:, target_field: 'issue_id')
38
+ def build_subquery_for_tags_field(klass:, operator:, values:, joined_table:, joined_field:,
39
+ source_field: 'id', target_field: 'issue_id')
39
40
  quoted_joined_table = self.class.connection.quote_table_name joined_table
40
41
  quoted_joined_field = self.class.connection.quote_column_name joined_field
42
+ quoted_source_field = self.class.connection.quote_column_name source_field
41
43
  quoted_target_field = self.class.connection.quote_column_name target_field
42
44
  subsql = ActsAsTaggableOn::Tagging.joins("INNER JOIN #{quoted_joined_table}" \
43
45
  " ON additional_taggings.taggable_id = #{quoted_joined_table}.#{quoted_target_field}")
44
46
  .where(taggable_type: klass.name)
45
- .where("#{self.class.connection.quote_table_name queried_table_name}.id ="\
47
+ .where("#{self.class.connection.quote_table_name queried_table_name}.#{quoted_source_field} ="\
46
48
  " #{quoted_joined_table}.#{quoted_joined_field}")
47
49
  .select(1)
48
50
 
@@ -63,7 +65,12 @@ module AdditionalTags
63
65
  case operator
64
66
  when '=', '!'
65
67
  ids_list = klass.tagged_with(values, any: true).pluck :id
66
- "(#{klass.table_name}.id #{compare} (#{ids_list.join ','}))" if ids_list.present?
68
+ if ids_list.present?
69
+ "(#{klass.table_name}.id #{compare} (#{ids_list.join ','}))"
70
+ elsif values.present? && operator == '='
71
+ # special case: filter with deleted tag
72
+ '(1=0)'
73
+ end
67
74
  else
68
75
  entries = ActsAsTaggableOn::Tagging.where taggable_type: klass.name
69
76
  id_table = klass.table_name
@@ -28,16 +28,23 @@ module AdditionalTags
28
28
  AdditionalTags::Tags.available_tags self, **options
29
29
  end
30
30
 
31
- def with_tags(tag, project: nil, order: 'title_asc', max_entries: nil)
32
- wiki = project&.wiki
33
-
31
+ def with_tags_scope(project: nil, wiki: nil)
34
32
  scope = if wiki
35
33
  wiki.pages
36
34
  else
37
- WikiPage.joins wiki: :project
35
+ scope = WikiPage.joins wiki: :project
36
+ scope = scope.where wikis: { project_id: project.id } if project
37
+ scope
38
38
  end
39
39
 
40
40
  scope = scope.visible User.current, project: project if scope.respond_to? :visible
41
+ scope
42
+ end
43
+
44
+ def with_tags(tag, project: nil, order: 'title_asc', max_entries: nil)
45
+ wiki = project&.wiki
46
+
47
+ scope = with_tags_scope wiki: wiki, project: project
41
48
  scope = scope.limit max_entries if max_entries
42
49
 
43
50
  tags = Array tag
@@ -49,7 +56,6 @@ module AdditionalTags
49
56
 
50
57
  scope = scope.where(id: tagged_with(tags, any: true).ids)
51
58
  .with_updated_on
52
- .joins(wiki: :project)
53
59
 
54
60
  return scope if order.nil?
55
61
 
@@ -89,7 +95,7 @@ module AdditionalTags
89
95
  end
90
96
  end
91
97
 
92
- send 'safe_attributes_without_tags=', attrs, user
98
+ send :safe_attributes_without_tags=, attrs, user
93
99
  end
94
100
 
95
101
  private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AdditionalTags
4
4
  module PluginVersion
5
- VERSION = '1.0.3' unless defined? VERSION
5
+ VERSION = '1.0.4' unless defined? VERSION
6
6
  end
7
7
  end
@@ -64,7 +64,8 @@ module AdditionalTags
64
64
  tags_to_merge.reject { |t| t.id == tag.id }.each(&:destroy)
65
65
  # remove duplicate taggings
66
66
  dup_scope = ActsAsTaggableOn::Tagging.where tag_id: tag.id
67
- valid_ids = dup_scope.group(:tag_id, :taggable_id, :taggable_type, :tagger_id, :tagger_type, :context).pluck(Arel.sql('MIN(id)'))
67
+ valid_ids = dup_scope.group(:tag_id, :taggable_id, :taggable_type, :tagger_id, :tagger_type, :context)
68
+ .pluck(Arel.sql('MIN(id)'))
68
69
  dup_scope.where.not(id: valid_ids).destroy_all if valid_ids.any?
69
70
  # recalc count for new tag
70
71
  ActsAsTaggableOn::Tag.reset_counters tag.id, :taggings
@@ -86,6 +87,7 @@ module AdditionalTags
86
87
  end
87
88
 
88
89
  def build_relation_tags(entries)
90
+ entries = Array entries
89
91
  return [] if entries.none?
90
92
 
91
93
  tags = entries.map(&:tags)
@@ -94,8 +96,55 @@ module AdditionalTags
94
96
  tags.uniq
95
97
  end
96
98
 
99
+ def entity_group_by(scope:, tags:, statuses: nil, sub_groups: nil, group_id_is_bool: false)
100
+ counts = {}
101
+ tags.each do |tag|
102
+ values = { tag: tag, total: 0, total_sub_groups: 0, groups: [] }
103
+
104
+ if statuses
105
+ statuses.each do |status|
106
+ group_id = status.first
107
+ group = status.second
108
+ values[group] = status_for_tag_value scope: scope,
109
+ tag_id: tag.id,
110
+ group_id: group_id,
111
+ group_id_is_bool: group_id_is_bool
112
+ values[:groups] << { id: group_id, group: group, count: values[group] }
113
+ values[:total] += values[group]
114
+ values[:total_sub_groups] += values[group] if sub_groups&.include? group_id
115
+ end
116
+ else
117
+ values[:total] += status_for_tag_value scope: scope, tag_id: tag.id
118
+ end
119
+
120
+ values[:total_without_sub_groups] = values[:total] - values[:total_sub_groups]
121
+
122
+ counts[tag.name] = values
123
+ end
124
+
125
+ counts
126
+ end
127
+
97
128
  private
98
129
 
130
+ def status_for_tag_value(scope:, tag_id:, group_id: nil, group_id_is_bool: false)
131
+ value = if group_id_is_bool || group_id
132
+ if group_id_is_bool
133
+ if group_id
134
+ scope[[1, tag_id]] || scope[[true, tag_id]]
135
+ else
136
+ scope[[0, tag_id]] || scope[[false, tag_id]]
137
+ end
138
+ else
139
+ scope[[group_id, tag_id]]
140
+ end
141
+ else
142
+ scope[tag_id]
143
+ end
144
+
145
+ value || 0
146
+ end
147
+
99
148
  def build_order_sql(sort_by, order)
100
149
  order = order.present? && order == 'DESC' ? 'DESC' : 'ASC'
101
150
 
@@ -17,12 +17,7 @@ module AdditionalTags
17
17
  private
18
18
 
19
19
  def setup
20
- # TODO: this check does not work with Rails 6 at the moment
21
- # reason: ActiveSupport.on_load(:active_record) is used for setup
22
- # as temp solution
23
- if Rails.version < '6.0' && !Redmine::Plugin.installed?('additionals')
24
- raise 'Please install additionals plugin (https://github.com/alphanodes/additionals)'
25
- end
20
+ raise 'Please install additionals plugin (https://github.com/alphanodes/additionals)' unless Redmine::Plugin.installed? 'additionals'
26
21
 
27
22
  loader.incompatible? %w[redmine_tags
28
23
  redmine_tagging
@@ -75,6 +70,10 @@ module AdditionalTags
75
70
 
76
71
  ActsAsTaggableOn.tags_table = TAG_TABLE_NAME
77
72
  ActsAsTaggableOn.taggings_table = TAGGING_TABLE_NAME
73
+ # NOTE: remove_unused_tags cannot be used, because tag is deleted before assign for tagging
74
+ # @see https://github.com/mbleigh/acts-as-taggable-on/issues/946
75
+ # NOTE2: merging tags is not compatible, too.
76
+ ActsAsTaggableOn.remove_unused_tags = false
78
77
 
79
78
  config.after_initialize do
80
79
  # engine_name could be used (additional_tags_plugin), but can
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :redmine do
4
+ namespace :additional_tags do
5
+ desc <<-DESCRIPTION
6
+ Remove unused tags.
7
+
8
+ Example:
9
+ bundle exec rake redmine:additional_tags:remove_unused_tags RAILS_ENV=production
10
+ DESCRIPTION
11
+ task remove_unused_tags: :environment do
12
+ AdditionalTags::Tags.remove_unused_tags
13
+
14
+ puts 'Unused tags has been removed.'
15
+ exit 0
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: additional_tags
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - AlphaNodes
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-18 00:00:00.000000000 Z
11
+ date: 2022-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts-as-taggable-on
@@ -74,7 +74,7 @@ extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
76
  - ".eslintrc.yml"
77
- - ".github/workflows/brakeman.yml"
77
+ - ".github/workflows/codeql-analysis.yml"
78
78
  - ".github/workflows/linters.yml"
79
79
  - ".github/workflows/tests.yml"
80
80
  - ".gitignore"
@@ -95,6 +95,7 @@ files:
95
95
  - app/models/migrate_tag.rb
96
96
  - app/models/migrate_tagging.rb
97
97
  - app/models/query_tags_column.rb
98
+ - app/views/additional_tags/_body_bottom.html.slim
98
99
  - app/views/additional_tags/_html_head.html.slim
99
100
  - app/views/additional_tags/_tag_list.html.slim
100
101
  - app/views/additional_tags/context_menu.html.slim
@@ -104,6 +105,7 @@ files:
104
105
  - app/views/additional_tags/settings/_manage_tags.html.slim
105
106
  - app/views/additional_tags/settings/_settings.html.slim
106
107
  - app/views/auto_completes/_additional_tag_list.slim
108
+ - app/views/common/_tag_summary_block.html.slim
107
109
  - app/views/context_menus/_issues_tags.html.slim
108
110
  - app/views/dashboards/blocks/_issue_tags.html.slim
109
111
  - app/views/dashboards/blocks/_issue_tags_settings.html.slim
@@ -171,6 +173,7 @@ files:
171
173
  - lib/additional_tags/patches/wiki_page_patch.rb
172
174
  - lib/additional_tags/plugin_version.rb
173
175
  - lib/additional_tags/tags.rb
176
+ - lib/tasks/additional_tags.rake
174
177
  homepage: https://github.com/alphanodes/additional_tags
175
178
  licenses:
176
179
  - GPL-2.0
@@ -1,34 +0,0 @@
1
- name: Run Brakeman
2
-
3
- on: [push]
4
-
5
- jobs:
6
- build:
7
-
8
- runs-on: ubuntu-latest
9
-
10
- steps:
11
- - uses: actions/checkout@v2
12
-
13
- - name: Install package dependencies
14
- run: >
15
- sudo apt-get install --yes --quiet pandoc
16
-
17
- - name: Setup Gemfile
18
- run: |
19
- touch .enable_dev
20
- sed -i "3isource 'https://rubygems.org'" Gemfile
21
-
22
- - name: Setup Ruby
23
- uses: ruby/setup-ruby@v1
24
- with:
25
- ruby-version: 2.6
26
- bundler-cache: true
27
-
28
- - name: Setup gems
29
- run: |
30
- bundle install --jobs 4 --retry 3
31
-
32
- - name: Run Brakeman
33
- run: |
34
- bundle exec brakeman -5