additional_tags 1.0.0

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.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/brakeman.yml +38 -0
  3. data/.github/workflows/linters.yml +41 -0
  4. data/.github/workflows/tests.yml +139 -0
  5. data/.gitignore +8 -0
  6. data/.rubocop.yml +75 -0
  7. data/.slim-lint.yml +25 -0
  8. data/.stylelintrc.json +163 -0
  9. data/LICENSE +339 -0
  10. data/README.md +126 -0
  11. data/Rakefile +11 -0
  12. data/additional_tags.gemspec +28 -0
  13. data/app/controllers/additional_tags_controller.rb +80 -0
  14. data/app/controllers/issue_tags_controller.rb +48 -0
  15. data/app/helpers/additional_tags_helper.rb +197 -0
  16. data/app/helpers/additional_tags_issues_helper.rb +43 -0
  17. data/app/helpers/additional_tags_wiki_helper.rb +17 -0
  18. data/app/jobs/additional_tags_job.rb +3 -0
  19. data/app/jobs/additional_tags_remove_unused_tag_job.rb +5 -0
  20. data/app/models/migrate_tag.rb +4 -0
  21. data/app/models/migrate_tagging.rb +5 -0
  22. data/app/views/additional_tags/_html_head.html.slim +6 -0
  23. data/app/views/additional_tags/_tag_list.html.slim +35 -0
  24. data/app/views/additional_tags/context_menu.html.slim +16 -0
  25. data/app/views/additional_tags/edit.html.slim +14 -0
  26. data/app/views/additional_tags/merge.html.slim +17 -0
  27. data/app/views/additional_tags/settings/_general.html.slim +45 -0
  28. data/app/views/additional_tags/settings/_manage_tags.html.slim +35 -0
  29. data/app/views/additional_tags/settings/_settings.html.slim +9 -0
  30. data/app/views/auto_completes/_additional_tag_list.slim +1 -0
  31. data/app/views/context_menus/_issues_tags.html.slim +10 -0
  32. data/app/views/issue_tags/_edit_modal.html.slim +30 -0
  33. data/app/views/issue_tags/edit.js.erb +2 -0
  34. data/app/views/issues/_tags.html.slim +8 -0
  35. data/app/views/issues/_tags_bulk_edit.html.slim +7 -0
  36. data/app/views/issues/_tags_form.html.slim +11 -0
  37. data/app/views/issues/_tags_form_details.html.slim +7 -0
  38. data/app/views/issues/_tags_sidebar.html.slim +5 -0
  39. data/app/views/reports/_tags_simple.html.slim +11 -0
  40. data/app/views/wiki/_tags_form.html.slim +7 -0
  41. data/app/views/wiki/_tags_form_bottom.html.slim +5 -0
  42. data/app/views/wiki/_tags_show.html.slim +9 -0
  43. data/app/views/wiki/_tags_sidebar.html.slim +4 -0
  44. data/app/views/wiki/tag_index.html.slim +21 -0
  45. data/assets/javascripts/tags.js +19 -0
  46. data/assets/stylesheets/tags.css +119 -0
  47. data/config/locales/bg.yml +33 -0
  48. data/config/locales/cs.yml +33 -0
  49. data/config/locales/de.yml +33 -0
  50. data/config/locales/en.yml +33 -0
  51. data/config/locales/es.yml +33 -0
  52. data/config/locales/fr.yml +33 -0
  53. data/config/locales/it.yml +33 -0
  54. data/config/locales/ja.yml +33 -0
  55. data/config/locales/ko.yml +33 -0
  56. data/config/locales/pl.yml +33 -0
  57. data/config/locales/pt-BR.yml +33 -0
  58. data/config/locales/ru.yml +33 -0
  59. data/config/routes.rb +31 -0
  60. data/config/settings.yml +9 -0
  61. data/db/migrate/20201116145429_acts_as_taggable_migration.rb +40 -0
  62. data/db/migrate/20201123093214_migrate_existing_tags.rb +39 -0
  63. data/doc/images/additional-tags-framework.png +0 -0
  64. data/doc/images/additional-tags.gif +0 -0
  65. data/init.rb +31 -0
  66. data/lib/additional_tags.rb +107 -0
  67. data/lib/additional_tags/hooks.rb +63 -0
  68. data/lib/additional_tags/patches/agile_boards_controller_patch.rb +14 -0
  69. data/lib/additional_tags/patches/agile_query_patch.rb +41 -0
  70. data/lib/additional_tags/patches/agile_versions_controller_patch.rb +14 -0
  71. data/lib/additional_tags/patches/agile_versions_query_patch.rb +11 -0
  72. data/lib/additional_tags/patches/auto_completes_controller_patch.rb +42 -0
  73. data/lib/additional_tags/patches/calendars_controller_patch.rb +14 -0
  74. data/lib/additional_tags/patches/dashboard_async_blocks_controller_patch.rb +11 -0
  75. data/lib/additional_tags/patches/dashboards_controller_patch.rb +11 -0
  76. data/lib/additional_tags/patches/gantts_controller_patch.rb +14 -0
  77. data/lib/additional_tags/patches/imports_controller_patch.rb +12 -0
  78. data/lib/additional_tags/patches/issue_patch.rb +95 -0
  79. data/lib/additional_tags/patches/issue_query_patch.rb +55 -0
  80. data/lib/additional_tags/patches/issues_controller_patch.rb +14 -0
  81. data/lib/additional_tags/patches/journal_patch.rb +18 -0
  82. data/lib/additional_tags/patches/my_controller_patch.rb +14 -0
  83. data/lib/additional_tags/patches/queries_helper_patch.rb +40 -0
  84. data/lib/additional_tags/patches/reports_controller_patch.rb +32 -0
  85. data/lib/additional_tags/patches/settings_controller_patch.rb +12 -0
  86. data/lib/additional_tags/patches/time_entry_patch.rb +19 -0
  87. data/lib/additional_tags/patches/time_entry_query_patch.rb +53 -0
  88. data/lib/additional_tags/patches/time_report_patch.rb +46 -0
  89. data/lib/additional_tags/patches/timelog_controller_patch.rb +14 -0
  90. data/lib/additional_tags/patches/wiki_controller_patch.rb +63 -0
  91. data/lib/additional_tags/patches/wiki_page_patch.rb +39 -0
  92. data/lib/additional_tags/tags.rb +134 -0
  93. data/lib/additional_tags/version.rb +3 -0
  94. metadata +177 -0
@@ -0,0 +1,11 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module DashboardsControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module GanttsControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ helper :additional_tags_issues
9
+
10
+ include AdditionalTagsHelper
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module ImportsControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ helper :additional_tags_issues
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,95 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module IssuePatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include InstanceMethods
8
+ acts_as_ordered_taggable
9
+
10
+ before_save :prepare_save_tag_change
11
+ before_save :sort_tag_list
12
+
13
+ alias_method :safe_attributes_without_tags=, :safe_attributes=
14
+ alias_method :safe_attributes=, :safe_attributes_with_tags=
15
+
16
+ alias_method :copy_from_without_tags, :copy_from
17
+ alias_method :copy_from, :copy_from_with_tags
18
+ end
19
+
20
+ class_methods do
21
+ def allowed_tags?(tags)
22
+ allowed_tags = available_tags.map(&:name)
23
+ tags.all? { |tag| allowed_tags.include?(tag) }
24
+ end
25
+
26
+ def by_tags(project, with_subprojects: false)
27
+ count_and_group_by(project: project, association: :tags, with_subprojects: with_subprojects)
28
+ end
29
+
30
+ def available_tags(options = {})
31
+ options[:permission] = :view_issues
32
+ tags = AdditionalTags::Tags.available_tags self, options
33
+ return tags unless options[:open_issues_only]
34
+
35
+ tags.joins("JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{table_name}.status_id")
36
+ .where(issue_statuses: { is_closed: false })
37
+ end
38
+
39
+ def remove_unused_tags!
40
+ AdditionalTagsRemoveUnusedTagJob.perform_later
41
+ end
42
+ end
43
+
44
+ module InstanceMethods
45
+ # tag_list_changed? is broken for after_save
46
+ # tag_list_changed? is not working here, too!
47
+ def prepare_save_tag_change
48
+ return unless defined?(tag_list) && defined?(tag_list_was) && !tag_list_was.nil?
49
+
50
+ @prepare_save_tag_change ||= tag_list != tag_list_was
51
+ end
52
+
53
+ def safe_attributes_with_tags=(attrs, user = User.current)
54
+ if attrs && attrs[:tag_list]
55
+ tags = attrs.delete :tag_list
56
+ tags = Array(tags).reject(&:empty?)
57
+
58
+ if user.allowed_to?(:create_issue_tags, project) ||
59
+ user.allowed_to?(:edit_issue_tags, project) && Issue.allowed_tags?(tags)
60
+ attrs[:tag_list] = tags
61
+ self.tag_list = tags
62
+ end
63
+ end
64
+
65
+ send 'safe_attributes_without_tags=', attrs, user
66
+ end
67
+
68
+ def copy_from_with_tags(arg, options = {})
69
+ copy_from_without_tags(arg, options)
70
+ issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
71
+ self.tag_list = issue.tag_list
72
+ self
73
+ 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
83
+
84
+ private
85
+
86
+ def sort_tag_list
87
+ tags = tag_list.reject(&:empty?)
88
+ return unless tags.present? && tag_list_changed?
89
+
90
+ self.tag_list = AdditionalTags::Tags.sort_tags tags
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,55 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module IssueQueryPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include AdditionalsQuery
8
+ include InstanceMethods
9
+
10
+ alias_method :initialize_available_filters_without_tags, :initialize_available_filters
11
+ alias_method :initialize_available_filters, :initialize_available_filters_with_tags
12
+
13
+ alias_method :available_columns_without_tags, :available_columns
14
+ alias_method :available_columns, :available_columns_with_tags
15
+
16
+ alias_method :build_from_params_without_tags, :build_from_params
17
+ alias_method :build_from_params, :build_from_params_with_tags
18
+ end
19
+
20
+ module InstanceMethods
21
+ def initialize_available_filters_with_tags
22
+ initialize_available_filters_without_tags
23
+
24
+ initialize_tags_filter if !available_filters.key?('tags') &&
25
+ AdditionalTags.setting?(:active_issue_tags) &&
26
+ User.current.allowed_to?(:view_issue_tags, project, global: true)
27
+ end
28
+
29
+ def available_columns_with_tags
30
+ if @available_columns.nil?
31
+ @available_columns = available_columns_without_tags
32
+
33
+ if AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, project, global: true)
34
+ @available_columns << QueryColumn.new(:tags)
35
+ end
36
+ else
37
+ available_columns_without_tags
38
+ end
39
+ @available_columns
40
+ 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
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,14 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module IssuesControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ helper :additional_tags_issues
9
+
10
+ include AdditionalTagsHelper
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module JournalPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ prepend InstanceOverwriteMethods
8
+ end
9
+
10
+ module InstanceOverwriteMethods
11
+ def visible_details(user = User.current)
12
+ details = super
13
+ details.reject { |x| x.prop_key == 'tag_list' && !user.allowed_to?(:view_issue_tags, project) }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module MyControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ helper :additional_tags_issues
9
+
10
+ include AdditionalTagsHelper
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module QueriesHelperPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include InstanceMethods
8
+
9
+ alias_method :column_content_without_tags, :column_content
10
+ 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
+ end
15
+
16
+ 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
+ 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
30
+ additional_tag_links column.value(item),
31
+ tag_controller: 'issues',
32
+ use_colors: AdditionalTags.setting?(:use_colors)
33
+ else
34
+ column_content_without_tags column, item
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module ReportsControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ prepend InstanceOverwriteMethods
8
+ end
9
+
10
+ module InstanceOverwriteMethods
11
+ def issue_report
12
+ @tags = AdditionalTags::Tags.sort_tag_list Issue.available_tags(project_id: @project.id)
13
+ @issues_by_tags = Issue.by_tags @project, with_subprojects: Setting.display_subprojects_issues?
14
+ super
15
+ end
16
+
17
+ def issue_report_details
18
+ if params[:detail] == 'tag' &&
19
+ AdditionalTags.setting?(:active_issue_tags) &&
20
+ User.current.allowed_to?(:view_issue_tags, @project)
21
+ @field = 'tag_id'
22
+ @rows = AdditionalTags::Tags.sort_tag_list Issue.available_tags(project_id: @project.id)
23
+ @data = Issue.by_tags @project, with_subprojects: Setting.display_subprojects_issues?
24
+ @report_title = l :field_tags
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module SettingsControllerPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper :additional_tags
8
+ helper :additional_tags_issues
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module AdditionalTags
2
+ module Patches
3
+ module TimeEntryPatch
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def issue_tags
12
+ return [] if issue.nil?
13
+
14
+ issue.tags
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ require_dependency 'time_entry_query'
2
+
3
+ module AdditionalTags
4
+ module Patches
5
+ module TimeEntryQueryPatch
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include InstanceMethods
10
+
11
+ alias_method :initialize_available_filters_without_tags, :initialize_available_filters
12
+ alias_method :initialize_available_filters, :initialize_available_filters_with_tags
13
+
14
+ alias_method :available_columns_without_tags, :available_columns
15
+ alias_method :available_columns, :available_columns_with_tags
16
+ end
17
+
18
+ module InstanceMethods
19
+ def initialize_available_filters_with_tags
20
+ 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,
26
+ name: l('label_attribute_of_issue', name: l(:field_tags)),
27
+ values: -> { available_issue_tags_values }
28
+ end
29
+
30
+ def available_columns_with_tags
31
+ if @available_columns.nil?
32
+ @available_columns = available_columns_without_tags
33
+ if AdditionalTags.setting?(:active_issue_tags) && User.current.allowed_to?(:view_issue_tags, project, global: true)
34
+ @available_columns << QueryColumn.new(:issue_tags)
35
+ end
36
+ else
37
+ available_columns_without_tags
38
+ end
39
+ @available_columns
40
+ end
41
+
42
+ def sql_for_issue_tags_field(_field, operator, value)
43
+ AdditionalTags.sql_for_tags_field Issue, operator, value
44
+ end
45
+
46
+ def available_issue_tags_values
47
+ Issue.available_tags(project: project)
48
+ .map { |c| [c.name, c.name] }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,46 @@
1
+ require_dependency 'query'
2
+
3
+ module AdditionalTags
4
+ module Patches
5
+ module TimeReportPatch
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include InstanceMethods
10
+
11
+ alias_method :load_available_criteria_without_tags, :load_available_criteria
12
+ alias_method :load_available_criteria, :load_available_criteria_with_tags
13
+ end
14
+
15
+ module InstanceMethods
16
+ def load_available_criteria_with_tags
17
+ return @load_available_criteria_with_tags if @load_available_criteria_with_tags
18
+
19
+ @load_available_criteria_with_tags = load_available_criteria_without_tags
20
+ @load_available_criteria_with_tags['tags'] = { sql: "#{ActsAsTaggableOn.tags_table}.id",
21
+ klass: ActsAsTaggableOn::Tag,
22
+ joins: additional_tags_join,
23
+ label: :field_tags }
24
+ @load_available_criteria_with_tags
25
+ end
26
+
27
+ private
28
+
29
+ def additional_tags_join
30
+ time_entry_table = Arel::Table.new TimeEntry.table_name
31
+ issues_table = Arel::Table.new Issue.table_name, as: :issues_time_entries
32
+ taggings_table = Arel::Table.new ActsAsTaggableOn.taggings_table
33
+ tags_table = Arel::Table.new ActsAsTaggableOn.tags_table
34
+
35
+ time_entry_table.join(issues_table)
36
+ .on(issues_table[:id].eq(time_entry_table[:issue_id]))
37
+ .join(taggings_table)
38
+ .on(taggings_table[:taggable_id].eq(issues_table[:id]).and(taggings_table[:taggable_type].eq('Issue')))
39
+ .join(tags_table)
40
+ .on(tags_table[:id].eq(taggings_table[:tag_id]))
41
+ .join_sources
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end