Feature #1448 » 0001-Add-tags-to-issues.patch
| Gemfile | ||
|---|---|---|
| 18 | 18 |
gem "nokogiri", "~> 1.8.0" |
| 19 | 19 |
gem "i18n", "~> 0.7.0" |
| 20 | 20 | |
| 21 |
# Tags |
|
| 22 |
gem 'acts-as-taggable-on', '~> 6.0' |
|
| 23 | ||
| 21 | 24 |
# Request at least rails-html-sanitizer 1.0.3 because of security advisories |
| 22 | 25 |
gem "rails-html-sanitizer", ">= 1.0.3" |
| 23 | 26 | |
| app/models/issue.rb | ||
|---|---|---|
| 51 | 51 | |
| 52 | 52 |
acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status), |
| 53 | 53 |
:author_key => :author_id |
| 54 | ||
| 54 |
acts_as_taggable |
|
| 55 | 55 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
| 56 | 56 | |
| 57 | 57 |
attr_accessor :deleted_attachment_ids |
| ... | ... | |
| 244 | 244 |
@total_estimated_hours = nil |
| 245 | 245 |
@last_updated_by = nil |
| 246 | 246 |
@last_notes = nil |
| 247 |
@tags_list = nil |
|
| 247 | 248 |
base_reload(*args) |
| 248 | 249 |
end |
| 249 | 250 | |
| ... | ... | |
| 459 | 460 |
'estimated_hours', |
| 460 | 461 |
'custom_field_values', |
| 461 | 462 |
'custom_fields', |
| 463 |
'tag_list', |
|
| 462 | 464 |
'lock_version', |
| 463 | 465 |
'notes', |
| 464 | 466 |
:if => lambda {|issue, user| issue.new_record? || issue.attributes_editable?(user) }
|
| ... | ... | |
| 1107 | 1109 |
end |
| 1108 | 1110 |
end |
| 1109 | 1111 | |
| 1112 |
def tags_list |
|
| 1113 |
if @tags_list |
|
| 1114 |
@tags_list |
|
| 1115 |
else |
|
| 1116 |
tag_list |
|
| 1117 |
end |
|
| 1118 |
end |
|
| 1119 | ||
| 1110 | 1120 |
# Preloads relations for a collection of issues |
| 1111 | 1121 |
def self.load_relations(issues) |
| 1112 | 1122 |
if issues.any? |
| ... | ... | |
| 1161 | 1171 |
end |
| 1162 | 1172 |
end |
| 1163 | 1173 | |
| 1174 |
# Preloads tags for a collection of issues |
|
| 1175 |
def self.load_issues_tags(issues) |
|
| 1176 |
if issues.any? |
|
| 1177 |
tags = ActsAsTaggableOn::Tag.joins(:taggings) |
|
| 1178 |
.select("#{ActsAsTaggableOn::Tag.table_name}.id", "#{ActsAsTaggableOn::Tag.table_name}.name",
|
|
| 1179 |
"#{ActsAsTaggableOn::Tagging.table_name}.taggable_id")
|
|
| 1180 |
.where(:taggings => {:taggable_type => 'Issue', :taggable_id => issues.map(&:id), :context => 'tags'})
|
|
| 1181 |
.sort |
|
| 1182 |
issues.each do |issue| |
|
| 1183 |
issue.instance_variable_set "@tags_list", (tags.select{|t| t.taggable_id == issue.id} || [])
|
|
| 1184 |
end |
|
| 1185 |
end |
|
| 1186 |
end |
|
| 1187 | ||
| 1164 | 1188 |
# Returns a scope of the given issues and their descendants |
| 1165 | 1189 |
def self.self_and_descendants(issues) |
| 1166 | 1190 |
Issue.joins("JOIN #{Issue.table_name} ancestors" +
|
| ... | ... | |
| 1565 | 1589 |
Tracker.none |
| 1566 | 1590 |
end |
| 1567 | 1591 |
end |
| 1568 | ||
| 1569 | 1592 |
private |
| 1570 | 1593 | |
| 1571 | 1594 |
def user_tracker_permission?(user, permission) |
| app/models/issue_query.rb | ||
|---|---|---|
| 44 | 44 |
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
| 45 | 45 |
QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
|
| 46 | 46 |
QueryColumn.new(:last_updated_by, :sortable => lambda {User.fields_for_order_statement("last_journal_user")}),
|
| 47 |
QueryColumn.new(:tags_list, :caption => :field_tags), |
|
| 47 | 48 |
QueryColumn.new(:relations, :caption => :label_related_issues), |
| 48 | 49 |
QueryColumn.new(:attachments, :caption => :label_attachment_plural), |
| 49 | 50 |
QueryColumn.new(:description, :inline => false), |
| ... | ... | |
| 143 | 144 |
:values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] |
| 144 | 145 |
end |
| 145 | 146 | |
| 147 |
add_available_filter('tags_list', :type => :list_optional, :name => l(:field_tags),
|
|
| 148 |
:values => lambda { tags_values })
|
|
| 149 | ||
| 146 | 150 |
add_available_filter "attachment", |
| 147 | 151 |
:type => :text, :name => l(:label_attachment) |
| 148 | 152 | |
| ... | ... | |
| 307 | 311 |
if has_column?(:last_notes) |
| 308 | 312 |
Issue.load_visible_last_notes(issues) |
| 309 | 313 |
end |
| 314 |
if has_column?(:tags_list) |
|
| 315 |
Issue.load_issues_tags(issues) |
|
| 316 |
end |
|
| 310 | 317 |
issues |
| 311 | 318 |
rescue ::ActiveRecord::StatementInvalid => e |
| 312 | 319 |
raise StatementInvalid.new(e.message) |
| ... | ... | |
| 577 | 584 |
"(#{sql})"
|
| 578 | 585 |
end |
| 579 | 586 | |
| 587 |
def sql_for_tags_list_field(field, operator, value) |
|
| 588 |
case operator |
|
| 589 |
when '=', '!' |
|
| 590 |
issues = Issue.tagged_with(values_for('tags_list'), any: true)
|
|
| 591 |
when '!*' |
|
| 592 |
issues = Issue.tagged_with ActsAsTaggableOn::Tag.all.map(&:to_s), exclude: true |
|
| 593 |
else |
|
| 594 |
issues = Issue.tagged_with ActsAsTaggableOn::Tag.all.map(&:to_s), any: true |
|
| 595 |
end |
|
| 596 |
compare = operator.eql?('!') ? 'NOT IN' : 'IN'
|
|
| 597 |
issue_ids = issues.collect {|issue| issue.id }.push(0).join(',')
|
|
| 598 | ||
| 599 |
"( #{ Issue.table_name }.id #{ compare } (#{ issue_ids }) )"
|
|
| 600 |
end |
|
| 601 | ||
| 580 | 602 |
def find_assigned_to_id_filter_values(values) |
| 581 | 603 |
Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
|
| 582 | 604 |
end |
| app/models/journal.rb | ||
|---|---|---|
| 203 | 203 |
h[c.custom_field_id] = c.value |
| 204 | 204 |
h |
| 205 | 205 |
end |
| 206 |
@tag_list_before_change = journalized.tag_list |
|
| 206 | 207 |
end |
| 207 | 208 |
self |
| 208 | 209 |
end |
| ... | ... | |
| 272 | 273 |
end |
| 273 | 274 |
end |
| 274 | 275 |
end |
| 276 | ||
| 277 |
if @tag_list_before_change |
|
| 278 |
new_tags = journalized.send('tag_list')
|
|
| 279 |
if new_tags != @tag_list_before_change |
|
| 280 |
add_attribute_detail('tags', @tag_list_before_change.to_s, new_tags.to_s)
|
|
| 281 |
end |
|
| 282 |
end |
|
| 275 | 283 |
start |
| 276 | 284 |
end |
| 277 | 285 | |
| app/models/query.rb | ||
|---|---|---|
| 579 | 579 |
end |
| 580 | 580 |
end |
| 581 | 581 | |
| 582 |
def tags_values |
|
| 583 |
issues_scope = Issue.visible.select("#{Issue.table_name}.id").joins(:project)
|
|
| 584 |
issues_scope.where("#{project.project_condition(Setting.display_subprojects_issues?)}") if project
|
|
| 585 | ||
| 586 |
result_scope = ActsAsTaggableOn::Tag |
|
| 587 |
.joins(:taggings) |
|
| 588 |
.select('tags.name')
|
|
| 589 |
.group('tags.id, tags.name')
|
|
| 590 |
.where(taggings: { taggable_type: 'Issue', taggable_id: issues_scope})
|
|
| 591 |
.collect {|t| [t.name, t.name]}
|
|
| 592 | ||
| 593 |
result_scope |
|
| 594 |
end |
|
| 595 | ||
| 582 | 596 |
# Adds available filters |
| 583 | 597 |
def initialize_available_filters |
| 584 | 598 |
# 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.tag_list.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.tag_list.empty? %> |
|
| 77 |
<div class="tags attribute"> |
|
| 78 |
<div class="label"> |
|
| 79 |
<span><%= l(:field_tags) %>:</span> |
|
| 80 |
</div> |
|
| 81 |
<div class="value"> |
|
| 82 |
<%= @issue.tag_list.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 | ||
|---|---|---|
| 378 | 378 |
field_full_width_layout: Full width layout |
| 379 | 379 |
field_digest: Checksum |
| 380 | 380 |
field_default_assigned_to: Default assignee |
| 381 |
field_tags: Tags |
|
| 381 | 382 | |
| 382 | 383 |
setting_app_title: Application title |
| 383 | 384 |
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 | ||
|---|---|---|
| 45 | 45 |
pdf.SetFontStyle('',8)
|
| 46 | 46 |
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
| 47 | 47 |
pdf.ln |
| 48 |
|
|
| 48 | ||
| 49 | 49 |
left = [] |
| 50 | 50 |
left << [l(:field_status), issue.status] |
| 51 | 51 |
left << [l(:field_priority), issue.priority] |
| 52 | 52 |
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
| 53 | 53 |
left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
| 54 | 54 |
left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
| 55 |
|
|
| 55 | ||
| 56 | 56 |
right = [] |
| 57 | 57 |
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
| 58 | 58 |
right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
|
| 59 | 59 |
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
| 60 | 60 |
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
| 61 | 61 |
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) |
| 62 |
|
|
| 62 | ||
| 63 | 63 |
rows = left.size > right.size ? left.size : right.size |
| 64 | 64 |
while left.size < rows |
| 65 | 65 |
left << nil |
| ... | ... | |
| 73 | 73 |
custom_field_values.each_with_index do |custom_value, i| |
| 74 | 74 |
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] |
| 75 | 75 |
end |
| 76 |
|
|
| 76 | ||
| 77 | 77 |
if pdf.get_rtl |
| 78 | 78 |
border_first_top = 'RT' |
| 79 | 79 |
border_last_top = 'LT' |
| ... | ... | |
| 85 | 85 |
border_first = 'L' |
| 86 | 86 |
border_last = 'R' |
| 87 | 87 |
end |
| 88 |
|
|
| 88 | ||
| 89 | 89 |
rows = left.size > right.size ? left.size : right.size |
| 90 | 90 |
rows.times do |i| |
| 91 | 91 |
heights = [] |
| ... | ... | |
| 100 | 100 |
item = right[i] |
| 101 | 101 |
heights << pdf.get_string_height(60, item ? item.last.to_s : "") |
| 102 | 102 |
height = heights.max |
| 103 |
|
|
| 103 | ||
| 104 | 104 |
item = left[i] |
| 105 | 105 |
pdf.SetFontStyle('B',9)
|
| 106 | 106 |
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
| 107 | 107 |
pdf.SetFontStyle('',9)
|
| 108 | 108 |
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0) |
| 109 |
|
|
| 109 | ||
| 110 | 110 |
item = right[i] |
| 111 | 111 |
pdf.SetFontStyle('B',9)
|
| 112 | 112 |
pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
|
| 113 | 113 |
pdf.SetFontStyle('',9)
|
| 114 | 114 |
pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2) |
| 115 |
|
|
| 115 | ||
| 116 | 116 |
pdf.set_x(base_x) |
| 117 | 117 |
end |
| 118 |
|
|
| 118 | ||
| 119 |
tags = issue.tags_list |
|
| 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.RDMwriteHTMLCell(35+155, 5, '', '', tags.to_s, '', "LRB") |
|
| 127 |
end |
|
| 128 | ||
| 119 | 129 |
pdf.SetFontStyle('B',9)
|
| 120 | 130 |
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) |
| 121 | 131 |
pdf.SetFontStyle('',9)
|
| 122 |
|
|
| 132 | ||
| 123 | 133 |
# Set resize image scale |
| 124 | 134 |
pdf.set_image_scale(1.6) |
| 125 | 135 |
text = textilizable(issue, :description, |
| ... | ... | |
| 157 | 167 |
pdf.ln |
| 158 | 168 |
end |
| 159 | 169 |
end |
| 160 |
|
|
| 170 | ||
| 161 | 171 |
relations = issue.relations.select { |r| r.other_issue(issue).visible? }
|
| 162 | 172 |
unless relations.empty? |
| 163 | 173 |
truncate_length = (!is_cjk? ? 80 : 60) |
| ... | ... | |
| 185 | 195 |
end |
| 186 | 196 |
pdf.RDMCell(190,5, "", "T") |
| 187 | 197 |
pdf.ln |
| 188 |
|
|
| 198 | ||
| 189 | 199 |
if issue.changesets.any? && |
| 190 | 200 |
User.current.allowed_to?(:view_changesets, issue.project) |
| 191 | 201 |
pdf.SetFontStyle('B',9)
|
| ... | ... | |
| 205 | 215 |
pdf.ln |
| 206 | 216 |
end |
| 207 | 217 |
end |
| 208 |
|
|
| 218 | ||
| 209 | 219 |
if assoc[:journals].present? |
| 210 | 220 |
pdf.SetFontStyle('B',9)
|
| 211 | 221 |
pdf.RDMCell(190,5, l(:label_history), "B") |
| ... | ... | |
| 234 | 244 |
pdf.ln |
| 235 | 245 |
end |
| 236 | 246 |
end |
| 237 |
|
|
| 247 | ||
| 238 | 248 |
if issue.attachments.any? |
| 239 | 249 |
pdf.SetFontStyle('B',9)
|
| 240 | 250 |
pdf.RDMCell(190,5, l(:label_attachment_plural), "B") |
| ... | ... | |
| 261 | 271 |
pdf.footer_date = format_date(User.current.today) |
| 262 | 272 |
pdf.set_auto_page_break(false) |
| 263 | 273 |
pdf.add_page("L")
|
| 264 |
|
|
| 274 | ||
| 265 | 275 |
# Landscape A4 = 210 x 297 mm |
| 266 | 276 |
page_height = pdf.get_page_height # 210 |
| 267 | 277 |
page_width = pdf.get_page_width # 297 |
| ... | ... | |
| 269 | 279 |
right_margin = pdf.get_original_margins['right'] # 10 |
| 270 | 280 |
bottom_margin = pdf.get_footer_margin |
| 271 | 281 |
row_height = 4 |
| 272 |
|
|
| 282 | ||
| 273 | 283 |
# column widths |
| 274 | 284 |
table_width = page_width - right_margin - left_margin |
| 275 | 285 |
col_width = [] |
| ... | ... | |
| 277 | 287 |
col_width = calc_col_width(issues, query, table_width, pdf) |
| 278 | 288 |
table_width = col_width.inject(0, :+) |
| 279 | 289 |
end |
| 280 |
|
|
| 290 | ||
| 281 | 291 |
# use full width if the description or last_notes are displayed |
| 282 | 292 |
if table_width > 0 && (query.has_column?(:description) || query.has_column?(:last_notes)) |
| 283 | 293 |
col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width}
|
| 284 | 294 |
table_width = col_width.inject(0, :+) |
| 285 | 295 |
end |
| 286 |
|
|
| 296 | ||
| 287 | 297 |
# title |
| 288 | 298 |
pdf.SetFontStyle('B',11)
|
| 289 | 299 |
pdf.RDMCell(190, 8, title) |
| ... | ... | |
| 317 | 327 |
end |
| 318 | 328 |
previous_group = group |
| 319 | 329 |
end |
| 320 |
|
|
| 330 | ||
| 321 | 331 |
# fetch row values |
| 322 | 332 |
col_values = fetch_row_values(issue, query, level) |
| 323 |
|
|
| 333 | ||
| 324 | 334 |
# make new page if it doesn't fit on the current one |
| 325 | 335 |
base_y = pdf.get_y |
| 326 | 336 |
max_height = get_issues_to_pdf_write_cells(pdf, col_values, col_width) |
| ... | ... | |
| 330 | 340 |
render_table_header(pdf, query, col_width, row_height, table_width) |
| 331 | 341 |
base_y = pdf.get_y |
| 332 | 342 |
end |
| 333 |
|
|
| 343 | ||
| 334 | 344 |
# write the cells on page |
| 335 | 345 |
issues_to_pdf_write_cells(pdf, col_values, col_width, max_height) |
| 336 | 346 |
pdf.set_y(base_y + max_height) |
| 337 |
|
|
| 347 | ||
| 338 | 348 |
if query.has_column?(:description) && issue.description? |
| 339 | 349 |
pdf.set_x(10) |
| 340 | 350 |
pdf.set_auto_page_break(true, bottom_margin) |
| ... | ... | |
| 349 | 359 |
pdf.set_auto_page_break(false) |
| 350 | 360 |
end |
| 351 | 361 |
end |
| 352 |
|
|
| 362 | ||
| 353 | 363 |
if issues.size == Setting.issues_export_limit.to_i |
| 354 | 364 |
pdf.SetFontStyle('B',10)
|
| 355 | 365 |
pdf.RDMCell(0, row_height, '...') |
| ... | ... | |
| 379 | 389 |
value = " " * level + value |
| 380 | 390 |
when :attachments |
| 381 | 391 |
value = value.to_a.map {|a| a.filename}.join("\n")
|
| 392 |
when :tags_list |
|
| 393 |
value = value.to_s |
|
| 382 | 394 |
end |
| 383 | 395 |
if value.is_a?(Date) |
| 384 | 396 |
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: <%= 2.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: <%= 2.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 | ||
|---|---|---|
| 43 | 43 |
:journal_details, |
| 44 | 44 |
:queries, |
| 45 | 45 |
:repositories, |
| 46 |
:changesets |
|
| 46 |
:changesets, |
|
| 47 |
:tags, |
|
| 48 |
:taggings |
|
| 47 | 49 | |
| 48 | 50 |
include Redmine::I18n |
| 49 | 51 | |
| ... | ... | |
| 1558 | 1560 |
end |
| 1559 | 1561 |
end |
| 1560 | 1562 | |
| 1563 |
def test_index_with_tags_column |
|
| 1564 |
get :index, :params => {
|
|
| 1565 |
:set_filter => 1, |
|
| 1566 |
:project_id => 1, |
|
| 1567 |
:c => %w(subject tags_list) |
|
| 1568 |
} |
|
| 1569 | ||
| 1570 |
assert_response :success |
|
| 1571 |
assert_select 'td.tags_list', :text => 'UX' |
|
| 1572 |
assert_select 'td.tags_list', :text => 'UX, Backend' |
|
| 1573 | ||
| 1574 |
get :index, :params => {
|
|
| 1575 |
:set_filter => 1, |
|
| 1576 |
:project_id => 1, |
|
| 1577 |
:c => %w(subject tags_list), |
|
| 1578 |
:format => 'pdf' |
|
| 1579 |
} |
|
| 1580 |
assert_response :success |
|
| 1581 |
assert_equal 'application/pdf', response.content_type |
|
| 1582 |
end |
|
| 1583 | ||
| 1561 | 1584 |
def test_show_by_anonymous |
| 1562 | 1585 |
get :show, :params => {
|
| 1563 | 1586 |
:id => 1 |
| ... | ... | |
| 2312 | 2335 |
assert_select 'a', :text => 'Delete', :count => 0 |
| 2313 | 2336 |
end |
| 2314 | 2337 | |
| 2338 |
def test_show_should_render_issue_tags_for_issue_with_tags |
|
| 2339 |
@request.session[:user_id] = 1 |
|
| 2340 | ||
| 2341 |
get :show, :params => {
|
|
| 2342 |
:id => 1 |
|
| 2343 |
} |
|
| 2344 | ||
| 2345 |
assert_response :success |
|
| 2346 |
assert_select 'div.tags .value', :text => 'UX, Backend', :count => 1 |
|
| 2347 |
end |
|
| 2348 | ||
| 2349 |
def test_show_should_not_render_issue_tags_for_issue_without_tags |
|
| 2350 |
@request.session[:user_id] = 1 |
|
| 2351 | ||
| 2352 |
get :show, :params => {
|
|
| 2353 |
:id => 14 |
|
| 2354 |
} |
|
| 2355 | ||
| 2356 |
assert_response :success |
|
| 2357 |
assert_select 'div.tags', 0 |
|
| 2358 |
end |
|
| 2359 | ||
| 2315 | 2360 |
def test_get_new |
| 2316 | 2361 |
@request.session[:user_id] = 2 |
| 2317 | 2362 |
get :new, :params => {
|
| ... | ... | |
| 2338 | 2383 |
assert_select 'select[name=?]', 'issue[done_ratio]' |
| 2339 | 2384 |
assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string' |
| 2340 | 2385 |
assert_select 'input[name=?]', 'issue[watcher_user_ids][]' |
| 2386 |
assert_select 'input[name=?]', 'issue[tag_list]' |
|
| 2341 | 2387 |
end |
| 2342 | 2388 | |
| 2343 | 2389 |
# Be sure we don't display inactive IssuePriorities |
| ... | ... | |
| 3291 | 3337 |
assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail) |
| 3292 | 3338 |
end |
| 3293 | 3339 | |
| 3340 |
def test_post_create_with_tags |
|
| 3341 |
@request.session[:user_id] = 2 |
|
| 3342 | ||
| 3343 |
post :create, :params => {
|
|
| 3344 |
:project_id => 1, |
|
| 3345 |
:issue => {
|
|
| 3346 |
:tracker_id => 1, |
|
| 3347 |
:subject => 'This is a new issue with tags', |
|
| 3348 |
:description => 'This is the description', |
|
| 3349 |
:priority_id => 5, |
|
| 3350 |
:tag_list => 'Two, Three' |
|
| 3351 |
} |
|
| 3352 |
} |
|
| 3353 | ||
| 3354 |
issue = Issue.order('id DESC').first
|
|
| 3355 |
assert_equal ['Two', 'Three'], issue.tag_list |
|
| 3356 |
end |
|
| 3357 | ||
| 3294 | 3358 |
def test_post_create_subissue |
| 3295 | 3359 |
@request.session[:user_id] = 2 |
| 3296 | 3360 | |
| ... | ... | |
| 4644 | 4708 |
:project_id => '1', |
| 4645 | 4709 |
:tracker_id => '2', |
| 4646 | 4710 |
:priority_id => '6' |
| 4647 | ||
| 4648 | 4711 |
} |
| 4649 | 4712 |
} |
| 4650 | 4713 |
end |
| ... | ... | |
| 5261 | 5324 |
assert_equal 'Original subject', issue.reload.subject |
| 5262 | 5325 |
end |
| 5263 | 5326 | |
| 5327 |
def test_put_update_issue_tags |
|
| 5328 |
@request.session[:user_id] = 1 |
|
| 5329 | ||
| 5330 |
put :update, :params => {
|
|
| 5331 |
:id => 1, |
|
| 5332 |
:issue => {
|
|
| 5333 |
:tag_list => 'Three' |
|
| 5334 |
} |
|
| 5335 |
} |
|
| 5336 |
assert_response 302 |
|
| 5337 | ||
| 5338 |
assert_equal ['Three'], Issue.find(1).tag_list |
|
| 5339 |
end |
|
| 5340 | ||
| 5264 | 5341 |
def test_get_bulk_edit |
| 5265 | 5342 |
@request.session[:user_id] = 2 |
| 5266 | 5343 |
get :bulk_edit, :params => {
|
| test/functional/queries_controller_test.rb | ||
|---|---|---|
| 23 | 23 |
:members, :member_roles, :roles, |
| 24 | 24 |
:trackers, :issue_statuses, :issue_categories, :enumerations, :versions, |
| 25 | 25 |
:issues, :custom_fields, :custom_values, |
| 26 |
:queries |
|
| 26 |
:queries, :tags, :taggings
|
|
| 27 | 27 | |
| 28 | 28 |
def setup |
| 29 | 29 |
User.current = nil |
| ... | ... | |
| 732 | 732 |
assert_include ["Dave Lopper", "3", "active"], json |
| 733 | 733 |
assert_include ["Dave2 Lopper2", "5", "locked"], json |
| 734 | 734 |
end |
| 735 | ||
| 736 |
def test_filter_with_tags_should_return_filter_values |
|
| 737 |
@request.session[:user_id] = 2 |
|
| 738 |
get :filter, :params => {
|
|
| 739 |
:project_id => 1, |
|
| 740 |
:type => 'IssueQuery', |
|
| 741 |
:name => 'tags_list' |
|
| 742 |
} |
|
| 743 | ||
| 744 |
assert_response :success |
|
| 745 |
assert_equal 'application/json', response.content_type |
|
| 746 |
json = ActiveSupport::JSON.decode(response.body) |
|
| 747 |
assert_equal [["UX","UX"],["Backend","Backend"]], json |
|
| 748 |
end |
|
| 735 | 749 |
end |
| test/unit/issue_tags_test.rb | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2017 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 14 |
# You should have received a copy of the GNU General Public License |
|
| 15 |
# along with this program; if not, write to the Free Software |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 | ||
| 18 |
require File.expand_path('../../test_helper', __FILE__)
|
|
| 19 | ||
| 20 |
class IssueTagsTest < ActiveSupport::TestCase |
|
| 21 |
fixtures :projects, :users, :email_addresses, :user_preferences, :members, :member_roles, :roles, |
|
| 22 |
:groups_users, |
|
| 23 |
:trackers, :projects_trackers, |
|
| 24 |
:enabled_modules, |
|
| 25 |
:issue_statuses, |
|
| 26 |
:issues, :journals, :journal_details, |
|
| 27 |
:tags, :taggings |
|
| 28 | ||
| 29 |
include Redmine::I18n |
|
| 30 | ||
| 31 |
def setup |
|
| 32 |
set_language_if_valid 'en' |
|
| 33 |
end |
|
| 34 | ||
| 35 |
def teardown |
|
| 36 |
User.current = nil |
|
| 37 |
end |
|
| 38 | ||
| 39 |
def test_issue_tag_list_should_return_an_array_of_tags |
|
| 40 |
assert_equal ['UX', 'Backend'], Issue.find(1).tag_list |
|
| 41 |
end |
|
| 42 | ||
| 43 |
def test_issue_tag_list_to_s_should_return_a_string_of_tags_delimited_by_comma |
|
| 44 |
assert_equal 'UX, Backend', Issue.find(1).tag_list.to_s |
|
| 45 |
end |
|
| 46 | ||
| 47 |
def test_add_issue_tags |
|
| 48 |
issue = Issue.find(2) |
|
| 49 |
issue.tag_list = "One, Two" |
|
| 50 | ||
| 51 |
assert issue.save! |
|
| 52 | ||
| 53 |
issue.reload |
|
| 54 | ||
| 55 |
assert_equal ['One', 'Two'], issue.tag_list |
|
| 56 |
end |
|
| 57 | ||
| 58 |
def test_clear_issue_tags |
|
| 59 |
issue = Issue.find(1) |
|
| 60 |
issue.tag_list = '' |
|
| 61 | ||
| 62 |
assert issue.save! |
|
| 63 |
assert_equal [], issue.tag_list |
|
| 64 |
end |
|
| 65 | ||
| 66 |
def test_update_issue_tags_should_journalize_changes |
|
| 67 |
issue = Issue.find(1) |
|
| 68 |
issue.init_journal User.find(1) |
|
| 69 |
issue.tag_list = "UX, API" |
|
| 70 | ||
| 71 |
assert_difference 'Journal.count', 1 do |
|
| 72 |
assert_difference 'JournalDetail.count', 1 do |
|
| 73 |
issue.save! |
|
| 74 |
end |
|
| 75 |
end |
|
| 76 |
issue.reload |
|
| 77 | ||
| 78 |
assert_equal ['UX', 'API'], issue.tag_list |
|
| 79 | ||
| 80 |
detail = JournalDetail.order('id DESC').first
|
|
| 81 |
assert_equal issue, detail.journal.journalized |
|
| 82 |
assert_equal 'attr', detail.property |
|
| 83 |
assert_equal 'tags', detail.prop_key |
|
| 84 |
assert_equal 'UX, Backend', detail.old_value |
|
| 85 |
assert_equal 'UX, API', detail.value |
|
| 86 |
end |
|
| 87 | ||
| 88 |
def test_update_issue_tags_should_not_journalize_changes_if_tags_are_not_changed |
|
| 89 |
issue = Issue.find(1) |
|
| 90 |
issue.init_journal User.find(1) |
|
| 91 |
issue.tag_list = "UX, Backend" |
|
| 92 | ||
| 93 |
assert_difference 'Journal.count', 0 do |
|
| 94 |
assert_difference 'JournalDetail.count', 0 do |
|
| 95 |
issue.save! |
|
| 96 |
end |
|
| 97 |
end |
|
| 98 |
end |
|
| 99 |
end |
|
| test/unit/lib/redmine/export/pdf/issues_pdf_test.rb | ||
|---|---|---|
| 19 | 19 | |
| 20 | 20 |
class IssuesPdfHelperTest < ActiveSupport::TestCase |
| 21 | 21 |
fixtures :users, :projects, :roles, :members, :member_roles, |
| 22 |
:enabled_modules, :issues, :trackers, :enumerations |
|
| 22 |
:enabled_modules, :issues, :trackers, :enumerations, |
|
| 23 |
:tags, :taggings |
|
| 23 | 24 | |
| 24 | 25 |
include Redmine::Export::PDF::IssuesPdfHelper |
| 25 | 26 | |
| ... | ... | |
| 32 | 33 |
results = fetch_row_values(issue, query, 0) |
| 33 | 34 |
assert_equal ["2", "Add ingredients categories", "4.34"], results |
| 34 | 35 |
end |
| 36 | ||
| 37 |
def test_fetch_row_values_should_return_issue_tags_as_string |
|
| 38 |
query = IssueQuery.new(:project => Project.find(1), :name => '_') |
|
| 39 |
query.column_names = [:subject, :tags_list] |
|
| 40 | ||
| 41 |
results = fetch_row_values(Issue.find(1), query, 0) |
|
| 42 | ||
| 43 |
assert_equal ["1", "Cannot print recipes", "UX, Backend"], results |
|
| 44 |
end |
|
| 35 | 45 |
end |
| test/unit/query_test.rb | ||
|---|---|---|
| 30 | 30 |
:projects_trackers, |
| 31 | 31 |
:custom_fields_trackers, |
| 32 | 32 |
:workflows, :journals, |
| 33 |
:attachments |
|
| 33 |
:attachments, |
|
| 34 |
:tags, :taggings |
|
| 34 | 35 | |
| 35 | 36 |
INTEGER_KLASS = RUBY_VERSION >= "2.4" ? Integer : Fixnum |
| 36 | 37 | |
| ... | ... | |
| 821 | 822 |
end |
| 822 | 823 |
end |
| 823 | 824 | |
| 825 |
def test_filter_by_tags_with_operator_is |
|
| 826 |
query = IssueQuery.new(:name => '_') |
|
| 827 |
filter_name = "tags_list" |
|
| 828 |
assert_include filter_name, query.available_filters.keys |
|
| 829 | ||
| 830 |
query.filters = {filter_name => {:operator => '=', :values => ['UX']}}
|
|
| 831 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
| 832 | ||
| 833 |
# Should return issue tagged with any of the values |
|
| 834 |
query.filters = {filter_name => {:operator => '=', :values => ['UX, Backend']}}
|
|
| 835 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
| 836 |
end |
|
| 837 | ||
| 838 |
def test_filter_by_tags_with_operator_is_not |
|
| 839 |
query = IssueQuery.new(:name => '_') |
|
| 840 |
filter_name = "tags_list" |
|
| 841 |
assert_include filter_name, query.available_filters.keys |
|
| 842 | ||
| 843 |
query.filters = {filter_name => {:operator => '!', :values => ['Backend']}}
|
|
| 844 |
issues = find_issues_with_query(query).map(&:id).sort |
|
| 845 | ||
| 846 |
# Issue tagged with Backend should not be returned |
|
| 847 |
assert_not_include 1, issues |
|
| 848 |
assert_include 2, issues |
|
| 849 |
# Untagged issues should be returned |
|
| 850 |
assert_include 5, issues |
|
| 851 |
end |
|
| 852 | ||
| 853 |
def test_filter_by_tags_with_operator_none |
|
| 854 |
query = IssueQuery.new(:name => '_') |
|
| 855 |
filter_name = "tags_list" |
|
| 856 |
assert_include filter_name, query.available_filters.keys |
|
| 857 | ||
| 858 |
query.filters = {filter_name => {:operator => '!*', :values => ['']}}
|
|
| 859 |
issues = find_issues_with_query(query).map(&:id).sort |
|
| 860 | ||
| 861 |
# Tagged issues should not be returned |
|
| 862 |
assert_not_include 1, issues |
|
| 863 |
assert_not_include 2, issues |
|
| 864 | ||
| 865 |
# Untagged issues should be returned |
|
| 866 |
assert_include 5, issues |
|
| 867 |
end |
|
| 868 | ||
| 869 |
def test_filter_by_tags_with_operator_any |
|
| 870 |
query = IssueQuery.new(:name => '_') |
|
| 871 |
filter_name = "tags_list" |
|
| 872 |
assert_include filter_name, query.available_filters.keys |
|
| 873 | ||
| 874 |
query.filters = {filter_name => {:operator => '*', :values => ['']}}
|
|
| 875 |
assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort |
|
| 876 |
end |
|
| 877 | ||
| 824 | 878 |
def test_user_custom_field_filtered_on_me |
| 825 | 879 |
User.current = User.find(2) |
| 826 | 880 |
cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1]) |
| ... | ... | |
| 1400 | 1454 |
assert_not_nil issues.first.instance_variable_get("@last_notes")
|
| 1401 | 1455 |
end |
| 1402 | 1456 | |
| 1457 |
def test_query_should_preload_tags |
|
| 1458 |
q = IssueQuery.new(:name => '_', :column_names => [:subject, :tags_list]) |
|
| 1459 |
assert q.has_column?(:tags_list) |
|
| 1460 |
issues = q.issues |
|
| 1461 |
assert_not_nil issues.first.instance_variable_get("@tags_list")
|
|
| 1462 |
end |
|
| 1463 | ||
| 1403 | 1464 |
def test_groupable_columns_should_include_custom_fields |
| 1404 | 1465 |
q = IssueQuery.new |
| 1405 | 1466 |
column = q.groupable_columns.detect {|c| c.name == :cf_1}
|