mirror of
https://github.com/redmine/redmine.git
synced 2025-10-26 07:46:17 +01:00
Option to switch between table list and board list (#29482).
Patch by Marius BALTEANU. git-svn-id: http://svn.redmine.org/redmine/trunk@18765 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -34,6 +34,8 @@ class ProjectsController < ApplicationController
|
||||
helper :issues
|
||||
helper :queries
|
||||
include QueriesHelper
|
||||
helper :projects_queries
|
||||
include ProjectsQueriesHelper
|
||||
helper :repositories
|
||||
helper :members
|
||||
helper :trackers
|
||||
@@ -50,7 +52,9 @@ class ProjectsController < ApplicationController
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@projects = scope.to_a
|
||||
@entry_count = scope.count
|
||||
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
|
||||
@entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
|
||||
}
|
||||
format.api {
|
||||
@offset, @limit = api_offset_and_limit
|
||||
@@ -61,6 +65,11 @@ class ProjectsController < ApplicationController
|
||||
projects = scope.reorder(:created_on => :desc).limit(Setting.feeds_limit.to_i).to_a
|
||||
render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
|
||||
}
|
||||
format.csv {
|
||||
# Export all entries
|
||||
@entries = scope.to_a
|
||||
send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'projects.csv')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -158,4 +158,13 @@ module ProjectsHelper
|
||||
url = bookmark_project_url(project)
|
||||
link_to text, url, remote: true, method: method, class: css
|
||||
end
|
||||
|
||||
def grouped_project_list(projects, query, &block)
|
||||
ancestors = []
|
||||
grouped_query_results(projects, query) do |project, group_name, group_count, group_totals|
|
||||
ancestors.pop while ancestors.any? && !project.is_descendant_of?(ancestors.last)
|
||||
yield project, ancestors.size, group_name, group_count, group_totals
|
||||
ancestors << project unless project.leaf?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
61
app/helpers/projects_queries_helper.rb
Normal file
61
app/helpers/projects_queries_helper.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine - project management software
|
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
module ProjectsQueriesHelper
|
||||
include ApplicationHelper
|
||||
|
||||
def column_value(column, item, value)
|
||||
if item.is_a?(Project)
|
||||
case column.name
|
||||
when :name
|
||||
link_to_project(item) + (content_tag('span', '', :class => 'icon icon-user my-project', :title => l(:label_my_projects)) if User.current.member_of?(item))
|
||||
when :short_description
|
||||
item.description? ? content_tag('div', textilizable(item, :short_description), :class => "wiki") : ''
|
||||
when :homepage
|
||||
item.homepage? ? content_tag('div', textilizable(item, :homepage), :class => "wiki") : ''
|
||||
when :status
|
||||
get_project_status_label[column.value_object(item)]
|
||||
when :parent_id
|
||||
link_to_project(item.parent) unless item.parent.nil?
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def csv_content(column, item)
|
||||
if item.is_a?(Project)
|
||||
case column.name
|
||||
when :status
|
||||
get_project_status_label[column.value_object(item)]
|
||||
when :parent_id
|
||||
return item.parent.name unless item.parent.nil?
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_project_status_label
|
||||
{
|
||||
Project::STATUS_ACTIVE => l(:project_status_active),
|
||||
Project::STATUS_CLOSED => l(:project_status_closed)
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -120,6 +120,14 @@ module QueriesHelper
|
||||
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
|
||||
end
|
||||
|
||||
def available_display_types_tags(query)
|
||||
available_display_types = []
|
||||
query.available_display_types.each do |t|
|
||||
available_display_types << [l(:"label_display_type_#{t}"), t]
|
||||
end
|
||||
select_tag('display_type', options_for_select(available_display_types, @query.display_type), :id => 'display_type')
|
||||
end
|
||||
|
||||
def grouped_query_results(items, query, &block)
|
||||
result_count_by_group = query.result_count_by_group
|
||||
previous_group, first = false, true
|
||||
|
||||
@@ -22,7 +22,16 @@ class ProjectQuery < Query
|
||||
self.queried_class = Project
|
||||
self.view_permission = :search_project
|
||||
|
||||
self.available_columns = []
|
||||
self.available_columns = [
|
||||
QueryColumn.new(:name, :sortable => "#{Project.table_name}.name"),
|
||||
QueryColumn.new(:status, :sortable => "#{Project.table_name}.status"),
|
||||
QueryColumn.new(:short_description, :sortable => "#{Project.table_name}.description", :caption => :field_description),
|
||||
QueryColumn.new(:homepage, :sortable => "#{Project.table_name}.homepage"),
|
||||
QueryColumn.new(:identifier, :sortable => "#{Project.table_name}.identifier"),
|
||||
QueryColumn.new(:parent_id, :sortable => "#{Project.table_name}.lft ASC", :default_order => 'desc', :caption => :field_parent),
|
||||
QueryColumn.new(:is_public, :sortable => "#{Project.table_name}.is_public", :groupable => true),
|
||||
QueryColumn.new(:created_on, :sortable => "#{Project.table_name}.created_on", :default_order => 'desc')
|
||||
]
|
||||
|
||||
def initialize(attributes=nil, *args)
|
||||
super attributes
|
||||
@@ -48,7 +57,23 @@ class ProjectQuery < Query
|
||||
end
|
||||
|
||||
def available_columns
|
||||
[]
|
||||
return @available_columns if @available_columns
|
||||
@available_columns = self.class.available_columns.dup
|
||||
@available_columns += ProjectCustomField.visible.
|
||||
map {|cf| QueryAssociationCustomFieldColumn.new(:project, cf) }
|
||||
@available_columns
|
||||
end
|
||||
|
||||
def available_display_types
|
||||
['board', 'list']
|
||||
end
|
||||
|
||||
def default_columns_names
|
||||
@default_columns_names ||= [:name, :identifier, :short_description]
|
||||
end
|
||||
|
||||
def default_sort_criteria
|
||||
[[]]
|
||||
end
|
||||
|
||||
def base_scope
|
||||
|
||||
@@ -409,6 +409,7 @@ class Query < ActiveRecord::Base
|
||||
self.column_names = params[:c] || query_params[:column_names] || self.column_names
|
||||
self.totalable_names = params[:t] || query_params[:totalable_names] || self.totalable_names
|
||||
self.sort_criteria = params[:sort] || query_params[:sort_criteria] || self.sort_criteria
|
||||
self.display_type = params[:display_type] || query_params[:display_type] || self.display_type
|
||||
self
|
||||
end
|
||||
|
||||
@@ -983,6 +984,21 @@ class Query < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def display_type
|
||||
options[:display_type] || self.available_display_types.first
|
||||
end
|
||||
|
||||
def display_type=(type)
|
||||
unless type || self.available_display_types.include?(type)
|
||||
type = self.available_display_types.first
|
||||
end
|
||||
options[:display_type] = type
|
||||
end
|
||||
|
||||
def available_display_types
|
||||
['list']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def grouped_query(&block)
|
||||
|
||||
3
app/views/projects/_board.html.erb
Normal file
3
app/views/projects/_board.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
<div id="projects-index">
|
||||
<%= render_project_hierarchy(@entries) %>
|
||||
</div>
|
||||
35
app/views/projects/_list.html.erb
Normal file
35
app/views/projects/_list.html.erb
Normal file
@@ -0,0 +1,35 @@
|
||||
<div class="autoscroll">
|
||||
<table class="list projects odd-even <%= @query.css_classes %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<% @query.inline_columns.each do |column| %>
|
||||
<%= column_header(@query, column) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% grouped_project_list(entries, @query) do |entry, level, group_name, group_count, group_totals| -%>
|
||||
<% if group_name %>
|
||||
<% reset_cycle %>
|
||||
<tr class="group open">
|
||||
<td colspan="<%= @query.inline_columns.size %>">
|
||||
<span class="expander" onclick="toggleRowGroup(this);"> </span>
|
||||
<span class="name"><%= group_name %></span>
|
||||
<% if group_count %>
|
||||
<span class="count"><%= group_count %></span>
|
||||
<% end %>
|
||||
<span class="totals"><%= group_totals %></span>
|
||||
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
|
||||
"toggleAllRowGroups(this)", :class => 'toggle-all') %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr id="project-<%= entry.id %>" class="<%= cycle('odd', 'even') %> <%= entry.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
|
||||
<% @query.inline_columns.each do |column| %>
|
||||
<%= content_tag('td', column_content(column, entry), :class => column.css_classes) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -11,12 +11,11 @@
|
||||
<% end %>
|
||||
|
||||
<% if @query.valid? %>
|
||||
<% if @projects.empty? %>
|
||||
<% if @entries.empty? %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% else %>
|
||||
<div id="projects-index">
|
||||
<%= render_project_hierarchy(@projects) %>
|
||||
</div>
|
||||
<%= render :partial => @query.display_type, :locals => { :entries => @entries }%>
|
||||
<span class="pagination"><%= pagination_links_full @entry_pages, @entry_count %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
@@ -29,12 +29,18 @@
|
||||
<% end %>
|
||||
|
||||
<fieldset id="options"><legend><%= l(:label_options) %></legend>
|
||||
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label>
|
||||
<% if @query.available_display_types.size > 1 %>
|
||||
<p><label for='display_type'><%= l(:label_display_type) %></label>
|
||||
<%= available_display_types_tags(@query) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p id ="default_columns"><label for="query_default_columns"><%=l(:label_default_columns)%></label>
|
||||
<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
|
||||
:data => {:disables => "#columns, .block_columns input"} %></p>
|
||||
|
||||
<% unless params[:gantt] %>
|
||||
<p><label for="query_group_by"><%= l(:field_group_by) %></label>
|
||||
<p id="group_by"><label id="group_by" for="query_group_by"><%= l(:field_group_by) %></label>
|
||||
<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p>
|
||||
|
||||
<% unless @query.available_block_columns.empty? %>
|
||||
@@ -99,4 +105,15 @@ $(document).ready(function(){
|
||||
$("input.disable-unless-private").attr('disabled', !private_checked);
|
||||
}).trigger('change');
|
||||
});
|
||||
|
||||
$(function ($) {
|
||||
$('#display_type').change(function (e) {
|
||||
var option = $(e.target).val()
|
||||
if (option == 'board') {
|
||||
$('fieldset#columns, fieldset#sort, p#default_columns, p#group_by').hide();
|
||||
} else {
|
||||
$('fieldset#columns, fieldset#sort, p#default_columns, p#group_by').show();
|
||||
}
|
||||
}).change()
|
||||
});
|
||||
<% end %>
|
||||
|
||||
@@ -14,8 +14,14 @@
|
||||
<% if @query.available_columns.any? %>
|
||||
<fieldset id="options" class="collapsible collapsed">
|
||||
<legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend>
|
||||
<div style="display: none;">
|
||||
<table>
|
||||
<div class="hidden">
|
||||
<% if @query.available_display_types.size > 1 %>
|
||||
<div>
|
||||
<span class="field"><label for='display_type'><%= l(:label_display_type) %></label></span>
|
||||
<%= available_display_types_tags(@query) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<table id="list" class="<%= 'hidden' if (@query.display_type == 'board') %>">
|
||||
<% if @query.available_columns.any? %>
|
||||
<tr>
|
||||
<td class="field"><%= l(:field_column_names) %></td>
|
||||
@@ -65,3 +71,18 @@
|
||||
</div>
|
||||
|
||||
<%= error_messages_for @query %>
|
||||
|
||||
<%= javascript_tag do %>
|
||||
$(function ($) {
|
||||
$('#display_type').change(function (e) {
|
||||
var option = $(e.target).val()
|
||||
if (option == 'board') {
|
||||
$('table#list').hide();
|
||||
} else {
|
||||
$('table#list').show();
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
<% end %>
|
||||
|
||||
@@ -1072,6 +1072,9 @@ en:
|
||||
label_password_char_class_lowercase: lowercase letters
|
||||
label_password_char_class_digits: digits
|
||||
label_password_char_class_special_chars: special characters
|
||||
label_display_type: Display results as
|
||||
label_display_type_list: List
|
||||
label_display_type_board: Board
|
||||
|
||||
button_login: Login
|
||||
button_submit: Submit
|
||||
|
||||
@@ -130,6 +130,7 @@ div.modal .box p {margin: 0.3em 0;}
|
||||
.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
|
||||
|
||||
.mobile-show {display: none;}
|
||||
.hidden {display: none;}
|
||||
|
||||
/***** Links *****/
|
||||
a, a:link, a:visited{ color: #169; text-decoration: none; }
|
||||
@@ -235,7 +236,8 @@ table.list, .table-list { border: 1px solid #e4e4e4; width: 100%; margin-bottom:
|
||||
table.list th, .table-list-header { background-color:#EEEEEE; padding: 4px; white-space:nowrap; font-weight:bold; }
|
||||
table.list td {text-align:center; vertical-align:middle; padding-right:10px;}
|
||||
table.list td.id { width: 2%; text-align: center;}
|
||||
table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles, table.list td.attachments, table.list td.text {text-align: left;}
|
||||
table.list td.name, table.list td.description, table.list td.subject, table.list td.comments, table.list td.roles, table.list td.attachments, table.list td.text, table.list td.short_description {text-align: left;}
|
||||
|
||||
table.list td.attachments a {display:block;}
|
||||
table.list td.tick {width:15%}
|
||||
table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
|
||||
@@ -257,17 +259,6 @@ tr.project td.name a { white-space:nowrap; }
|
||||
tr.project.closed, tr.project.archived { color: #aaa; }
|
||||
tr.project.closed a, tr.project.archived a { color: #aaa; }
|
||||
|
||||
tr.project.idnt td.name span {background: url(../images/arrow_right.png) no-repeat 2px 50%; padding-left: 16px;}
|
||||
tr.project.idnt-1 td.name {padding-left: 0.5em;}
|
||||
tr.project.idnt-2 td.name {padding-left: 2em;}
|
||||
tr.project.idnt-3 td.name {padding-left: 3.5em;}
|
||||
tr.project.idnt-4 td.name {padding-left: 5em;}
|
||||
tr.project.idnt-5 td.name {padding-left: 6.5em;}
|
||||
tr.project.idnt-6 td.name {padding-left: 8em;}
|
||||
tr.project.idnt-7 td.name {padding-left: 9.5em;}
|
||||
tr.project.idnt-8 td.name {padding-left: 11em;}
|
||||
tr.project.idnt-9 td.name {padding-left: 12.5em;}
|
||||
|
||||
tr.issue { text-align: center; white-space: nowrap; }
|
||||
tr.issue td.subject, tr.issue td.category, td.assigned_to, td.last_updated_by, tr.issue td.string, tr.issue td.text, tr.issue td.list, tr.issue td.relations, tr.issue td.parent { white-space: normal; }
|
||||
tr.issue td.relations { text-align: left; }
|
||||
@@ -277,16 +268,16 @@ table.issues td.block_column {color:#777; font-size:90%; padding:4px 4px 4px 24p
|
||||
table.issues td.block_column span {font-weight: bold; display: block; margin-bottom: 4px;}
|
||||
table.issues td.block_column pre {white-space:normal;}
|
||||
|
||||
tr.issue.idnt td.subject {background: url(../images/arrow_right.png) no-repeat 2px 50%;}
|
||||
tr.issue.idnt-1 td.subject {padding-left: 24px; background-position: 8px 50%;}
|
||||
tr.issue.idnt-2 td.subject {padding-left: 40px; background-position: 24px 50%;}
|
||||
tr.issue.idnt-3 td.subject {padding-left: 56px; background-position: 40px 50%;}
|
||||
tr.issue.idnt-4 td.subject {padding-left: 72px; background-position: 56px 50%;}
|
||||
tr.issue.idnt-5 td.subject {padding-left: 88px; background-position: 72px 50%;}
|
||||
tr.issue.idnt-6 td.subject {padding-left: 104px; background-position: 88px 50%;}
|
||||
tr.issue.idnt-7 td.subject {padding-left: 120px; background-position: 104px 50%;}
|
||||
tr.issue.idnt-8 td.subject {padding-left: 136px; background-position: 120px 50%;}
|
||||
tr.issue.idnt-9 td.subject {padding-left: 152px; background-position: 136px 50%;}
|
||||
tr.issue.idnt td.subject, tr.project.idnt td.name {background: url(../images/arrow_right.png) no-repeat 2px 50%;}
|
||||
tr.issue.idnt-1 td.subject, tr.project.idnt-1 td.name {padding-left: 24px; background-position: 8px 50%;}
|
||||
tr.issue.idnt-2 td.subject, tr.project.idnt-2 td.name {padding-left: 40px; background-position: 24px 50%;}
|
||||
tr.issue.idnt-3 td.subject, tr.project.idnt-3 td.name {padding-left: 56px; background-position: 40px 50%;}
|
||||
tr.issue.idnt-4 td.subject, tr.project.idnt-4 td.name {padding-left: 72px; background-position: 56px 50%;}
|
||||
tr.issue.idnt-5 td.subject, tr.project.idnt-5 td.name {padding-left: 88px; background-position: 72px 50%;}
|
||||
tr.issue.idnt-6 td.subject, tr.project.idnt-6 td.name {padding-left: 104px; background-position: 88px 50%;}
|
||||
tr.issue.idnt-7 td.subject, tr.project.idnt-7 td.name {padding-left: 120px; background-position: 104px 50%;}
|
||||
tr.issue.idnt-8 td.subject, tr.project.idnt-8 td.name {padding-left: 136px; background-position: 120px 50%;}
|
||||
tr.issue.idnt-9 td.subject, tr.project.idnt-9 td.name {padding-left: 152px; background-position: 136px 50%;}
|
||||
|
||||
table.issue-report {table-layout:fixed;}
|
||||
.issue-report-graph {width: 75%; margin: 2em 0;}
|
||||
|
||||
@@ -94,6 +94,59 @@ class ProjectsControllerTest < Redmine::ControllerTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_index_as_list_should_format_column_value
|
||||
get :index, :params => {
|
||||
:c => ['name', 'status', 'short_description', 'homepage', 'parent_id', 'identifier', 'is_public', 'created_on', 'project.cf_3'],
|
||||
:display_type => 'list'
|
||||
}
|
||||
assert_response :success
|
||||
|
||||
assert_select 'table.projects' do
|
||||
assert_select 'tr[id=?]', 'project-1' do
|
||||
assert_select 'td.name a[href=?]', '/projects/ecookbook', :text => 'eCookbook'
|
||||
assert_select 'td.status', :text => 'active'
|
||||
assert_select 'td.short_description', :text => 'Recipes management application'
|
||||
assert_select 'td.homepage a.external', :text => 'http://ecookbook.somenet.foo/'
|
||||
assert_select 'td.identifier', :text => 'ecookbook'
|
||||
assert_select 'td.is_public', :text => 'Yes'
|
||||
assert_select 'td.created_on', :text => '07/19/2006 05:13 PM'
|
||||
assert_select 'td.project_cf_3.list', :text => 'Stable'
|
||||
end
|
||||
assert_select 'tr[id=?]', 'project-4' do
|
||||
assert_select 'td.parent_id a[href=?]', '/projects/ecookbook', :text => 'eCookbook'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_index_as_list_should_show_my_favourite_projects
|
||||
@request.session[:user_id] = 1
|
||||
get :index, :params => {
|
||||
:display_type => 'list'
|
||||
}
|
||||
|
||||
assert_response :success
|
||||
assert_select 'tr[id=?] td.name span[class=?]', 'project-5', 'icon icon-user my-project'
|
||||
end
|
||||
|
||||
def test_index_as_list_should_indent_projects
|
||||
@request.session[:user_id] = 1
|
||||
get :index, :params => {
|
||||
:c => ['name', 'short_description'],
|
||||
:sort => 'parent_id:desc,lft:desc',
|
||||
:display_type => 'list'
|
||||
}
|
||||
assert_response :success
|
||||
|
||||
child_level1 = css_select('tr#project-5').map {|e| e.attr('class')}.first.split(' ')
|
||||
child_level2 = css_select('tr#project-6').map {|e| e.attr('class')}.first.split(' ')
|
||||
|
||||
assert_include 'idnt', child_level1
|
||||
assert_include 'idnt-1', child_level1
|
||||
|
||||
assert_include 'idnt', child_level2
|
||||
assert_include 'idnt-2', child_level2
|
||||
end
|
||||
|
||||
def test_autocomplete_js
|
||||
get :autocomplete, :params => {
|
||||
:format => 'js',
|
||||
|
||||
@@ -44,6 +44,17 @@ class ProjectQueryTest < ActiveSupport::TestCase
|
||||
values = query.available_filters['status'][:values]
|
||||
assert_equal ['active', 'closed'], values.map(&:first)
|
||||
assert_equal ['1', '5'], values.map(&:second)
|
||||
end
|
||||
|
||||
def test_default_columns
|
||||
q = ProjectQuery.new
|
||||
assert q.columns.any?
|
||||
assert q.inline_columns.any?
|
||||
assert q.block_columns.empty?
|
||||
end
|
||||
|
||||
def test_available_columns_should_include_project_custom_fields
|
||||
query = ProjectQuery.new
|
||||
assert_include :"project.cf_3", query.available_columns.map(&:name)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user