additional_tags 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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