Feature #1448 » 0001-Adds-tags-to-issues.patch
Gemfile | ||
---|---|---|
15 | 15 |
gem "i18n", "~> 1.6.0" |
16 | 16 |
gem "rbpdf", "~> 1.20.0" |
17 | 17 | |
18 |
# Tags |
|
19 |
gem 'acts-as-taggable-on', '~> 6.0' |
|
20 | ||
18 | 21 |
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem |
19 | 22 |
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin] |
20 | 23 |
app/models/issue.rb | ||
---|---|---|
53 | 53 | |
54 | 54 |
acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status), |
55 | 55 |
:author_key => :author_id |
56 |
acts_as_ordered_taggable |
|
56 | 57 | |
57 | 58 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
58 | 59 | |
... | ... | |
248 | 249 |
@total_estimated_hours = nil |
249 | 250 |
@last_updated_by = nil |
250 | 251 |
@last_notes = nil |
252 |
@tags = nil |
|
251 | 253 |
base_reload(*args) |
252 | 254 |
end |
253 | 255 | |
... | ... | |
468 | 470 |
'estimated_hours', |
469 | 471 |
'custom_field_values', |
470 | 472 |
'custom_fields', |
473 |
'tag_list', |
|
471 | 474 |
'lock_version', |
472 | 475 |
'notes', |
473 | 476 |
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user) } |
... | ... | |
1101 | 1104 |
end |
1102 | 1105 |
end |
1103 | 1106 | |
1107 |
def tags |
|
1108 |
if @tags |
|
1109 |
@tags |
|
1110 |
else |
|
1111 |
tag_list |
|
1112 |
end |
|
1113 |
end |
|
1114 | ||
1104 | 1115 |
# Preloads relations for a collection of issues |
1105 | 1116 |
def self.load_relations(issues) |
1106 | 1117 |
if issues.any? |
... | ... | |
1155 | 1166 |
end |
1156 | 1167 |
end |
1157 | 1168 | |
1169 |
# Preloads tags for a collection of issues |
|
1170 |
def self.load_issues_tags(issues) |
|
1171 |
if issues.any? |
|
1172 |
tags = ActsAsTaggableOn::Tag.joins(:taggings) |
|
1173 |
.select("#{ActsAsTaggableOn::Tag.table_name}.id", "#{ActsAsTaggableOn::Tag.table_name}.name", |
|
1174 |
"#{ActsAsTaggableOn::Tagging.table_name}.taggable_id") |
|
1175 |
.where(:taggings => {:taggable_type => 'Issue', :taggable_id => issues.map(&:id), :context => 'tags'}) |
|
1176 |
.order("#{ActsAsTaggableOn::Tagging.table_name}.id") |
|
1177 | ||
1178 |
issues.each do |issue| |
|
1179 |
issue.instance_variable_set "@tags", (tags.select{|t| t.taggable_id == issue.id} || []) |
|
1180 |
end |
|
1181 |
end |
|
1182 |
end |
|
1183 | ||
1158 | 1184 |
# Returns a scope of the given issues and their descendants |
1159 | 1185 |
def self.self_and_descendants(issues) |
1160 | 1186 |
Issue.joins("JOIN #{Issue.table_name} ancestors" + |
app/models/issue_query.rb | ||
---|---|---|
49 | 49 |
TimestampQueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc', :groupable => true), |
50 | 50 |
TimestampQueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc', :groupable => true), |
51 | 51 |
QueryColumn.new(:last_updated_by, :sortable => lambda {User.fields_for_order_statement("last_journal_user")}), |
52 |
QueryColumn.new(:tags, :caption => :field_tags), |
|
52 | 53 |
QueryColumn.new(:relations, :caption => :label_related_issues), |
53 | 54 |
QueryColumn.new(:attachments, :caption => :label_attachment_plural), |
54 | 55 |
QueryColumn.new(:description, :inline => false), |
... | ... | |
163 | 164 |
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] |
164 | 165 |
end |
165 | 166 | |
167 |
add_available_filter('tags', :type => :list_optional, :name => l(:field_tags), |
|
168 |
:values => lambda { tags_values }) |
|
169 | ||
166 | 170 |
add_available_filter "attachment", |
167 | 171 |
:type => :text, :name => l(:label_attachment) |
168 | 172 | |
... | ... | |
337 | 341 |
if has_column?(:last_notes) |
338 | 342 |
Issue.load_visible_last_notes(issues) |
339 | 343 |
end |
344 |
if has_column?(:tags) |
|
345 |
Issue.load_issues_tags(issues) |
|
346 |
end |
|
340 | 347 |
issues |
341 | 348 |
rescue ::ActiveRecord::StatementInvalid => e |
342 | 349 |
raise StatementInvalid.new(e.message) |
... | ... | |
641 | 648 |
sql_for_field(field, operator, value, Project.table_name, "status") |
642 | 649 |
end |
643 | 650 | |
651 |
def sql_for_tags_field(field, operator, value) |
|
652 |
case operator |
|
653 |
when '=', '!' |
|
654 |
issues = Issue.tagged_with(values_for('tags'), any: true) |
|
655 |
when '!*' |
|
656 |
issues = Issue.tagged_with ActsAsTaggableOn::Tag.all.map(&:to_s), exclude: true |
|
657 |
else |
|
658 |
issues = Issue.tagged_with ActsAsTaggableOn::Tag.all.map(&:to_s), any: true |
|
659 |
end |
|
660 | ||
661 |
compare = operator.eql?('!') ? 'NOT IN' : 'IN' |
|
662 |
issue_ids = issues.collect {|issue| issue.id }.push(0).join(',') |
|
663 | ||
664 |
"( #{ Issue.table_name }.id #{ compare } (#{ issue_ids }) )" |
|
665 |
end |
|
666 | ||
644 | 667 |
def find_assigned_to_id_filter_values(values) |
645 | 668 |
Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]} |
646 | 669 |
end |
app/models/journal.rb | ||
---|---|---|
192 | 192 |
h[c.custom_field_id] = c.value |
193 | 193 |
h |
194 | 194 |
end |
195 | ||
196 |
@tags_before_change = journalized.tags |
|
195 | 197 |
end |
196 | 198 |
self |
197 | 199 |
end |
... | ... | |
261 | 263 |
end |
262 | 264 |
end |
263 | 265 |
end |
266 | ||
267 |
if @tags_before_change |
|
268 |
new_tags = journalized.send('tags') |
|
269 |
if new_tags != @tags_before_change |
|
270 |
add_attribute_detail('tags', @tags_before_change.to_s, new_tags.to_s) |
|
271 |
end |
|
272 |
end |
|
264 | 273 |
start |
265 | 274 |
end |
266 | 275 |
app/models/query.rb | ||
---|---|---|
614 | 614 |
] |
615 | 615 |
end |
616 | 616 | |
617 |
def tags_values |
|
618 |
issues_scope = Issue.visible.select("#{Issue.table_name}.id").joins(:project) |
|
619 |
issues_scope.where("#{project.project_condition(Setting.display_subprojects_issues?)}") if project |
|
620 | ||
621 |
result_scope = ActsAsTaggableOn::Tag |
|
622 |
.joins(:taggings) |
|
623 |
.select('tags.name') |
|
624 |
.group('tags.id, tags.name') |
|
625 |
.where(taggings: { taggable_type: 'Issue', taggable_id: issues_scope}) |
|
626 |
.collect {|t| [t.name, t.name]} |
|
627 | ||
628 |
result_scope |
|
629 |
end |
|
630 | ||
617 | 631 |
# Adds available filters |
618 | 632 |
def initialize_available_filters |
619 | 633 |
# implemented by sub-classes |
app/views/issues/_attributes.html.erb | ||
---|---|---|
78 | 78 |
<%= render :partial => 'issues/form_custom_fields' %> |
79 | 79 |
<% end %> |
80 | 80 | |
81 |
<p><%= f.text_field :tag_list, :size => 60, :value => @issue.tags.to_s %> |
|
82 | ||
81 | 83 |
<% end %> |
82 | 84 | |
83 | 85 |
<% include_calendar_headers_tags %> |
app/views/issues/show.html.erb | ||
---|---|---|
72 | 72 |
end |
73 | 73 |
end %> |
74 | 74 |
<%= render_half_width_custom_fields_rows(@issue) %> |
75 | ||
76 |
<% unless @issue.tags.empty? %> |
|
77 |
<div class="tags attribute"> |
|
78 |
<div class="label"> |
|
79 |
<span><%= l(:field_tags) %>:</span> |
|
80 |
</div> |
|
81 |
<div class="value"> |
|
82 |
<%= @issue.tags.to_s %> |
|
83 |
</div> |
|
84 |
</div> |
|
85 |
<% end %> |
|
86 | ||
75 | 87 |
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> |
76 | 88 |
</div> |
77 | 89 |
config/locales/en.yml | ||
---|---|---|
389 | 389 |
field_recently_used_projects: Number of recently used projects in jump box |
390 | 390 |
field_history_default_tab: Issue's history default tab |
391 | 391 |
field_unique_id: Unique ID |
392 |
field_tags: Tags |
|
392 | 393 | |
393 | 394 |
setting_app_title: Application title |
394 | 395 |
setting_welcome_text: Welcome text |
db/migrate/20180802191932_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 1) |
|
2 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
3 |
class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end |
|
4 |
else |
|
5 |
class ActsAsTaggableOnMigration < ActiveRecord::Migration; end |
|
6 |
end |
|
7 |
ActsAsTaggableOnMigration.class_eval do |
|
8 |
def self.up |
|
9 |
create_table :tags do |t| |
|
10 |
t.string :name |
|
11 |
end |
|
12 | ||
13 |
create_table :taggings do |t| |
|
14 |
t.references :tag |
|
15 | ||
16 |
# You should make sure that the column created is |
|
17 |
# long enough to store the required class names. |
|
18 |
t.references :taggable, polymorphic: true |
|
19 |
t.references :tagger, polymorphic: true |
|
20 | ||
21 |
# Limit is created to prevent MySQL error on index |
|
22 |
# length for MyISAM table type: http://bit.ly/vgW2Ql |
|
23 |
t.string :context, limit: 128 |
|
24 | ||
25 |
t.datetime :created_at |
|
26 |
end |
|
27 | ||
28 |
add_index :taggings, :tag_id |
|
29 |
add_index :taggings, [:taggable_id, :taggable_type, :context] |
|
30 |
end |
|
31 | ||
32 |
def self.down |
|
33 |
drop_table :taggings |
|
34 |
drop_table :tags |
|
35 |
end |
|
36 |
end |
db/migrate/20180802191933_add_missing_unique_indices.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 2) |
|
2 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
3 |
class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end |
|
4 |
else |
|
5 |
class AddMissingUniqueIndices < ActiveRecord::Migration; end |
|
6 |
end |
|
7 |
AddMissingUniqueIndices.class_eval do |
|
8 |
def self.up |
|
9 |
add_index :tags, :name, unique: true |
|
10 | ||
11 |
remove_index :taggings, :tag_id if index_exists?(:taggings, :tag_id) |
|
12 |
remove_index :taggings, [:taggable_id, :taggable_type, :context] |
|
13 |
add_index :taggings, |
|
14 |
[:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], |
|
15 |
unique: true, name: 'taggings_idx' |
|
16 |
end |
|
17 | ||
18 |
def self.down |
|
19 |
remove_index :tags, :name |
|
20 | ||
21 |
remove_index :taggings, name: 'taggings_idx' |
|
22 | ||
23 |
add_index :taggings, :tag_id unless index_exists?(:taggings, :tag_id) |
|
24 |
add_index :taggings, [:taggable_id, :taggable_type, :context] |
|
25 |
end |
|
26 |
end |
db/migrate/20180802191934_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 3) |
|
2 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
3 |
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end |
|
4 |
else |
|
5 |
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end |
|
6 |
end |
|
7 |
AddTaggingsCounterCacheToTags.class_eval do |
|
8 |
def self.up |
|
9 |
add_column :tags, :taggings_count, :integer, default: 0 |
|
10 | ||
11 |
ActsAsTaggableOn::Tag.reset_column_information |
|
12 |
ActsAsTaggableOn::Tag.find_each do |tag| |
|
13 |
ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) |
|
14 |
end |
|
15 |
end |
|
16 | ||
17 |
def self.down |
|
18 |
remove_column :tags, :taggings_count |
|
19 |
end |
|
20 |
end |
db/migrate/20180802191935_add_missing_taggable_index.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 4) |
|
2 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
3 |
class AddMissingTaggableIndex < ActiveRecord::Migration[4.2]; end |
|
4 |
else |
|
5 |
class AddMissingTaggableIndex < ActiveRecord::Migration; end |
|
6 |
end |
|
7 |
AddMissingTaggableIndex.class_eval do |
|
8 |
def self.up |
|
9 |
add_index :taggings, [:taggable_id, :taggable_type, :context] |
|
10 |
end |
|
11 | ||
12 |
def self.down |
|
13 |
remove_index :taggings, [:taggable_id, :taggable_type, :context] |
|
14 |
end |
|
15 |
end |
db/migrate/20180802191936_change_collation_for_tag_names.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 5) |
|
2 |
# This migration is added to circumvent issue #623 and have special characters |
|
3 |
# work properly |
|
4 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
5 |
class ChangeCollationForTagNames < ActiveRecord::Migration[4.2]; end |
|
6 |
else |
|
7 |
class ChangeCollationForTagNames < ActiveRecord::Migration; end |
|
8 |
end |
|
9 |
ChangeCollationForTagNames.class_eval do |
|
10 |
def up |
|
11 |
if ActsAsTaggableOn::Utils.using_mysql? |
|
12 |
execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") |
|
13 |
end |
|
14 |
end |
|
15 |
end |
db/migrate/20180802191937_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb | ||
---|---|---|
1 |
# This migration comes from acts_as_taggable_on_engine (originally 6) |
|
2 |
if ActiveRecord.gem_version >= Gem::Version.new('5.0') |
|
3 |
class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end |
|
4 |
else |
|
5 |
class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end |
|
6 |
end |
|
7 |
AddMissingIndexesOnTaggings.class_eval do |
|
8 |
def change |
|
9 |
add_index :taggings, :tag_id unless index_exists? :taggings, :tag_id |
|
10 |
add_index :taggings, :taggable_id unless index_exists? :taggings, :taggable_id |
|
11 |
add_index :taggings, :taggable_type unless index_exists? :taggings, :taggable_type |
|
12 |
add_index :taggings, :tagger_id unless index_exists? :taggings, :tagger_id |
|
13 |
add_index :taggings, :context unless index_exists? :taggings, :context |
|
14 | ||
15 |
unless index_exists? :taggings, [:tagger_id, :tagger_type] |
|
16 |
add_index :taggings, [:tagger_id, :tagger_type] |
|
17 |
end |
|
18 | ||
19 |
unless index_exists? :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' |
|
20 |
add_index :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' |
|
21 |
end |
|
22 |
end |
|
23 |
end |
lib/redmine/export/pdf/issues_pdf_helper.rb | ||
---|---|---|
116 | 116 |
pdf.set_x(base_x) |
117 | 117 |
end |
118 | 118 | |
119 |
tags = issue.tags |
|
120 |
if tags.any? |
|
121 |
pdf.SetFontStyle('B',9) |
|
122 |
pdf.RDMCell(35+155, 5, l(:field_tags), "LRT", 1) |
|
123 |
pdf.SetFontStyle('',9) |
|
124 | ||
125 |
pdf.SetFontStyle('',9) |
|
126 |
pdf.RDMwriteFormattedCell(35+155, 5, '', '', tags.to_s, '', "LRB") |
|
127 |
end |
|
119 | 128 |
pdf.SetFontStyle('B',9) |
120 | 129 |
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) |
121 | 130 |
pdf.SetFontStyle('',9) |
... | ... | |
379 | 388 |
value = " " * level + value |
380 | 389 |
when :attachments |
381 | 390 |
value = value.to_a.map {|a| a.filename}.join("\n") |
391 |
when :tags |
|
392 |
value = value.to_s |
|
382 | 393 |
end |
383 | 394 |
if value.is_a?(Date) |
384 | 395 |
format_date(value) |
test/fixtures/taggings.yml | ||
---|---|---|
1 |
--- |
|
2 |
tagging_1: |
|
3 |
tag_id: 1 |
|
4 |
taggable_id: 1 |
|
5 |
taggable_type: Issue |
|
6 |
context: tags |
|
7 |
created_at: <%= 3.days.ago.to_s(:db) %> |
|
8 |
tagging_2: |
|
9 |
tag_id: 2 |
|
10 |
taggable_id: 1 |
|
11 |
taggable_type: Issue |
|
12 |
context: tags |
|
13 |
created_at: <%= 2.days.ago.to_s(:db) %> |
|
14 |
tagging_3: |
|
15 |
tag_id: 1 |
|
16 |
taggable_id: 2 |
|
17 |
taggable_type: Issue |
|
18 |
context: tags |
|
19 |
created_at: <%= 1.days.ago.to_s(:db) %> |
test/fixtures/tags.yml | ||
---|---|---|
1 |
--- |
|
2 |
tag_001: |
|
3 |
id: 1 |
|
4 |
name: UX |
|
5 |
tag_002: |
|
6 |
id: 2 |
|
7 |
name: Backend |
|
8 |
tag_003: |
|
9 |
id: 3 |
|
10 |
name: API |
test/functional/issues_controller_test.rb | ||
---|---|---|
46 | 46 |
:queries, |
47 | 47 |
:repositories, |
48 | 48 |
:changesets, |
49 |
:watchers |
|
49 |
:watchers, |
|
50 |
:tags, |
|
51 |
:taggings |
|
50 | 52 | |
51 | 53 |
include Redmine::I18n |
52 | 54 | |
... | ... | |
1704 | 1706 |
end |
1705 | 1707 |
end |
1706 | 1708 | |
1709 |
def test_index_with_tags_column |
|
1710 |
get :index, :params => { |
|
1711 |
:set_filter => 1, |
|
1712 |
:project_id => 1, |
|
1713 |
:c => %w(subject tags) |
|
1714 |
} |
|
1715 | ||
1716 |
assert_response :success |
|
1717 |
assert_select 'td.tags', :text => 'UX' |
|
1718 |
assert_select 'td.tags', :text => 'UX, Backend' |
|
1719 | ||
1720 |
get :index, :params => { |
|
1721 |
:set_filter => 1, |
|
1722 |
:project_id => 1, |
|
1723 |
:c => %w(subject tags), |
|
1724 |
:format => 'pdf' |
|
1725 |
} |
|
1726 |
assert_response :success |
|
1727 |
assert_equal 'application/pdf', response.content_type |
|
1728 |
end |
|
1729 | ||
1707 | 1730 |
def test_show_by_anonymous |
1708 | 1731 |
get :show, :params => { |
1709 | 1732 |
:id => 1 |
... | ... | |
2579 | 2602 |
end |
2580 | 2603 |
end |
2581 | 2604 | |
2605 |
def test_show_should_render_issue_tags_for_issue_with_tags |
|
2606 |
@request.session[:user_id] = 1 |
|
2607 | ||
2608 |
get :show, :params => { |
|
2609 |
:id => 1 |
|
2610 |
} |
|
2611 | ||
2612 |
assert_select 'div.tags .value', :text => 'UX, Backend', :count => 1 |
|
2613 |
end |
|
2614 | ||
2615 |
def test_show_should_not_render_issue_tags_for_issue_without_tags |
|
2616 |
@request.session[:user_id] = 1 |
|
2617 | ||
2618 |
get :show, :params => { |
|
2619 |
:id => 14 |
|
2620 |
} |
|
2621 | ||
2622 |
assert_response :success |
|
2623 |
assert_select 'div.tags', 0 |
|
2624 |
end |
|
2625 | ||
2582 | 2626 |
def test_get_new |
2583 | 2627 |
@request.session[:user_id] = 2 |
2584 | 2628 |
get :new, :params => { |
... | ... | |
2605 | 2649 |
assert_select 'select[name=?]', 'issue[done_ratio]' |
2606 | 2650 |
assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' |
2607 | 2651 |
assert_select 'input[name=?]', 'issue[watcher_user_ids][]' |
2652 |
assert_select 'input[name=?]', 'issue[tag_list]' |
|
2608 | 2653 |
end |
2609 | 2654 | |
2610 | 2655 |
# Be sure we don't display inactive IssuePriorities |
... | ... | |
3612 | 3657 |
assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail) |
3613 | 3658 |
end |
3614 | 3659 | |
3660 |
def test_post_create_with_tags |
|
3661 |
@request.session[:user_id] = 2 |
|
3662 | ||
3663 |
post :create, :params => { |
|
3664 |
:project_id => 1, |
|
3665 |
:issue => { |
|
3666 |
:tracker_id => 1, |
|
3667 |
:subject => 'This is a new issue with tags', |
|
3668 |
:description => 'This is the description', |
|
3669 |
:priority_id => 5, |
|
3670 |
:tag_list => 'One, Two' |
|
3671 |
} |
|
3672 |
} |
|
3673 | ||
3674 |
issue = Issue.order('id DESC').first |
|
3675 |
assert_equal ['One', 'Two'], issue.tag_list |
|
3676 |
end |
|
3677 | ||
3615 | 3678 |
def test_post_create_subissue |
3616 | 3679 |
@request.session[:user_id] = 2 |
3617 | 3680 | |
... | ... | |
4977 | 5040 |
:project_id => '1', |
4978 | 5041 |
:tracker_id => '2', |
4979 | 5042 |
:priority_id => '6' |
4980 | ||
4981 | 5043 |
} |
4982 | 5044 |
} |
4983 | 5045 |
end |
... | ... | |
5609 | 5671 |
assert_equal 2, issue.reload.assigned_to_id |
5610 | 5672 |
end |
5611 | 5673 | |
5674 |
def test_put_update_issue_tags |
|
5675 |
@request.session[:user_id] = 1 |
|
5676 | ||
5677 |
put :update, :params => { |
|
5678 |
:id => 1, |
|
5679 |
:issue => { |
|
5680 |
:tag_list => 'Three' |
|
5681 |
} |
|
5682 |
} |
|
5683 |
assert_response 302 |
|
5684 | ||
5685 |
assert_equal ['Three'], Issue.find(1).tag_list |
|
5686 |
end |
|
5687 | ||
5612 | 5688 |
def test_get_bulk_edit |
5613 | 5689 |
@request.session[:user_id] = 2 |
5614 | 5690 |
get :bulk_edit, :params => { |
test/functional/queries_controller_test.rb | ||
---|---|---|
25 | 25 |
:members, :member_roles, :roles, |
26 | 26 |
:trackers, :issue_statuses, :issue_categories, :enumerations, :versions, |
27 | 27 |
:issues, :custom_fields, :custom_values, |
28 |
:queries |
|
28 |
:queries, :tags, :taggings
|
|
29 | 29 | |
30 | 30 |
def setup |
31 | 31 |
User.current = nil |
... | ... | |
738 | 738 |
assert_include ["Dave Lopper", "3", "active"], json |
739 | 739 |
assert_include ["Dave2 Lopper2", "5", "locked"], json |
740 | 740 |
end |
741 | ||
742 |
def test_filter_with_tags_should_return_filter_values |
|
743 |
@request.session[:user_id] = 2 |
|
744 |
get :filter, :params => { |
|
745 |
:project_id => 1, |
|
746 |
:type => 'IssueQuery', |
|
747 |
:name => 'tags' |
|
748 |
} |
|
749 | ||
750 |
assert_response :success |
|
751 |
assert_equal 'application/json', response.content_type |
|
752 |
json = ActiveSupport::JSON.decode(response.body) |
|
753 |
assert_equal [["UX","UX"],["Backend","Backend"]], json |
|
754 |
end |
|
741 | 755 |
end |
test/unit/issue_tags_test.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
# Redmine - project management software |
|
4 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or |
|
7 |
# modify it under the terms of the GNU General Public License |
|
8 |
# as published by the Free Software Foundation; either version 2 |
|
9 |
# of the License, or (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, |
|
12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
# GNU General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
19 | ||
20 |
require File.expand_path('../../test_helper', __FILE__) |
|
21 | ||
22 |
class IssueTagsTest < ActiveSupport::TestCase |
|
23 |
fixtures :projects, :users, :email_addresses, :user_preferences, :members, :member_roles, :roles, |
|
24 |
:groups_users, |
|
25 |
:trackers, :projects_trackers, |
|
26 |
:enabled_modules, |
|
27 |
:issue_statuses, |
|
28 |
:issues, :journals, :journal_details, |
|
29 |
:tags, :taggings |
|
30 | ||
31 |
include Redmine::I18n |
|
32 | ||
33 |
def setup |
|
34 |
set_language_if_valid 'en' |
|
35 |
end |
|
36 | ||
37 |
def teardown |
|
38 |
User.current = nil |
|
39 |
end |
|
40 | ||
41 |
def test_issue_tag_list_should_return_an_array_of_tags |
|
42 |
assert_equal ['UX', 'Backend'], Issue.find(1).tags |
|
43 |
end |
|
44 | ||
45 |
def test_add_issue_tags |
|
46 |
issue = Issue.find(2) |
|
47 |
issue.tag_list = "One, Two" |
|
48 | ||
49 |
assert issue.save |
|
50 | ||
51 |
issue.reload |
|
52 |
assert_equal ['One', 'Two'], issue.tag_list |
|
53 |
end |
|
54 | ||
55 |
def test_clear_issue_tags |
|
56 |
issue = Issue.find(1) |
|
57 |
issue.tag_list = '' |
|
58 | ||
59 |
assert issue.save! |
|
60 |
assert_equal [], issue.tag_list |
|
61 |
end |
|
62 | ||
63 |
def test_update_issue_tags_should_journalize_changes |
|
64 |
issue = Issue.find(1) |
|
65 |
issue.init_journal User.find(1) |
|
66 |
issue.tag_list = "UX, API" |
|
67 | ||
68 |
assert_difference 'Journal.count', 1 do |
|
69 |
assert_difference 'JournalDetail.count', 1 do |
|
70 |
issue.save! |
|
71 |
end |
|
72 |
end |
|
73 |
issue.reload |
|
74 | ||
75 |
assert_equal ['UX', 'API'], issue.tag_list |
|
76 | ||
77 |
detail = JournalDetail.order('id DESC').first |
|
78 | ||
79 |
assert_equal issue, detail.journal.journalized |
|
80 |
assert_equal 'attr', detail.property |
|
81 |
assert_equal 'tags', detail.prop_key |
|
82 |
assert_equal 'UX, API', detail.value |
|
83 |
assert_equal 'UX, Backend', detail.old_value |
|
84 |
end |
|
85 | ||
86 |
def test_update_issue_tags_should_not_journalize_changes_if_tags_are_not_changed |
|
87 |
issue = Issue.find(1) |
|
88 |
issue.init_journal User.find(1) |
|
89 |
issue.tag_list = "UX, Backend" |
|
90 | ||
91 |
assert_difference 'Journal.count', 0 do |
|
92 |
assert_difference 'JournalDetail.count', 0 do |
|
93 |
issue.save! |
|
94 |
end |
|
95 |
end |
|
96 |
end |
|
97 |
end |
test/unit/lib/redmine/export/pdf/issues_pdf_test.rb | ||
---|---|---|
21 | 21 | |
22 | 22 |
class IssuesPdfHelperTest < ActiveSupport::TestCase |
23 | 23 |
fixtures :users, :projects, :roles, :members, :member_roles, |
24 |
:enabled_modules, :issues, :trackers, :enumerations |
|
24 |
:enabled_modules, :issues, :trackers, :enumerations, |
|
25 |
:tags, :taggings |
|
25 | 26 | |
26 | 27 |
include Redmine::Export::PDF::IssuesPdfHelper |
27 | 28 | |
... | ... | |
36 | 37 |
results = fetch_row_values(issue, query, 0) |
37 | 38 |
assert_equal ["2", "Add ingredients categories", "4.34"], results |
38 | 39 |
end |
40 | ||
41 |
def test_fetch_row_values_should_return_issue_tags_as_string |
|
42 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
43 |
query.column_names = [:subject, :tags] |
|
44 | ||
45 |
results = fetch_row_values(Issue.find(1), query, 0) |
|
46 | ||
47 |
# tags is the third column |
|
48 |
tags = results[2] |
|
49 |
assert_match 'Backend', tags |
|
50 |
assert_match 'UX', tags |
|
51 |
end |
|
39 | 52 |
end |
test/unit/query_test.rb | ||
---|---|---|
30 | 30 |
:projects_trackers, |
31 | 31 |
:custom_fields_trackers, |
32 | 32 |
:workflows, :journals, |
33 |
:attachments, :time_entries |
|
33 |
:attachments, :time_entries, |
|
34 |
:tags, :taggings |
|
34 | 35 | |
35 | 36 |
INTEGER_KLASS = RUBY_VERSION >= "2.4" ? Integer : Fixnum |
36 | 37 | |
... | ... | |
887 | 888 |
end |
888 | 889 |
end |
889 | 890 | |
891 |
def test_filter_by_tags_with_operator_is |
|
892 |
query = IssueQuery.new(:name => '_') |
|
893 |
filter_name = "tags" |
|
894 |
assert_include filter_name, query.available_filters.keys |
|
895 | ||
896 |
query.filters = {filter_name => {:operator => '=', :values => ['UX']}} |
|
897 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
898 | ||
899 |
# Should return issue tagged with any of the values |
|
900 |
query.filters = {filter_name => {:operator => '=', :values => ['UX, Backend']}} |
|
901 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
902 |
end |
|
903 | ||
904 |
def test_filter_by_tags_with_operator_is_not |
|
905 |
query = IssueQuery.new(:name => '_') |
|
906 |
filter_name = "tags" |
|
907 |
assert_include filter_name, query.available_filters.keys |
|
908 | ||
909 |
query.filters = {filter_name => {:operator => '!', :values => ['Backend']}} |
|
910 |
issues = find_issues_with_query(query).map(&:id).sort |
|
911 | ||
912 |
# Issue tagged with Backend should not be returned |
|
913 |
assert_not_include 1, issues |
|
914 |
assert_include 2, issues |
|
915 |
# Untagged issues should be returned |
|
916 |
assert_include 5, issues |
|
917 |
end |
|
918 | ||
919 |
def test_filter_by_tags_with_operator_none |
|
920 |
query = IssueQuery.new(:name => '_') |
|
921 |
filter_name = "tags" |
|
922 |
assert_include filter_name, query.available_filters.keys |
|
923 | ||
924 |
query.filters = {filter_name => {:operator => '!*', :values => ['']}} |
|
925 |
issues = find_issues_with_query(query).map(&:id).sort |
|
926 | ||
927 |
# Tagged issues should not be returned |
|
928 |
assert_not_include 1, issues |
|
929 |
assert_not_include 2, issues |
|
930 | ||
931 |
# Untagged issues should be returned |
|
932 |
assert_include 5, issues |
|
933 |
end |
|
934 | ||
935 |
def test_filter_by_tags_with_operator_any |
|
936 |
query = IssueQuery.new(:name => '_') |
|
937 |
filter_name = "tags" |
|
938 |
assert_include filter_name, query.available_filters.keys |
|
939 | ||
940 |
query.filters = {filter_name => {:operator => '*', :values => ['']}} |
|
941 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
942 |
end |
|
943 | ||
890 | 944 |
def test_user_custom_field_filtered_on_me |
891 | 945 |
User.current = User.find(2) |
892 | 946 |
cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |
... | ... | |
1466 | 1520 |
assert_not_nil issues.first.instance_variable_get("@last_notes") |
1467 | 1521 |
end |
1468 | 1522 | |
1523 |
def test_query_should_preload_tags |
|
1524 |
q = IssueQuery.new(:name => '_', :column_names => [:subject, :tags]) |
|
1525 |
assert q.has_column?(:tags) |
|
1526 |
issues = q.issues |
|
1527 |
assert_not_nil issues.first.instance_variable_get("@tags") |
|
1528 |
end |
|
1529 | ||
1469 | 1530 |
def test_groupable_columns_should_include_custom_fields |
1470 | 1531 |
q = IssueQuery.new |
1471 | 1532 |
column = q.groupable_columns.detect {|c| c.name == :cf_1} |
- « Previous
- 1
- …
- 10
- 11
- 12
- Next »