additional_tags 1.0.1 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/linters.yml +6 -1
- data/.github/workflows/tests.yml +11 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +55 -5
- data/.slim-lint.yml +3 -2
- data/README.md +16 -17
- data/Rakefile +2 -0
- data/additional_tags.gemspec +10 -6
- data/app/controllers/additional_tags_controller.rb +4 -1
- data/app/controllers/issue_tags_controller.rb +6 -4
- data/app/helpers/additional_tags_helper.rb +91 -52
- data/app/helpers/additional_tags_issues_helper.rb +15 -3
- data/app/helpers/additional_tags_wiki_helper.rb +4 -26
- data/app/jobs/additional_tags_job.rb +2 -0
- data/app/jobs/additional_tags_remove_unused_tag_job.rb +7 -0
- data/app/models/migrate_tag.rb +2 -0
- data/app/models/migrate_tagging.rb +2 -0
- data/app/models/query_tags_column.rb +7 -0
- 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/_tag_list.html.slim +1 -1
- data/app/views/additional_tags/merge.html.slim +4 -3
- 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/context_menus/_issues_tags.html.slim +1 -1
- data/app/views/dashboards/blocks/_issue_tags.html.slim +58 -0
- data/app/views/dashboards/blocks/_issue_tags_settings.html.slim +20 -0
- data/app/views/issue_tags/_edit_modal.html.slim +6 -4
- data/app/views/issues/_tags_form.html.slim +1 -1
- data/assets/javascripts/tags.js +4 -3
- data/assets/stylesheets/tags.css +27 -13
- data/config/initializers/zeitwerk.rb +6 -0
- data/config/locales/bg.yml +5 -0
- data/config/locales/cs.yml +5 -0
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +5 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/it.yml +5 -0
- data/config/locales/ja.yml +5 -0
- data/config/locales/ko.yml +5 -0
- data/config/locales/pl.yml +5 -0
- data/config/locales/pt-BR.yml +5 -0
- data/config/locales/ru.yml +28 -23
- data/config/routes.rb +2 -0
- data/db/migrate/20201116145429_acts_as_taggable_migration.rb +3 -1
- data/db/migrate/20201123093214_migrate_existing_tags.rb +5 -3
- data/init.rb +11 -6
- data/lib/additional_tags/hooks/model_hook.rb +13 -0
- data/lib/additional_tags/hooks/view_hook.rb +77 -0
- data/lib/additional_tags/patches/agile_boards_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/agile_query_patch.rb +11 -9
- data/lib/additional_tags/patches/agile_versions_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/agile_versions_query_patch.rb +3 -1
- data/lib/additional_tags/patches/auto_completes_controller_patch.rb +8 -7
- data/lib/additional_tags/patches/calendars_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/dashboard_async_blocks_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/dashboard_content_patch.rb +28 -0
- data/lib/additional_tags/patches/dashboards_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/gantts_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/imports_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/issue_patch.rb +19 -20
- data/lib/additional_tags/patches/issue_query_patch.rb +17 -14
- data/lib/additional_tags/patches/issues_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/journal_patch.rb +2 -0
- data/lib/additional_tags/patches/my_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/queries_helper_patch.rb +3 -15
- data/lib/additional_tags/patches/query_patch.rb +83 -0
- data/lib/additional_tags/patches/settings_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/time_entry_patch.rb +2 -0
- data/lib/additional_tags/patches/time_entry_query_patch.rb +5 -15
- data/lib/additional_tags/patches/time_report_patch.rb +2 -0
- data/lib/additional_tags/patches/timelog_controller_patch.rb +2 -0
- data/lib/additional_tags/patches/wiki_controller_patch.rb +3 -1
- data/lib/additional_tags/patches/wiki_page_patch.rb +54 -3
- data/lib/additional_tags/plugin_version.rb +7 -0
- data/lib/additional_tags/tags.rb +78 -18
- data/lib/additional_tags.rb +53 -74
- data/lib/tasks/additional_tags.rake +18 -0
- metadata +32 -9
- data/.github/workflows/brakeman.yml +0 -33
- data/app/views/reports/_tags_simple.html.slim +0 -11
- data/lib/additional_tags/hooks.rb +0 -73
- data/lib/additional_tags/patches/reports_controller_patch.rb +0 -32
- data/lib/additional_tags/version.rb +0 -3
@@ -1,15 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
module Patches
|
3
5
|
module IssuePatch
|
4
6
|
extend ActiveSupport::Concern
|
5
7
|
|
6
8
|
included do
|
9
|
+
include Additionals::EntityMethodsGlobal
|
7
10
|
include InstanceMethods
|
8
11
|
acts_as_ordered_taggable
|
9
12
|
|
10
13
|
before_save :prepare_save_tag_change
|
11
14
|
before_save :sort_tag_list
|
12
15
|
|
16
|
+
after_commit :add_remove_unused_tags_job, on: %i[update destroy],
|
17
|
+
if: proc { AdditionalTags.setting?(:active_issue_tags) }
|
18
|
+
|
13
19
|
alias_method :safe_attributes_without_tags=, :safe_attributes=
|
14
20
|
alias_method :safe_attributes=, :safe_attributes_with_tags=
|
15
21
|
|
@@ -20,25 +26,24 @@ module AdditionalTags
|
|
20
26
|
class_methods do
|
21
27
|
def allowed_tags?(tags)
|
22
28
|
allowed_tags = available_tags.map(&:name)
|
23
|
-
tags.all? { |tag| allowed_tags.include?
|
29
|
+
tags.all? { |tag| allowed_tags.include? tag }
|
24
30
|
end
|
25
31
|
|
26
|
-
def
|
27
|
-
|
32
|
+
def group_by_status_with_tags(project = nil)
|
33
|
+
visible(User.current, project: project).joins(:status)
|
34
|
+
.joins(:tags)
|
35
|
+
.group(:is_closed, 'tag_id')
|
36
|
+
.count
|
28
37
|
end
|
29
38
|
|
30
|
-
def available_tags(options
|
39
|
+
def available_tags(**options)
|
31
40
|
options[:permission] = :view_issues
|
32
|
-
tags = AdditionalTags::Tags.available_tags self, options
|
41
|
+
tags = AdditionalTags::Tags.available_tags self, **options
|
33
42
|
return tags unless options[:open_issues_only]
|
34
43
|
|
35
44
|
tags.joins("JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id")
|
36
45
|
.where(issue_statuses: { is_closed: false })
|
37
46
|
end
|
38
|
-
|
39
|
-
def remove_unused_tags!
|
40
|
-
AdditionalTagsRemoveUnusedTagJob.perform_later
|
41
|
-
end
|
42
47
|
end
|
43
48
|
|
44
49
|
module InstanceMethods
|
@@ -62,24 +67,18 @@ module AdditionalTags
|
|
62
67
|
end
|
63
68
|
end
|
64
69
|
|
65
|
-
send
|
70
|
+
send :safe_attributes_without_tags=, attrs, user
|
66
71
|
end
|
67
72
|
|
73
|
+
# copy_from requires hash for Redmine - works with Ruby 3
|
74
|
+
# rubocop: disable Style/OptionHash
|
68
75
|
def copy_from_with_tags(arg, options = {})
|
69
|
-
copy_from_without_tags
|
76
|
+
copy_from_without_tags arg, options
|
70
77
|
issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
|
71
78
|
self.tag_list = issue.tag_list
|
72
79
|
self
|
73
80
|
end
|
74
|
-
|
75
|
-
def tags_to_journal(old_tags, new_tags)
|
76
|
-
return if current_journal.blank? || old_tags == new_tags
|
77
|
-
|
78
|
-
current_journal.details << JournalDetail.new(property: 'attr',
|
79
|
-
prop_key: 'tag_list',
|
80
|
-
old_value: old_tags,
|
81
|
-
value: new_tags)
|
82
|
-
end
|
81
|
+
# rubocop: enable Style/OptionHash
|
83
82
|
|
84
83
|
private
|
85
84
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
module Patches
|
3
5
|
module IssueQueryPatch
|
@@ -5,6 +7,7 @@ module AdditionalTags
|
|
5
7
|
|
6
8
|
included do
|
7
9
|
include AdditionalsQuery
|
10
|
+
prepend InstanceOverwriteMethods
|
8
11
|
include InstanceMethods
|
9
12
|
|
10
13
|
alias_method :initialize_available_filters_without_tags, :initialize_available_filters
|
@@ -12,9 +15,20 @@ module AdditionalTags
|
|
12
15
|
|
13
16
|
alias_method :available_columns_without_tags, :available_columns
|
14
17
|
alias_method :available_columns, :available_columns_with_tags
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceOverwriteMethods
|
21
|
+
def build_from_params(params, defaults = {})
|
22
|
+
super
|
23
|
+
|
24
|
+
return self if params[:tag_id].blank?
|
15
25
|
|
16
|
-
|
17
|
-
|
26
|
+
add_filter 'tags',
|
27
|
+
'=',
|
28
|
+
[ActsAsTaggableOn::Tag.find_by(id: params[:tag_id]).try(:name)]
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
18
32
|
end
|
19
33
|
|
20
34
|
module InstanceMethods
|
@@ -31,24 +45,13 @@ module AdditionalTags
|
|
31
45
|
@available_columns = available_columns_without_tags
|
32
46
|
|
33
47
|
if AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, project, global: true)
|
34
|
-
@available_columns <<
|
48
|
+
@available_columns << ::QueryTagsColumn.new
|
35
49
|
end
|
36
50
|
else
|
37
51
|
available_columns_without_tags
|
38
52
|
end
|
39
53
|
@available_columns
|
40
54
|
end
|
41
|
-
|
42
|
-
def build_from_params_with_tags(params, defaults = {})
|
43
|
-
build_from_params_without_tags params, defaults
|
44
|
-
return self if params[:tag_id].blank?
|
45
|
-
|
46
|
-
add_filter 'tags',
|
47
|
-
'=',
|
48
|
-
[ActsAsTaggableOn::Tag.find_by(id: params[:tag_id]).try(:name)]
|
49
|
-
|
50
|
-
self
|
51
|
-
end
|
52
55
|
end
|
53
56
|
end
|
54
57
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
module Patches
|
3
5
|
module QueriesHelperPatch
|
@@ -8,25 +10,11 @@ module AdditionalTags
|
|
8
10
|
|
9
11
|
alias_method :column_content_without_tags, :column_content
|
10
12
|
alias_method :column_content, :column_content_with_tags
|
11
|
-
|
12
|
-
alias_method :csv_content_without_tags, :csv_content
|
13
|
-
alias_method :csv_content, :csv_content_with_tags
|
14
13
|
end
|
15
14
|
|
16
15
|
module InstanceMethods
|
17
|
-
def csv_content_with_tags(column, item)
|
18
|
-
if item.is_a?(Issue) && column.name == :tags ||
|
19
|
-
item.is_a?(TimeEntry) && column.name == :issue_tags
|
20
|
-
|
21
|
-
additional_plain_tag_list column.value_object(item)
|
22
|
-
else
|
23
|
-
csv_content_without_tags column, item
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
16
|
def column_content_with_tags(column, item)
|
28
|
-
if item.is_a?(Issue) && column.name == :tags
|
29
|
-
item.is_a?(TimeEntry) && column.name == :issue_tags
|
17
|
+
if column.name == :issue_tags || item.is_a?(Issue) && column.name == :tags
|
30
18
|
additional_tag_links column.value(item),
|
31
19
|
tag_controller: 'issues',
|
32
20
|
use_colors: AdditionalTags.setting?(:use_colors)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AdditionalTags
|
4
|
+
module Patches
|
5
|
+
module QueryPatch
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def sql_for_tags_field(field, _operator, values)
|
14
|
+
build_sql_for_tags_field klass: queried_class, operator: operator_for(field), values: values
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_tags_filter(position: nil)
|
18
|
+
add_available_filter 'tags', order: position,
|
19
|
+
type: :list_optional,
|
20
|
+
values: -> { available_tag_values queried_class }
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize_issue_tags_filter
|
24
|
+
return unless AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, project, global: true)
|
25
|
+
|
26
|
+
add_available_filter 'issue.tags',
|
27
|
+
type: :list_optional,
|
28
|
+
name: l('label_attribute_of_issue', name: l(:field_tags)),
|
29
|
+
values: -> { available_tag_values Issue }
|
30
|
+
end
|
31
|
+
|
32
|
+
def available_tag_values(klass)
|
33
|
+
klass.available_tags(project: project)
|
34
|
+
.pluck(:name)
|
35
|
+
.map { |name| [name, name] }
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_subquery_for_tags_field(klass:, operator:, values:, joined_table:, joined_field:,
|
39
|
+
source_field: 'id', target_field: 'issue_id')
|
40
|
+
quoted_joined_table = self.class.connection.quote_table_name joined_table
|
41
|
+
quoted_joined_field = self.class.connection.quote_column_name joined_field
|
42
|
+
quoted_source_field = self.class.connection.quote_column_name source_field
|
43
|
+
quoted_target_field = self.class.connection.quote_column_name target_field
|
44
|
+
subsql = ActsAsTaggableOn::Tagging.joins("INNER JOIN #{quoted_joined_table}" \
|
45
|
+
" ON additional_taggings.taggable_id = #{quoted_joined_table}.#{quoted_target_field}")
|
46
|
+
.where(taggable_type: klass.name)
|
47
|
+
.where("#{self.class.connection.quote_table_name queried_table_name}.#{quoted_source_field} ="\
|
48
|
+
" #{quoted_joined_table}.#{quoted_joined_field}")
|
49
|
+
.select(1)
|
50
|
+
|
51
|
+
if %w[= !].include? operator
|
52
|
+
ids_list = klass.tagged_with(values, any: true).pluck :id
|
53
|
+
subsql = subsql.where taggable_id: ids_list
|
54
|
+
end
|
55
|
+
|
56
|
+
if %w[= *].include? operator
|
57
|
+
" EXISTS(#{subsql.to_sql})"
|
58
|
+
else
|
59
|
+
" NOT EXISTS(#{subsql.to_sql})"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_sql_for_tags_field(klass:, operator:, values:)
|
64
|
+
compare = ['=', '*'].include?(operator) ? 'IN' : 'NOT IN'
|
65
|
+
case operator
|
66
|
+
when '=', '!'
|
67
|
+
ids_list = klass.tagged_with(values, any: true).pluck :id
|
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
|
74
|
+
else
|
75
|
+
entries = ActsAsTaggableOn::Tagging.where taggable_type: klass.name
|
76
|
+
id_table = klass.table_name
|
77
|
+
"(#{id_table}.id #{compare} (#{entries.select(:taggable_id).to_sql}))"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_dependency 'time_entry_query'
|
2
4
|
|
3
5
|
module AdditionalTags
|
@@ -18,13 +20,7 @@ module AdditionalTags
|
|
18
20
|
module InstanceMethods
|
19
21
|
def initialize_available_filters_with_tags
|
20
22
|
initialize_available_filters_without_tags
|
21
|
-
|
22
|
-
return unless AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, project, global: true)
|
23
|
-
|
24
|
-
add_available_filter 'issue.tags',
|
25
|
-
type: :list_optional,
|
26
|
-
name: l('label_attribute_of_issue', name: l(:field_tags)),
|
27
|
-
values: -> { available_issue_tags_values }
|
23
|
+
initialize_issue_tags_filter
|
28
24
|
end
|
29
25
|
|
30
26
|
def available_columns_with_tags
|
@@ -39,14 +35,8 @@ module AdditionalTags
|
|
39
35
|
@available_columns
|
40
36
|
end
|
41
37
|
|
42
|
-
def sql_for_issue_tags_field(_field, operator,
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
def available_issue_tags_values
|
47
|
-
Issue.available_tags(project: project)
|
48
|
-
.pluck(:name)
|
49
|
-
.map { |name| [name, name] }
|
38
|
+
def sql_for_issue_tags_field(_field, operator, values)
|
39
|
+
build_sql_for_tags_field klass: Issue, operator: operator, values: values
|
50
40
|
end
|
51
41
|
end
|
52
42
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
module Patches
|
3
5
|
module WikiControllerPatch
|
@@ -29,7 +31,7 @@ module AdditionalTags
|
|
29
31
|
@tag = params[:tag]
|
30
32
|
return super unless AdditionalTags.setting?(:active_wiki_tags) && @tag.present?
|
31
33
|
|
32
|
-
@pages =
|
34
|
+
@pages = WikiPage.with_tags @tag, project: @project
|
33
35
|
|
34
36
|
respond_to do |format|
|
35
37
|
format.html do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
module Patches
|
3
5
|
module WikiPagePatch
|
@@ -20,10 +22,59 @@ module AdditionalTags
|
|
20
22
|
"JOIN #{Project.table_name} ON wikis.project_id = #{Project.table_name}.id"]
|
21
23
|
end
|
22
24
|
|
23
|
-
def available_tags(options
|
25
|
+
def available_tags(**options)
|
24
26
|
options[:project_join] = project_joins
|
25
27
|
options[:permission] = :view_wiki_pages
|
26
|
-
AdditionalTags::Tags.available_tags self, options
|
28
|
+
AdditionalTags::Tags.available_tags self, **options
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_tags_scope(project: nil, wiki: nil)
|
32
|
+
scope = if wiki
|
33
|
+
wiki.pages
|
34
|
+
else
|
35
|
+
scope = WikiPage.joins wiki: :project
|
36
|
+
scope = scope.where wikis: { project_id: project.id } if project
|
37
|
+
scope
|
38
|
+
end
|
39
|
+
|
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
|
48
|
+
scope = scope.limit max_entries if max_entries
|
49
|
+
|
50
|
+
tags = Array tag
|
51
|
+
tags.map!(&:strip)
|
52
|
+
tags.reject!(&:blank?)
|
53
|
+
return [] if tags.count.zero?
|
54
|
+
|
55
|
+
tags.map!(&:downcase)
|
56
|
+
|
57
|
+
scope = scope.where(id: tagged_with(tags, any: true).ids)
|
58
|
+
.with_updated_on
|
59
|
+
|
60
|
+
return scope if order.nil?
|
61
|
+
|
62
|
+
sorted = order.split '_'
|
63
|
+
raise 'unsupported sort order' if sorted != 2 && %w[title date].exclude?(sorted.first)
|
64
|
+
|
65
|
+
order_dir = sorted.second == 'desc' ? 'DESC' : 'ASC'
|
66
|
+
|
67
|
+
case sorted.first
|
68
|
+
when 'date'
|
69
|
+
scope.joins(:content)
|
70
|
+
.reorder("#{WikiContent.table_name}.updated_on #{order_dir}")
|
71
|
+
else
|
72
|
+
if wiki.nil?
|
73
|
+
scope.order "#{Project.table_name}.name, #{WikiPage.table_name}.title #{order_dir}"
|
74
|
+
else
|
75
|
+
scope.includes(:parent).order "#{WikiPage.table_name}.title #{order_dir}"
|
76
|
+
end
|
77
|
+
end
|
27
78
|
end
|
28
79
|
end
|
29
80
|
|
@@ -44,7 +95,7 @@ module AdditionalTags
|
|
44
95
|
end
|
45
96
|
end
|
46
97
|
|
47
|
-
send
|
98
|
+
send :safe_attributes_without_tags=, attrs, user
|
48
99
|
end
|
49
100
|
|
50
101
|
private
|
data/lib/additional_tags/tags.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AdditionalTags
|
2
4
|
class Tags
|
3
5
|
class << self
|
4
|
-
def available_tags(klass, options
|
6
|
+
def available_tags(klass, **options)
|
5
7
|
user = options[:user].presence || User.current
|
6
8
|
|
7
9
|
scope = ActsAsTaggableOn::Tag.where({})
|
8
|
-
scope = scope.where
|
10
|
+
scope = scope.where "#{Project.table_name}.id = ?", options[:project] if options[:project]
|
9
11
|
if options[:permission]
|
10
|
-
scope = scope.where
|
12
|
+
scope = scope.where tag_access(options[:permission], user)
|
11
13
|
elsif options[:visible_condition]
|
12
|
-
scope = scope.where
|
14
|
+
scope = scope.where klass.visible_condition(user)
|
13
15
|
end
|
14
|
-
scope = scope.where
|
15
|
-
scope = scope.where
|
16
|
-
scope = scope.where
|
17
|
-
scope = scope.where
|
16
|
+
scope = scope.where "LOWER(#{TAG_TABLE_NAME}.name) LIKE ?", "%#{options[:name_like].downcase}%" if options[:name_like]
|
17
|
+
scope = scope.where "#{TAG_TABLE_NAME}.name=?", options[:name] if options[:name]
|
18
|
+
scope = scope.where "#{TAGGING_TABLE_NAME}.taggable_id!=?", options[:exclude_id] if options[:exclude_id]
|
19
|
+
scope = scope.where options[:where_field] => options[:where_value] if options[:where_field].present? && options[:where_value]
|
18
20
|
|
19
21
|
columns = ["#{TAG_TABLE_NAME}.id",
|
20
22
|
"#{TAG_TABLE_NAME}.name",
|
@@ -24,14 +26,14 @@ module AdditionalTags
|
|
24
26
|
columns << "MIN(#{TAGGING_TABLE_NAME}.created_at) AS last_created" if options[:sort_by] == 'last_created'
|
25
27
|
|
26
28
|
scope.select(columns.join(', '))
|
27
|
-
.joins(tag_for_joins(klass, options))
|
29
|
+
.joins(tag_for_joins(klass, **options.slice(:project_join, :project, :without_projects)))
|
28
30
|
.group("#{TAG_TABLE_NAME}.id, #{TAG_TABLE_NAME}.name, #{TAG_TABLE_NAME}.taggings_count").having('COUNT(*) > 0')
|
29
31
|
.order(build_order_sql(options[:sort_by], options[:order]))
|
30
32
|
end
|
31
33
|
|
32
|
-
def all_type_tags(klass,
|
33
|
-
ActsAsTaggableOn::Tag.
|
34
|
-
.joins(tag_for_joins(klass,
|
34
|
+
def all_type_tags(klass, without_projects: false)
|
35
|
+
ActsAsTaggableOn::Tag.all
|
36
|
+
.joins(tag_for_joins(klass, without_projects: without_projects))
|
35
37
|
.distinct
|
36
38
|
.order("#{TAG_TABLE_NAME}.name")
|
37
39
|
end
|
@@ -61,8 +63,9 @@ module AdditionalTags
|
|
61
63
|
# remove old (merged) tags
|
62
64
|
tags_to_merge.reject { |t| t.id == tag.id }.each(&:destroy)
|
63
65
|
# remove duplicate taggings
|
64
|
-
dup_scope = ActsAsTaggableOn::Tagging.where
|
65
|
-
valid_ids = dup_scope.group(:tag_id, :taggable_id, :taggable_type, :tagger_id, :tagger_type, :context)
|
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)
|
68
|
+
.pluck(Arel.sql('MIN(id)'))
|
66
69
|
dup_scope.where.not(id: valid_ids).destroy_all if valid_ids.any?
|
67
70
|
# recalc count for new tag
|
68
71
|
ActsAsTaggableOn::Tag.reset_counters tag.id, :taggings
|
@@ -83,8 +86,65 @@ module AdditionalTags
|
|
83
86
|
end
|
84
87
|
end
|
85
88
|
|
89
|
+
def build_relation_tags(entries)
|
90
|
+
entries = Array entries
|
91
|
+
return [] if entries.none?
|
92
|
+
|
93
|
+
tags = entries.map(&:tags)
|
94
|
+
tags.flatten!
|
95
|
+
|
96
|
+
tags.uniq
|
97
|
+
end
|
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
|
+
|
86
128
|
private
|
87
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
|
+
|
88
148
|
def build_order_sql(sort_by, order)
|
89
149
|
order = order.present? && order == 'DESC' ? 'DESC' : 'ASC'
|
90
150
|
|
@@ -100,16 +160,16 @@ module AdditionalTags
|
|
100
160
|
Arel.sql sql
|
101
161
|
end
|
102
162
|
|
103
|
-
def tag_for_joins(klass,
|
163
|
+
def tag_for_joins(klass, project_join: nil, project: nil, without_projects: false)
|
104
164
|
table_name = klass.table_name
|
105
165
|
|
106
166
|
joins = ["JOIN #{TAGGING_TABLE_NAME} ON #{TAGGING_TABLE_NAME}.tag_id = #{TAG_TABLE_NAME}.id"]
|
107
167
|
joins << "JOIN #{table_name} " \
|
108
168
|
"ON #{table_name}.id = #{TAGGING_TABLE_NAME}.taggable_id AND #{TAGGING_TABLE_NAME}.taggable_type = '#{klass}'"
|
109
169
|
|
110
|
-
if
|
111
|
-
joins <<
|
112
|
-
elsif
|
170
|
+
if project_join
|
171
|
+
joins << project_join
|
172
|
+
elsif project || !without_projects
|
113
173
|
joins << "JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id"
|
114
174
|
end
|
115
175
|
|