additional_tags 1.0.5 → 1.0.7
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 +6 -6
- data/.github/workflows/linters.yml +16 -4
- data/.github/workflows/tests.yml +7 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +9 -6
- data/.stylelintrc.json +12 -155
- data/README.md +28 -9
- data/app/controllers/additional_tags_controller.rb +10 -10
- data/app/controllers/issue_tags_controller.rb +3 -1
- data/app/helpers/additional_tags_helper.rb +39 -30
- data/app/helpers/additional_tags_issues_helper.rb +1 -3
- data/app/helpers/additional_tags_wiki_helper.rb +16 -10
- data/app/jobs/additional_tags_remove_unused_tag_job.rb +11 -6
- data/app/models/additional_tag.rb +98 -0
- data/app/views/additional_tags/_tag_list.html.slim +12 -6
- data/app/views/additional_tags/merge.html.slim +0 -1
- data/app/views/additional_tags/settings/_general.html.slim +7 -1
- data/app/views/additional_tags/settings/_manage_tags.html.slim +8 -1
- data/app/views/context_menus/_issues_tags.html.slim +7 -1
- data/app/views/dashboards/blocks/_issue_tags.html.slim +9 -7
- data/app/views/issue_tags/_edit_modal.html.slim +7 -7
- data/app/views/issue_tags/edit.js.erb +1 -1
- data/app/views/issues/_tags.html.slim +12 -7
- data/app/views/issues/_tags_bulk_edit.html.slim +2 -1
- data/app/views/issues/_tags_form_details.html.slim +1 -2
- data/app/views/wiki/_tags_form.html.slim +4 -0
- data/app/views/wiki/_tags_form_bottom.html.slim +1 -2
- data/app/views/wiki/_tags_show.html.slim +7 -7
- data/app/views/wiki/tag_index.html.slim +2 -2
- data/assets/javascripts/tags.js +3 -3
- data/assets/stylesheets/tags.css +41 -16
- data/config/locales/bg.yml +18 -11
- data/config/locales/cs.yml +19 -12
- data/config/locales/de.yml +19 -12
- data/config/locales/en.yml +19 -12
- data/config/locales/es.yml +19 -12
- data/config/locales/fr.yml +19 -12
- data/config/locales/it.yml +19 -12
- data/config/locales/ja.yml +18 -11
- data/config/locales/ko.yml +18 -11
- data/config/locales/pl.yml +19 -12
- data/config/locales/pt-BR.yml +18 -11
- data/config/locales/ru.yml +34 -27
- data/config/settings.yml +5 -5
- data/doc/images/additional-tags-framework.png +0 -0
- data/doc/images/additional-tags.gif +0 -0
- data/doc/images/tag-overview.png +0 -0
- data/lib/additional_tags/hooks/view_hook.rb +15 -2
- data/lib/additional_tags/patches/issue_patch.rb +53 -7
- data/lib/additional_tags/patches/issue_query_patch.rb +18 -0
- data/lib/additional_tags/patches/queries_helper_patch.rb +1 -3
- data/lib/additional_tags/patches/query_patch.rb +23 -2
- data/lib/additional_tags/patches/wiki_page_patch.rb +8 -2
- data/lib/additional_tags/plugin_version.rb +1 -1
- data/lib/additional_tags/tags.rb +35 -14
- data/lib/additional_tags.rb +5 -0
- data/package.json +8 -0
- metadata +6 -3
@@ -13,6 +13,8 @@ module AdditionalTags
|
|
13
13
|
before_save :prepare_save_tag_change
|
14
14
|
before_save :sort_tag_list
|
15
15
|
|
16
|
+
validate :validate_tags, if: proc { AdditionalTags.setting?(:active_issue_tags) }
|
17
|
+
|
16
18
|
after_commit :add_remove_unused_tags_job, on: %i[update destroy],
|
17
19
|
if: proc { AdditionalTags.setting?(:active_issue_tags) }
|
18
20
|
|
@@ -29,21 +31,58 @@ module AdditionalTags
|
|
29
31
|
tags.all? { |tag| allowed_tags.include? tag }
|
30
32
|
end
|
31
33
|
|
32
|
-
def group_by_status_with_tags(project = nil)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def group_by_status_with_tags(project = nil, user = User.current)
|
35
|
+
scope = if project && Setting.display_subprojects_issues?
|
36
|
+
visible(user).where(AdditionalTags::Tags.subproject_sql(project))
|
37
|
+
else
|
38
|
+
visible user, project: project
|
39
|
+
end
|
40
|
+
|
41
|
+
scope.joins(:status)
|
42
|
+
.joins(:tags)
|
43
|
+
.group(:is_closed, 'tag_id')
|
44
|
+
.count
|
37
45
|
end
|
38
46
|
|
39
47
|
def available_tags(**options)
|
40
|
-
options[:permission]
|
48
|
+
options[:permission] ||= :view_issue_tags
|
41
49
|
tags = AdditionalTags::Tags.available_tags self, **options
|
42
50
|
return tags unless options[:open_issues_only]
|
43
51
|
|
44
52
|
tags.joins("JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id")
|
45
53
|
.where(issue_statuses: { is_closed: false })
|
46
54
|
end
|
55
|
+
|
56
|
+
def common_tag_list_from_issues(ids)
|
57
|
+
common_tags = ActsAsTaggableOn::Tag.joins(:taggings)
|
58
|
+
.select(
|
59
|
+
"#{ActiveRecord::Base.connection.quote_table_name ActsAsTaggableOn.tags_table}.id",
|
60
|
+
"#{ActiveRecord::Base.connection.quote_table_name ActsAsTaggableOn.tags_table}.name"
|
61
|
+
)
|
62
|
+
.where(taggings: { taggable_type: 'Issue', taggable_id: ids })
|
63
|
+
.group(
|
64
|
+
"#{ActiveRecord::Base.connection.quote_table_name ActsAsTaggableOn.tags_table}.id",
|
65
|
+
"#{ActiveRecord::Base.connection.quote_table_name ActsAsTaggableOn.tags_table}.name"
|
66
|
+
)
|
67
|
+
.having('count(*) = ?', ids.count).to_a
|
68
|
+
|
69
|
+
ActsAsTaggableOn::TagList.new common_tags
|
70
|
+
end
|
71
|
+
|
72
|
+
def load_visible_tags(issues, user = User.current)
|
73
|
+
return if issues.blank?
|
74
|
+
|
75
|
+
available_projects = Project.where(AdditionalTags::Tags.visible_condition(user)).ids
|
76
|
+
|
77
|
+
issues.each do |issue|
|
78
|
+
tags = if available_projects.include? issue.project_id
|
79
|
+
issue.tags
|
80
|
+
else
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
issue.instance_variable_set :@visible_tags, tags
|
84
|
+
end
|
85
|
+
end
|
47
86
|
end
|
48
87
|
|
49
88
|
module InstanceMethods
|
@@ -62,7 +101,7 @@ module AdditionalTags
|
|
62
101
|
tags = attrs.delete :tag_list
|
63
102
|
tags = Array(tags).reject(&:empty?)
|
64
103
|
|
65
|
-
Additionals.debug "tags: #{tags.inspect} - project: #{project&.id} - access: #{user.allowed_to? :create_issue_tags, project}"
|
104
|
+
# Additionals.debug "tags: #{tags.inspect} - project: #{project&.id} - access: #{user.allowed_to? :create_issue_tags, project}"
|
66
105
|
|
67
106
|
if user.allowed_to?(:create_issue_tags, project) ||
|
68
107
|
user.allowed_to?(:edit_issue_tags, project) && Issue.allowed_tags?(tags)
|
@@ -88,6 +127,13 @@ module AdditionalTags
|
|
88
127
|
|
89
128
|
self.tag_list = AdditionalTags::Tags.sort_tags tags
|
90
129
|
end
|
130
|
+
|
131
|
+
def validate_tags
|
132
|
+
return if !User.current.allowed_to?(:create_issue_tags, project) &&
|
133
|
+
!(User.current.allowed_to?(:edit_issue_tags, project) && Issue.allowed_tags?(tags))
|
134
|
+
|
135
|
+
errors.add :tag_list, :invalid_mutually_exclusive_tags unless AdditionalTag.valid_mutually_exclusive_tag tag_list
|
136
|
+
end
|
91
137
|
end
|
92
138
|
end
|
93
139
|
end
|
@@ -15,6 +15,9 @@ module AdditionalTags
|
|
15
15
|
|
16
16
|
alias_method :available_columns_without_tags, :available_columns
|
17
17
|
alias_method :available_columns, :available_columns_with_tags
|
18
|
+
|
19
|
+
alias_method :issues_without_tags, :issues
|
20
|
+
alias_method :issues, :issues_with_tags
|
18
21
|
end
|
19
22
|
|
20
23
|
module InstanceOverwriteMethods
|
@@ -29,9 +32,24 @@ module AdditionalTags
|
|
29
32
|
|
30
33
|
self
|
31
34
|
end
|
35
|
+
|
36
|
+
def sql_for_tags_field(field, _operator, values)
|
37
|
+
build_sql_for_tags_field_with_permission klass: queried_class,
|
38
|
+
operator: operator_for(field),
|
39
|
+
values: values,
|
40
|
+
permission: :view_issue_tags
|
41
|
+
end
|
32
42
|
end
|
33
43
|
|
34
44
|
module InstanceMethods
|
45
|
+
def issues_with_tags(**options)
|
46
|
+
issues = issues_without_tags(**options)
|
47
|
+
return issues unless has_column? :tags
|
48
|
+
|
49
|
+
Issue.load_visible_tags issues
|
50
|
+
issues
|
51
|
+
end
|
52
|
+
|
35
53
|
def initialize_available_filters_with_tags
|
36
54
|
initialize_available_filters_without_tags
|
37
55
|
|
@@ -15,9 +15,7 @@ module AdditionalTags
|
|
15
15
|
module InstanceMethods
|
16
16
|
def column_content_with_tags(column, item)
|
17
17
|
if column.name == :issue_tags || item.is_a?(Issue) && column.name == :tags
|
18
|
-
additional_tag_links
|
19
|
-
tag_controller: 'issues',
|
20
|
-
use_colors: AdditionalTags.setting?(:use_colors)
|
18
|
+
additional_tag_links item.instance_variable_get(:@visible_tags), tag_controller: 'issues'
|
21
19
|
else
|
22
20
|
column_content_without_tags column, item
|
23
21
|
end
|
@@ -11,7 +11,9 @@ module AdditionalTags
|
|
11
11
|
|
12
12
|
module InstanceMethods
|
13
13
|
def sql_for_tags_field(field, _operator, values)
|
14
|
-
build_sql_for_tags_field klass: queried_class,
|
14
|
+
build_sql_for_tags_field klass: queried_class,
|
15
|
+
operator: operator_for(field),
|
16
|
+
values: values
|
15
17
|
end
|
16
18
|
|
17
19
|
def initialize_tags_filter(position: nil)
|
@@ -44,7 +46,7 @@ module AdditionalTags
|
|
44
46
|
subsql = ActsAsTaggableOn::Tagging.joins("INNER JOIN #{quoted_joined_table}" \
|
45
47
|
" ON additional_taggings.taggable_id = #{quoted_joined_table}.#{quoted_target_field}")
|
46
48
|
.where(taggable_type: klass.name)
|
47
|
-
.where("#{self.class.connection.quote_table_name queried_table_name}.#{quoted_source_field} ="\
|
49
|
+
.where("#{self.class.connection.quote_table_name queried_table_name}.#{quoted_source_field} =" \
|
48
50
|
" #{quoted_joined_table}.#{quoted_joined_field}")
|
49
51
|
.select(1)
|
50
52
|
|
@@ -60,6 +62,25 @@ module AdditionalTags
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
# NOTE: should be used, if tags required permission check
|
66
|
+
def build_sql_for_tags_field_with_permission(klass:, operator:, values:, permission:)
|
67
|
+
compare = ['=', '*'].include?(operator) ? 'in' : 'not_in'
|
68
|
+
case operator
|
69
|
+
when '=', '!'
|
70
|
+
ids_list = klass.tagged_with(values, any: true).ids
|
71
|
+
# special case: filter with deleted tag
|
72
|
+
return '(1=0)' if ids_list.blank? && values.present? && operator == '='
|
73
|
+
else
|
74
|
+
allowed_projects = Project.where(Project.allowed_to_condition(User.current, permission))
|
75
|
+
.select(:id)
|
76
|
+
ids_list = klass.tagged_with(klass.available_tags(skip_pre_condition: true), any: true)
|
77
|
+
.where(project_id: allowed_projects).ids
|
78
|
+
end
|
79
|
+
|
80
|
+
"(#{klass.arel_table[:id].send(compare, ids_list).to_sql})"
|
81
|
+
end
|
82
|
+
|
83
|
+
# NOTE: should be used, if tags do not require permission check
|
63
84
|
def build_sql_for_tags_field(klass:, operator:, values:)
|
64
85
|
compare = ['=', '*'].include?(operator) ? 'IN' : 'NOT IN'
|
65
86
|
case operator
|
@@ -13,6 +13,7 @@ module AdditionalTags
|
|
13
13
|
alias_method :safe_attributes_without_tags=, :safe_attributes=
|
14
14
|
alias_method :safe_attributes=, :safe_attributes_with_tags=
|
15
15
|
|
16
|
+
validate :validate_tags
|
16
17
|
before_save :sort_tag_list
|
17
18
|
end
|
18
19
|
|
@@ -24,7 +25,7 @@ module AdditionalTags
|
|
24
25
|
|
25
26
|
def available_tags(**options)
|
26
27
|
options[:project_join] = project_joins
|
27
|
-
options[:permission]
|
28
|
+
options[:permission] ||= :view_wiki_pages
|
28
29
|
AdditionalTags::Tags.available_tags self, **options
|
29
30
|
end
|
30
31
|
|
@@ -41,10 +42,11 @@ module AdditionalTags
|
|
41
42
|
scope
|
42
43
|
end
|
43
44
|
|
44
|
-
def with_tags(tag, project: nil, order: 'title_asc', max_entries: nil)
|
45
|
+
def with_tags(tag, project: nil, order: 'title_asc', max_entries: nil, exclude_page: nil)
|
45
46
|
wiki = project&.wiki
|
46
47
|
|
47
48
|
scope = with_tags_scope wiki: wiki, project: project
|
49
|
+
scope = scope.where.not id: exclude_page.id if exclude_page
|
48
50
|
scope = scope.limit max_entries if max_entries
|
49
51
|
|
50
52
|
tags = Array tag
|
@@ -105,6 +107,10 @@ module AdditionalTags
|
|
105
107
|
|
106
108
|
self.tag_list = AdditionalTags::Tags.sort_tags tag_list
|
107
109
|
end
|
110
|
+
|
111
|
+
def validate_tags
|
112
|
+
errors.add :tag_list, :invalid_mutually_exclusive_tags unless AdditionalTag.valid_mutually_exclusive_tag tag_list
|
113
|
+
end
|
108
114
|
end
|
109
115
|
end
|
110
116
|
end
|
data/lib/additional_tags/tags.rb
CHANGED
@@ -3,13 +3,27 @@
|
|
3
3
|
module AdditionalTags
|
4
4
|
class Tags
|
5
5
|
class << self
|
6
|
+
def visible_condition(user, **options)
|
7
|
+
permission = options[:permission] || :view_issue_tags
|
8
|
+
skip_pre_condition = options[:skip_pre_condition] || true
|
9
|
+
|
10
|
+
tag_access permission, user, skip_pre_condition: skip_pre_condition
|
11
|
+
end
|
12
|
+
|
6
13
|
def available_tags(klass, **options)
|
7
14
|
user = options[:user].presence || User.current
|
8
15
|
|
9
16
|
scope = ActsAsTaggableOn::Tag.where({})
|
10
|
-
|
17
|
+
if options[:project]
|
18
|
+
scope = if Setting.display_subprojects_issues?
|
19
|
+
scope.where subproject_sql(options[:project])
|
20
|
+
else
|
21
|
+
scope.where projects: { id: options[:project] }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
11
25
|
if options[:permission]
|
12
|
-
scope = scope.where tag_access(options[:permission], user)
|
26
|
+
scope = scope.where tag_access(options[:permission], user, skip_pre_condition: options[:skip_pre_condition])
|
13
27
|
elsif options[:visible_condition]
|
14
28
|
scope = scope.where klass.visible_condition(user)
|
15
29
|
end
|
@@ -18,14 +32,7 @@ module AdditionalTags
|
|
18
32
|
scope = scope.where "#{TAGGING_TABLE_NAME}.taggable_id!=?", options[:exclude_id] if options[:exclude_id]
|
19
33
|
scope = scope.where options[:where_field] => options[:where_value] if options[:where_field].present? && options[:where_value]
|
20
34
|
|
21
|
-
|
22
|
-
"#{TAG_TABLE_NAME}.name",
|
23
|
-
"#{TAG_TABLE_NAME}.taggings_count",
|
24
|
-
"COUNT(DISTINCT #{TAGGING_TABLE_NAME}.taggable_id) AS count"]
|
25
|
-
|
26
|
-
columns << "MIN(#{TAGGING_TABLE_NAME}.created_at) AS last_created" if options[:sort_by] == 'last_created'
|
27
|
-
|
28
|
-
scope.select(columns.to_list)
|
35
|
+
scope.select(table_columns(options[:sort_by]))
|
29
36
|
.joins(tag_for_joins(klass, **options.slice(:project_join, :project, :without_projects)))
|
30
37
|
.group("#{TAG_TABLE_NAME}.id, #{TAG_TABLE_NAME}.name, #{TAG_TABLE_NAME}.taggings_count").having('COUNT(*) > 0')
|
31
38
|
.order(build_order_sql(options[:sort_by], options[:order]))
|
@@ -49,8 +56,7 @@ module AdditionalTags
|
|
49
56
|
end
|
50
57
|
|
51
58
|
def remove_unused_tags
|
52
|
-
ActsAsTaggableOn::Tag.
|
53
|
-
.where(taggings: { id: nil })
|
59
|
+
ActsAsTaggableOn::Tag.where.missing(:taggings)
|
54
60
|
.each(&:destroy)
|
55
61
|
end
|
56
62
|
|
@@ -126,8 +132,23 @@ module AdditionalTags
|
|
126
132
|
counts
|
127
133
|
end
|
128
134
|
|
135
|
+
def subproject_sql(project)
|
136
|
+
"#{Project.table_name}.lft >= #{project.lft} " \
|
137
|
+
"AND #{Project.table_name}.rgt <= #{project.rgt}"
|
138
|
+
end
|
139
|
+
|
129
140
|
private
|
130
141
|
|
142
|
+
def table_columns(sort_by)
|
143
|
+
columns = ["#{TAG_TABLE_NAME}.id",
|
144
|
+
"#{TAG_TABLE_NAME}.name",
|
145
|
+
"#{TAG_TABLE_NAME}.taggings_count",
|
146
|
+
"COUNT(DISTINCT #{TAGGING_TABLE_NAME}.taggable_id) AS count"]
|
147
|
+
|
148
|
+
columns << "MIN(#{TAGGING_TABLE_NAME}.created_at) AS last_created" if sort_by == 'last_created'
|
149
|
+
columns.to_list
|
150
|
+
end
|
151
|
+
|
131
152
|
def status_for_tag_value(scope:, tag_id:, group_id: nil, group_id_is_bool: false)
|
132
153
|
value = if group_id_is_bool || group_id
|
133
154
|
if group_id_is_bool
|
@@ -177,11 +198,11 @@ module AdditionalTags
|
|
177
198
|
joins
|
178
199
|
end
|
179
200
|
|
180
|
-
def tag_access(permission, user)
|
201
|
+
def tag_access(permission, user, skip_pre_condition: false)
|
181
202
|
projects_allowed = if permission.nil?
|
182
203
|
Project.visible.ids
|
183
204
|
else
|
184
|
-
Project.where(Project.allowed_to_condition(user, permission)).ids
|
205
|
+
Project.where(Project.allowed_to_condition(user, permission, skip_pre_condition: skip_pre_condition)).ids
|
185
206
|
end
|
186
207
|
|
187
208
|
if projects_allowed.present?
|
data/lib/additional_tags.rb
CHANGED
@@ -14,6 +14,11 @@ module AdditionalTags
|
|
14
14
|
setting(:tags_sidebar).present? && setting(:tags_sidebar) != 'none'
|
15
15
|
end
|
16
16
|
|
17
|
+
# color is used by default (if setting is missing, too)
|
18
|
+
def use_colors?
|
19
|
+
setting(:tags_color_theme).to_s != '0'
|
20
|
+
end
|
21
|
+
|
17
22
|
private
|
18
23
|
|
19
24
|
def setup
|
data/package.json
ADDED
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AlphaNodes
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acts-as-taggable-on
|
@@ -92,6 +92,7 @@ files:
|
|
92
92
|
- app/helpers/additional_tags_wiki_helper.rb
|
93
93
|
- app/jobs/additional_tags_job.rb
|
94
94
|
- app/jobs/additional_tags_remove_unused_tag_job.rb
|
95
|
+
- app/models/additional_tag.rb
|
95
96
|
- app/models/migrate_tag.rb
|
96
97
|
- app/models/migrate_tagging.rb
|
97
98
|
- app/models/query_tags_column.rb
|
@@ -143,6 +144,7 @@ files:
|
|
143
144
|
- db/migrate/20201123093214_migrate_existing_tags.rb
|
144
145
|
- doc/images/additional-tags-framework.png
|
145
146
|
- doc/images/additional-tags.gif
|
147
|
+
- doc/images/tag-overview.png
|
146
148
|
- init.rb
|
147
149
|
- lib/additional_tags.rb
|
148
150
|
- lib/additional_tags/hooks/model_hook.rb
|
@@ -175,6 +177,7 @@ files:
|
|
175
177
|
- lib/additional_tags/plugin_version.rb
|
176
178
|
- lib/additional_tags/tags.rb
|
177
179
|
- lib/tasks/additional_tags.rake
|
180
|
+
- package.json
|
178
181
|
homepage: https://github.com/alphanodes/additional_tags
|
179
182
|
licenses:
|
180
183
|
- GPL-2.0
|
@@ -195,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
195
198
|
- !ruby/object:Gem::Version
|
196
199
|
version: '0'
|
197
200
|
requirements: []
|
198
|
-
rubygems_version: 3.3.
|
201
|
+
rubygems_version: 3.3.26
|
199
202
|
signing_key:
|
200
203
|
specification_version: 4
|
201
204
|
summary: Redmine plugin for adding tag functionality
|