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 »