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 +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/linters.yml +5 -1
- data/.github/workflows/tests.yml +5 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +11 -0
- data/README.md +1 -1
- data/app/helpers/additional_tags_helper.rb +13 -27
- data/app/helpers/additional_tags_issues_helper.rb +11 -1
- data/app/jobs/additional_tags_remove_unused_tag_job.rb +1 -1
- data/app/views/additional_tags/_body_bottom.html.slim +19 -0
- data/app/views/additional_tags/_html_head.html.slim +1 -4
- data/app/views/additional_tags/settings/_manage_tags.html.slim +24 -23
- data/app/views/common/_tag_summary_block.html.slim +11 -0
- data/app/views/dashboards/blocks/_issue_tags.html.slim +19 -24
- data/assets/stylesheets/tags.css +27 -13
- data/db/migrate/20201123093214_migrate_existing_tags.rb +1 -1
- data/lib/additional_tags/hooks/view_hook.rb +1 -0
- data/lib/additional_tags/patches/auto_completes_controller_patch.rb +5 -6
- data/lib/additional_tags/patches/issue_patch.rb +9 -19
- data/lib/additional_tags/patches/query_patch.rb +10 -3
- data/lib/additional_tags/patches/wiki_page_patch.rb +12 -6
- data/lib/additional_tags/plugin_version.rb +1 -1
- data/lib/additional_tags/tags.rb +50 -1
- data/lib/additional_tags.rb +5 -6
- data/lib/tasks/additional_tags.rake +18 -0
- metadata +6 -3
- data/.github/workflows/brakeman.yml +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d10d32f78d464486a2921a86274972ec068f3aad16c988f9037e89a433ac4efa
|
4
|
+
data.tar.gz: 50833ac552b8b3de0cba5eb5d9710cdbafdf17b3c2d53e742031ed22a52c9e86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 .
|
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
|
data/.github/workflows/tests.yml
CHANGED
@@ -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
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
|
-
[](https://www.redmine.org/plugins/additional_tags) [](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3A%22Run+Linters%22) [](https://www.redmine.org/plugins/additional_tags) [](https://github.com/AlphaNodes/additional_tags/actions?query=workflow%3A%22Run+Linters%22) [](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.
|
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.
|
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(
|
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
|
185
|
-
|
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?(:
|
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
|
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
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
|
@@ -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 =
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
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:
|
42
|
+
filter: issue_tag_status_filter(operator: 'o'),
|
48
43
|
use_colors: false,
|
49
|
-
name:
|
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:
|
48
|
+
filter: issue_tag_status_filter(operator: 'c'),
|
54
49
|
use_colors: false,
|
55
|
-
name:
|
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
|
54
|
+
filter: issue_tag_status_filter(open_issues_only: open_issues_only),
|
60
55
|
use_colors: false,
|
61
|
-
name: open_issues_only ?
|
56
|
+
name: open_issues_only ? counts[tag.name][:open] : counts[tag.name][:total]
|
62
57
|
- else
|
63
58
|
p.nodata = l :label_no_data
|
data/assets/stylesheets/tags.css
CHANGED
@@ -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:
|
30
|
-
border-radius:
|
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:
|
41
|
-
|
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:
|
44
|
+
border-radius: 0.75rem;
|
44
45
|
}
|
45
46
|
|
46
|
-
.additional-tag-label-color
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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.
|
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?(
|
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
|
-
@
|
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
|
-
@
|
27
|
-
|
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
|
-
|
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, "%#{
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
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:,
|
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}
|
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
|
-
|
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
|
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
|
98
|
+
send :safe_attributes_without_tags=, attrs, user
|
93
99
|
end
|
94
100
|
|
95
101
|
private
|
data/lib/additional_tags/tags.rb
CHANGED
@@ -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)
|
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
|
|
data/lib/additional_tags.rb
CHANGED
@@ -17,12 +17,7 @@ module AdditionalTags
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def setup
|
20
|
-
|
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.
|
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:
|
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/
|
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
|