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.
- checksums.yaml +7 -0
- data/.github/workflows/brakeman.yml +38 -0
- data/.github/workflows/linters.yml +41 -0
- data/.github/workflows/tests.yml +139 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +75 -0
- data/.slim-lint.yml +25 -0
- data/.stylelintrc.json +163 -0
- data/LICENSE +339 -0
- data/README.md +126 -0
- data/Rakefile +11 -0
- data/additional_tags.gemspec +28 -0
- data/app/controllers/additional_tags_controller.rb +80 -0
- data/app/controllers/issue_tags_controller.rb +48 -0
- data/app/helpers/additional_tags_helper.rb +197 -0
- data/app/helpers/additional_tags_issues_helper.rb +43 -0
- data/app/helpers/additional_tags_wiki_helper.rb +17 -0
- data/app/jobs/additional_tags_job.rb +3 -0
- data/app/jobs/additional_tags_remove_unused_tag_job.rb +5 -0
- data/app/models/migrate_tag.rb +4 -0
- data/app/models/migrate_tagging.rb +5 -0
- data/app/views/additional_tags/_html_head.html.slim +6 -0
- data/app/views/additional_tags/_tag_list.html.slim +35 -0
- data/app/views/additional_tags/context_menu.html.slim +16 -0
- data/app/views/additional_tags/edit.html.slim +14 -0
- data/app/views/additional_tags/merge.html.slim +17 -0
- data/app/views/additional_tags/settings/_general.html.slim +45 -0
- data/app/views/additional_tags/settings/_manage_tags.html.slim +35 -0
- data/app/views/additional_tags/settings/_settings.html.slim +9 -0
- data/app/views/auto_completes/_additional_tag_list.slim +1 -0
- data/app/views/context_menus/_issues_tags.html.slim +10 -0
- data/app/views/issue_tags/_edit_modal.html.slim +30 -0
- data/app/views/issue_tags/edit.js.erb +2 -0
- data/app/views/issues/_tags.html.slim +8 -0
- data/app/views/issues/_tags_bulk_edit.html.slim +7 -0
- data/app/views/issues/_tags_form.html.slim +11 -0
- data/app/views/issues/_tags_form_details.html.slim +7 -0
- data/app/views/issues/_tags_sidebar.html.slim +5 -0
- data/app/views/reports/_tags_simple.html.slim +11 -0
- data/app/views/wiki/_tags_form.html.slim +7 -0
- data/app/views/wiki/_tags_form_bottom.html.slim +5 -0
- data/app/views/wiki/_tags_show.html.slim +9 -0
- data/app/views/wiki/_tags_sidebar.html.slim +4 -0
- data/app/views/wiki/tag_index.html.slim +21 -0
- data/assets/javascripts/tags.js +19 -0
- data/assets/stylesheets/tags.css +119 -0
- data/config/locales/bg.yml +33 -0
- data/config/locales/cs.yml +33 -0
- data/config/locales/de.yml +33 -0
- data/config/locales/en.yml +33 -0
- data/config/locales/es.yml +33 -0
- data/config/locales/fr.yml +33 -0
- data/config/locales/it.yml +33 -0
- data/config/locales/ja.yml +33 -0
- data/config/locales/ko.yml +33 -0
- data/config/locales/pl.yml +33 -0
- data/config/locales/pt-BR.yml +33 -0
- data/config/locales/ru.yml +33 -0
- data/config/routes.rb +31 -0
- data/config/settings.yml +9 -0
- data/db/migrate/20201116145429_acts_as_taggable_migration.rb +40 -0
- data/db/migrate/20201123093214_migrate_existing_tags.rb +39 -0
- data/doc/images/additional-tags-framework.png +0 -0
- data/doc/images/additional-tags.gif +0 -0
- data/init.rb +31 -0
- data/lib/additional_tags.rb +107 -0
- data/lib/additional_tags/hooks.rb +63 -0
- data/lib/additional_tags/patches/agile_boards_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/agile_query_patch.rb +41 -0
- data/lib/additional_tags/patches/agile_versions_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/agile_versions_query_patch.rb +11 -0
- data/lib/additional_tags/patches/auto_completes_controller_patch.rb +42 -0
- data/lib/additional_tags/patches/calendars_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/dashboard_async_blocks_controller_patch.rb +11 -0
- data/lib/additional_tags/patches/dashboards_controller_patch.rb +11 -0
- data/lib/additional_tags/patches/gantts_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/imports_controller_patch.rb +12 -0
- data/lib/additional_tags/patches/issue_patch.rb +95 -0
- data/lib/additional_tags/patches/issue_query_patch.rb +55 -0
- data/lib/additional_tags/patches/issues_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/journal_patch.rb +18 -0
- data/lib/additional_tags/patches/my_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/queries_helper_patch.rb +40 -0
- data/lib/additional_tags/patches/reports_controller_patch.rb +32 -0
- data/lib/additional_tags/patches/settings_controller_patch.rb +12 -0
- data/lib/additional_tags/patches/time_entry_patch.rb +19 -0
- data/lib/additional_tags/patches/time_entry_query_patch.rb +53 -0
- data/lib/additional_tags/patches/time_report_patch.rb +46 -0
- data/lib/additional_tags/patches/timelog_controller_patch.rb +14 -0
- data/lib/additional_tags/patches/wiki_controller_patch.rb +63 -0
- data/lib/additional_tags/patches/wiki_page_patch.rb +39 -0
- data/lib/additional_tags/tags.rb +134 -0
- data/lib/additional_tags/version.rb +3 -0
- metadata +177 -0
|
@@ -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,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,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,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
|