column registration

This commit is contained in:
ulferts
2024-02-16 09:25:24 +01:00
parent 11fc354d20
commit ec29bf8d33
71 changed files with 934 additions and 244 deletions
@@ -35,15 +35,14 @@ class Projects::ConfigureViewModalComponent < ApplicationComponent
options :query
def all_columns
@all_columns ||= Projects::TableComponent
.new(current_user: User.current)
.all_columns
.map { |c| { id: c.first, name: c.last[:caption] } }
@all_columns ||= query
.available_selects
.map { |c| { id: c.attribute, name: c.caption } }
end
def selected_columns
@selected_columns ||= query
.columns
.flat_map { |name| all_columns.detect { |column| column[:id].to_s == name } }
.selects
.map { |c| { id: c.attribute, name: c.caption } }
end
end
+13 -13
View File
@@ -43,17 +43,17 @@ module Projects
end
def column_value(column)
if column.to_s.start_with? 'cf_'
if custom_field_column?(column)
custom_field_column(column)
else
super
send(column.attribute)
end
end
def custom_field_column(column)
return nil unless user_can_view_project?
cf = custom_field(column)
cf = column.custom_field
custom_value = project.formatted_custom_value_for(cf)
if cf.field_format == 'text' && custom_value.present?
@@ -118,6 +118,7 @@ module Projects
def description
return nil unless user_can_view_project?
if project.description.present?
render OpenProject::Common::AttributeComponent.new("dialog-#{project.id}-description", I18n.t('activerecord.attributes.project.description'), project.description)
end
@@ -154,21 +155,16 @@ module Projects
end
def column_css_class(column)
"#{super} #{additional_css_class(column)}"
end
def custom_field(name)
table.project_custom_fields.fetch(name)
"#{column.attribute} #{additional_css_class(column)}"
end
def additional_css_class(column)
case column
when :name
if column.attribute == :name
"project--hierarchy #{project.archived? ? 'archived' : ''}"
when :status_explanation, :description
elsif [:status_explanation, :description].include?(column.attribute)
"project-long-text-container"
when /\Acf_/
cf = custom_field(column)
elsif custom_field_column?(column)
cf = column.custom_field
formattable = cf.field_format == 'text' ? ' project-long-text-container' : ''
"format-#{cf.field_format}#{formattable}"
end
@@ -256,5 +252,9 @@ module Projects
def user_can_view_project?
User.current.allowed_in_project?(:view_project, project)
end
def custom_field_column?(column)
column.is_a?(Queries::Projects::Selects::CustomField)
end
end
end
@@ -32,15 +32,15 @@ See COPYRIGHT and LICENSE files for more details.
<div class="generic-table--results-container">
<table class="generic-table" <%= table_id ? "id=\"#{table_id}\"".html_safe : '' %>>
<colgroup>
<% headers.each do |_name, _options| %>
<col <%= "opHighlightCol" unless _name == :hierarchy %> >
<% columns.each do |column| %>
<col <%= "opHighlightCol" unless column.attribute == :hierarchy %> >
<% end %>
<col opHighlightCol>
</colgroup>
<thead class="-sticky">
<thead class="-sticky">
<tr>
<% headers.each do |name, options| %>
<% if name == :hierarchy %>
<% columns.each do |column| %>
<% if column.attribute == :hierarchy %>
<th id="project-table--hierarchy-header">
<div class="generic-table--sort-header-outer generic-table--sort-header-outer_no-highlighting">
<div class="generic-table--sort-header">
@@ -52,22 +52,15 @@ See COPYRIGHT and LICENSE files for more details.
</div>
</div>
</th>
<% elsif sortable_column?(name) %>
<%= build_sort_header name,
options.merge(data:
{
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["query_id"]',
'params-from-query-all-anchors-value': "true"
}
) %>
<% elsif sortable_column?(column) %>
<%= build_sort_header column.attribute,
order_options(column) %>
<% else %>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= options[:caption] %>
<%= column.caption %>
</span>
</div>
</div>
+19 -59
View File
@@ -86,75 +86,35 @@ module Projects
def href_only_when_not_sort_lft
unless sorted_by_lft?
projects_path(sortBy: JSON::dump([['lft', 'asc']]))
projects_path(filters: params[:filters], sortBy: JSON::dump([['lft', 'asc']]))
end
end
def all_columns
@all_columns ||= [
hierarchy_column,
[:name, { builtin: true, caption: Project.human_attribute_name(:name) }],
[:project_status, { caption: Project.human_attribute_name(:status) }],
[:status_explanation, { caption: Project.human_attribute_name(:status_explanation) }],
[:public, { caption: Project.human_attribute_name(:public) }],
[:description, { caption: Project.human_attribute_name(:description) }],
*custom_field_columns,
*admin_columns
]
def order_options(select)
{
caption: select.caption,
data:
{
controller: "params-from-query",
'application-target': "dynamic",
'params-from-query-allowed-value': '["query_id"]',
'params-from-query-all-anchors-value': "true"
}
}
end
def headers
headers = query
.columns
.map do |name|
all_columns.detect { |column| column.first.to_s == name }
end
index = headers.index { |column| column.first == :name }
headers.insert(index, hierarchy_column)
headers
end
def sortable_column?(_column)
true
def sortable_column?(select)
query.known_order?(select.attribute)
end
def columns
@columns ||= headers.map(&:first)
end
@columns ||= begin
columns = query.selects.reject { |select| select.is_a?(Queries::Selects::NotExistingSelect) }
def admin_columns
return [] unless current_user.admin?
index = columns.index { |column| column.attribute == :name }
columns.insert(index, Queries::Projects::Selects::Default.new(:hierarchy)) if index
[
[:created_at, { caption: Project.human_attribute_name(:created_at) }],
[:latest_activity_at, { caption: Project.human_attribute_name(:latest_activity_at) }],
[:required_disk_space, { caption: I18n.t(:label_required_disk_storage) }]
]
end
def custom_field_columns
project_custom_fields.values.map do |custom_field|
[custom_field.column_name.to_sym, { caption: custom_field.name, custom_field: true }]
end
end
def hierarchy_column
[:hierarchy, { builtin: true }]
end
def project_custom_fields
@project_custom_fields ||= begin
fields =
if EnterpriseToken.allows_to?(:custom_fields_in_projects_list)
ProjectCustomField.visible(current_user).order(:position)
else
ProjectCustomField.none
end
fields
.index_by { |cf| cf.column_name.to_sym }
columns
end
end
@@ -29,7 +29,7 @@
module Queries::Projects::ProjectQueries
class BaseContract < ::ModelContract
attribute :name
attribute :columns
attribute :selects
attribute :filters
attribute :orders
@@ -42,11 +42,18 @@ module Queries::Projects::ProjectQueries
length: { maximum: 255 }
validate :user_is_current_user_and_logged_in
validate :name_select_included
def user_is_current_user_and_logged_in
unless user.logged? && user == model.user
errors.add :base, :error_unauthorized
end
end
def name_select_included
if model.selects.none? { |s| s.attribute == :name }
errors.add :selects, :name_not_included
end
end
end
end
@@ -30,6 +30,6 @@ module Queries::Projects::ProjectQueries
class LoadingContract < ::ModelContract
attribute :filters
attribute :orders
attribute :columns
attribute :selects
end
end
+1 -1
View File
@@ -33,7 +33,7 @@ module Queries
query_params[:filters] = parse_filters_from_params(params) if params[:filters].present?
query_params[:orders] = parse_orders_from_params(params) if params[:sortBy].present?
query_params[:columns] = parse_columns_from_params(params) if params[:columns].present?
query_params[:selects] = parse_columns_from_params(params) if params[:columns].present?
query_params
end
+13
View File
@@ -31,6 +31,7 @@ module Queries::BaseQuery
included do
include Queries::Filters::AvailableFilters
include Queries::Selects::AvailableSelects
include Queries::Orders::AvailableOrders
include Queries::GroupBys::AvailableGroupBys
include ActiveModel::Validations
@@ -82,6 +83,18 @@ module Queries::BaseQuery
self
end
def select(*select_values, add_not_existing: true)
select_values.each do |select_value|
select_column = select_for(select_value)
if !select_column.is_a?(::Queries::Selects::NotExistingSelect) || add_not_existing
selects << select_column
end
end
self
end
def order(hash)
hash.each do |attribute, direction|
order = order_for(attribute)
@@ -33,6 +33,10 @@ module Queries
(find_registered_order(key) || ::Queries::Orders::NotExistingOrder).new(key)
end
def known_order?(key)
find_registered_order(key).present?
end
private
def find_registered_order(key)
+7
View File
@@ -54,5 +54,12 @@ module Queries::Projects
order Orders::ProjectStatusOrder
order Orders::NameOrder
order Orders::TypeaheadOrder
select Selects::CreatedAt
select Selects::CustomField
select Selects::Default
select Selects::LatestActivityAt
select Selects::RequiredDiskSpace
select Selects::Status
end
end
+2 -2
View File
@@ -97,7 +97,7 @@ class Queries::Projects::Factory
def list_with(name)
Queries::Projects::ProjectQuery.new(name: I18n.t(name)) do |query|
query.order('lft' => 'asc')
query.columns = Setting.enabled_projects_columns
query.select(*(['name'] + Setting.enabled_projects_columns).uniq, add_not_existing: false)
yield query
end
@@ -128,7 +128,7 @@ class Queries::Projects::Factory
end
def new_query(source_query, params, user)
update_query(Queries::Projects::ProjectQuery.new(source_query.attributes.slice('filters', 'orders')),
update_query(Queries::Projects::ProjectQuery.new(source_query.attributes.slice('filters', 'orders', 'selects')),
params,
user)
end
@@ -34,6 +34,7 @@ class Queries::Projects::ProjectQuery < ApplicationRecord
serialize :filters, coder: Queries::Serialization::Filters.new(self)
serialize :orders, coder: Queries::Serialization::Orders.new(self)
serialize :selects, coder: Queries::Serialization::Selects.new(self)
def self.model
Project
@@ -0,0 +1,37 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::CreatedAt < Queries::Selects::Base
def self.key
:created_at
end
def self.available?
User.current.admin?
end
end
@@ -1,6 +1,6 @@
#-- copyright
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
@@ -24,32 +24,41 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
# ++
class Queries::WorkPackages::Columns::WorkPackageColumn < Queries::Columns::Base
attr_accessor :highlightable
alias_method :highlightable?, :highlightable
class Queries::Projects::Selects::CustomField < Queries::Selects::Base
validates :custom_field, presence: { message: I18n.t(:'activerecord.errors.messages.does_not_exist') }
def initialize(name, options = {})
super(name, options)
self.highlightable = !!options.fetch(:highlightable, false)
def self.key
/cf_(\d+)/
end
def self.available?
EnterpriseToken.allows_to?(:custom_fields_in_projects_list)
end
def self.all_available
return [] unless available?
ProjectCustomField
.visible
.pluck(:id)
.map { |cf_id| new("cf_#{cf_id}") }
end
def caption
WorkPackage.human_attribute_name(name)
custom_field.name
end
def self.scoped_column_sum(scope, select, group_by)
scope = scope
.except(:order, :select)
def custom_field
@custom_field ||= begin
ProjectCustomField
.visible
.find_by_id(self.class.key.match(attribute)[1])
end
end
if group_by
scope
.group(group_by)
.select("#{group_by} id", select)
else
scope
.select(select)
end
def scope
super.select(custom_field.order_statements)
end
end
@@ -0,0 +1,39 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::Default < Queries::Selects::Base
KEYS = %i[status_explanation hierarchy name public description].freeze
def self.key
Regexp.new(KEYS.join('|'))
end
def self.all_available
KEYS.map { new(_1) }
end
end
@@ -0,0 +1,37 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::LatestActivityAt < Queries::Selects::Base
def self.key
:latest_activity_at
end
def self.available?
User.current.admin?
end
end
@@ -0,0 +1,41 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::RequiredDiskSpace < Queries::Selects::Base
def self.key
:required_disk_space
end
def self.available?
User.current.admin?
end
def caption
I18n.t(:label_required_disk_storage)
end
end
@@ -0,0 +1,37 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::Status < Queries::Selects::Base
def self.key
:project_status
end
def caption
I18n.t(:'attributes.status')
end
end
+7 -7
View File
@@ -58,12 +58,12 @@ module Queries::Register
@group_bys[query] << group_by
end
def column(query, column)
@columns ||= Hash.new do |hash, column_key|
hash[column_key] = []
def select(query, select)
@selects ||= Hash.new do |hash, select_key|
hash[select_key] = []
end
@columns[query] << column
@selects[query] << select
end
def register(query, &)
@@ -73,7 +73,7 @@ module Queries::Register
attr_accessor :filters,
:excluded_filters,
:orders,
:columns,
:selects,
:group_bys
end
@@ -101,8 +101,8 @@ module Queries::Register
Queries::Register.group_by(query, group_by)
end
def column(column)
Queries::Register.column(query, column)
def select(select)
Queries::Register.select(query, select)
end
end
end
@@ -0,0 +1,56 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
module Queries
module Selects
module AvailableSelects
def select_for(key)
(find_available_select(key) || ::Queries::Selects::NotExistingSelect).new(key.to_sym)
end
def available_selects
registered_and_available
.flat_map(&:all_available)
end
private
def find_available_select(key)
registered_and_available.detect do |s|
s.key === key.to_sym
end
end
def registered_and_available
::Queries::Register
.selects[self.class]
.select(&:available?)
end
end
end
end
+62
View File
@@ -0,0 +1,62 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::Selects::Base
include ActiveModel::Validations
def self.key
raise NotImplementedError
end
def self.available?
true
end
def self.all_available
if available?
[new(key)]
else
[]
end
end
def caption
model = self.class.name.split('::')[1].singularize.constantize
model.human_attribute_name(attribute)
end
attr_accessor :attribute
def initialize(attribute)
self.attribute = attribute
end
def scope
model.select(attribute)
end
end
@@ -0,0 +1,49 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
module Queries
module Selects
class NotExistingSelect < Base
validate :always_false
def self.key
:inexistent
end
def caption
I18n.t('activerecord.errors.messages.does_not_exist')
end
private
def always_false
errors.add :base, I18n.t(:'activerecord.errors.messages.does_not_exist')
end
end
end
end
@@ -0,0 +1,55 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-2024 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# 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.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Serialization::Selects
include Queries::Selects::AvailableSelects
def load(serialized_selects)
return [] if serialized_selects.nil?
serialized_selects.map do |o|
select_for(o.to_sym)
end
end
def dump(selects)
selects.map(&:attribute)
end
def registered_and_available
::Queries::Register
.selects[klass]
.select(&:available?)
end
def initialize(klass)
@klass = klass
end
attr_reader :klass
end
+6 -6
View File
@@ -79,11 +79,11 @@ module Queries::WorkPackages
filter Filter::DurationFilter
exclude Filter::RelatableFilter
column Columns::PropertyColumn
column Columns::CustomFieldColumn
column Columns::RelationToTypeColumn
column Columns::RelationOfTypeColumn
column Columns::ManualSortingColumn
column Columns::TypeaheadColumn
select Selects::PropertySelect
select Selects::CustomFieldSelect
select Selects::RelationToTypeSelect
select Selects::RelationOfTypeSelect
select Selects::ManualSortingSelect
select Selects::TypeaheadSelect
end
end
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::CustomFieldColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class Queries::WorkPackages::Selects::CustomFieldSelect < Queries::WorkPackages::Selects::WorkPackageSelect
def initialize(custom_field)
super
@@ -86,7 +86,7 @@ class Queries::WorkPackages::Columns::CustomFieldColumn < Queries::WorkPackages:
select = summable_select_statement
->(query, grouped) {
Queries::WorkPackages::Columns::WorkPackageColumn
Queries::WorkPackages::Selects::WorkPackageSelect
.scoped_column_sum(summable_scope(query), select, grouped && query.group_by_statement)
}
else
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::ManualSortingColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class Queries::WorkPackages::Selects::ManualSortingSelect < Queries::WorkPackages::Selects::WorkPackageSelect
include ::Queries::WorkPackages::Common::ManualSorting
def initialize
@@ -26,14 +26,14 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::PropertyColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class Queries::WorkPackages::Selects::PropertySelect < Queries::WorkPackages::Selects::WorkPackageSelect
def caption
WorkPackage.human_attribute_name(name)
end
class_attribute :property_columns
class_attribute :property_selects
self.property_columns = {
self.property_selects = {
id: {
sortable: "#{WorkPackage.table_name}.id",
groupable: false
@@ -141,7 +141,7 @@ class Queries::WorkPackages::Columns::PropertyColumn < Queries::WorkPackages::Co
}
def self.instances(_context = nil)
property_columns.filter_map do |name, options|
property_selects.filter_map do |name, options|
next unless !options[:if] || options[:if].call
new(name, options.except(:if))
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::RelationOfTypeColumn < Queries::WorkPackages::Columns::RelationColumn
class Queries::WorkPackages::Selects::RelationOfTypeSelect < Queries::WorkPackages::Selects::RelationSelect
def initialize(type)
self.type = type
super(name)
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::RelationColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class Queries::WorkPackages::Selects::RelationSelect < Queries::WorkPackages::Selects::WorkPackageSelect
attr_accessor :type
def self.granted_by_enterprise_token
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::RelationToTypeColumn < Queries::WorkPackages::Columns::RelationColumn
class Queries::WorkPackages::Selects::RelationToTypeSelect < Queries::WorkPackages::Selects::RelationSelect
def initialize(type)
super
@@ -26,7 +26,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::WorkPackages::Columns::TypeaheadColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class Queries::WorkPackages::Selects::TypeaheadSelect < Queries::WorkPackages::Selects::WorkPackageSelect
def self.instances(_context = nil)
new :typeahead,
displayable: false,
@@ -26,7 +26,10 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::Columns::Base
class Queries::WorkPackages::Selects::WorkPackageSelect
attr_accessor :highlightable
alias_method :highlightable?, :highlightable
attr_reader :groupable,
:sortable,
:displayable
@@ -41,31 +44,10 @@ class Queries::Columns::Base
:summable_select,
:summable_work_packages_select
def initialize(name, options = {})
self.name = name
%i(sortable
sortable_join
displayable
groupable
summable
summable_select
summable_work_packages_select
association
null_handling
default_order).each do |attribute|
send(:"#{attribute}=", options[attribute])
end
end
def sortable_join_statement(_query)
sortable_join
end
def caption
raise NotImplementedError
end
def null_handling(_asc)
@null_handling
end
@@ -139,4 +121,41 @@ class Queries::Columns::Base
value
end
end
def initialize(name, options = {})
self.name = name
%i(sortable
sortable_join
displayable
groupable
summable
summable_select
summable_work_packages_select
association
null_handling
default_order).each do |attribute|
send(:"#{attribute}=", options[attribute])
end
self.highlightable = !!options.fetch(:highlightable, false)
end
def caption
WorkPackage.human_attribute_name(name)
end
def self.scoped_column_sum(scope, select, group_by)
scope = scope
.except(:order, :select)
if group_by
scope
.group(group_by)
.select("#{group_by} id", select)
else
scope
.select(select)
end
end
end
+2 -2
View File
@@ -34,11 +34,11 @@ module Query::ManualSorting
-> { order(position: :asc) }
def manually_sorted?
sort_criteria_columns.any? { |clz, _| clz.is_a?(::Queries::WorkPackages::Columns::ManualSortingColumn) }
sort_criteria_columns.any? { |clz, _| clz.is_a?(::Queries::WorkPackages::Selects::ManualSortingSelect) }
end
def self.manual_sorting_column
::Queries::WorkPackages::Columns::ManualSortingColumn.new
::Queries::WorkPackages::Selects::ManualSortingSelect.new
end
delegate :manual_sorting_column, to: :class
end
+1 -1
View File
@@ -76,7 +76,7 @@ module ::Query::Results::GroupBy
end
def transform_group_keys(groups)
if query.group_by_column.is_a?(Queries::WorkPackages::Columns::CustomFieldColumn)
if query.group_by_column.is_a?(Queries::WorkPackages::Selects::Queries::WorkPackages::Selects::CustomFieldSelect)
transform_custom_field_keys(groups)
else
transform_property_keys(groups)
@@ -44,7 +44,7 @@ module WorkPackage::Exports
def get_columns
query
.columns
.reject { |c| c.is_a?(Queries::WorkPackages::Columns::RelationColumn) }
.reject { |c| c.is_a?(Queries::WorkPackages::Selects::RelationSelect) }
end
def page
+1 -1
View File
@@ -244,7 +244,7 @@ module WorkPackage::PDFExport::Common
end
def text_column?(column)
column.is_a?(Queries::WorkPackages::Columns::CustomFieldColumn) &&
column.is_a?(Queries::WorkPackages::Selects::Queries::WorkPackages::Selects::CustomFieldSelect) &&
%w(string text).include?(column.custom_field.field_format)
end
@@ -77,7 +77,7 @@ module WorkPackage::PDFExport::OverviewTable
def transformed_sum_group
sums = query.results.all_group_sums
if query.group_by_column.is_a?(Queries::WorkPackages::Columns::CustomFieldColumn)
if query.group_by_column.is_a?(Queries::WorkPackages::Selects::Queries::WorkPackages::Selects::CustomFieldSelect)
transform_custom_field_keys(sums)
else
sums
+1 -1
View File
@@ -54,7 +54,7 @@ module CustomFields
def after_perform(call)
cf = call.result
if cf.is_a?(ProjectCustomField)
if cf.is_a?(ProjectCustomField) && EnterpriseToken.allows_to?(:custom_fields_in_projects_list)
add_cf_to_visible_columns(cf)
end
@@ -32,6 +32,7 @@ class Queries::Projects::ProjectQueries::SetAttributesService < BaseServices::Se
def set_attributes(params)
set_filters(params.delete(:filters))
set_order(params.delete(:orders))
set_select(params.delete(:selects))
super
end
@@ -40,7 +41,7 @@ class Queries::Projects::ProjectQueries::SetAttributesService < BaseServices::Se
set_default_user
set_default_filter
set_default_order
set_default_columns
set_default_selects
end
def set_default_user
@@ -61,10 +62,10 @@ class Queries::Projects::ProjectQueries::SetAttributesService < BaseServices::Se
model.where('active', '=', OpenProject::Database::DB_VALUE_TRUE)
end
def set_default_columns
return if model.columns.any?
def set_default_selects
return if model.selects.any?
model.columns = Setting.enabled_projects_columns
model.select(*default_columns, add_not_existing: false)
end
def set_filters(filters)
@@ -82,4 +83,15 @@ class Queries::Projects::ProjectQueries::SetAttributesService < BaseServices::Se
model.orders.clear
model.order(orders.to_h { |o| [o[:attribute], o[:direction]] })
end
def set_select(selects)
return unless selects
model.selects.clear
model.select(*selects)
end
def default_columns
(['name'] + Setting.enabled_projects_columns).uniq
end
end
+2 -1
View File
@@ -423,7 +423,8 @@ module Settings
},
enabled_projects_columns: {
default: %w[project_status public created_at latest_activity_at required_disk_space],
allowed: -> { Projects::TableComponent.new(current_user: User.admin.first).all_columns.map(&:first).map(&:to_s) }
# TODO: write a method on ProjectQuery to get all available column names
allowed: -> { Queries::Projects::ProjectQuery.available_select_keys.map(&:to_s) }
},
enabled_scm: {
default: %w[subversion git]
+5
View File
@@ -894,6 +894,11 @@ en:
enabled_modules:
dependency_missing: "The module '%{dependency}' needs to be enabled as well since the module '%{module}' depends on it."
format: "%{message}"
queries/projects/project_query:
attributes:
selects:
name_not_included: "The 'Name' column needs to be included"
format: "%{message}"
query:
attributes:
project:
@@ -0,0 +1,5 @@
class RenameColumnsOnProjectQueries < ActiveRecord::Migration[7.1]
def change
rename_column :project_queries, :columns, :selects
end
end
@@ -33,10 +33,10 @@ module API
module QueryColumnsFactory
def self.representer(column)
case column
when ::Queries::WorkPackages::Columns::RelationToTypeColumn
::API::V3::Queries::Columns::QueryRelationToTypeColumnRepresenter
when ::Queries::WorkPackages::Columns::RelationOfTypeColumn
::API::V3::Queries::Columns::QueryRelationOfTypeColumnRepresenter
when ::Queries::WorkPackages::Selects::RelationToTypeSelect
::API::V3::Queries::Columns::QueryQueries::WorkPackages::Selects::RelationToTypeSelectRepresenter
when ::Queries::WorkPackages::Selects::RelationOfTypeSelect
::API::V3::Queries::Columns::QueryQueries::WorkPackages::Selects::RelationOfTypeSelectRepresenter
else
::API::V3::Queries::Columns::QueryPropertyColumnRepresenter
end
@@ -30,7 +30,7 @@ module API
module V3
module Queries
module Columns
class QueryRelationOfTypeColumnRepresenter < QueryColumnRepresenter
class QueryQueries::WorkPackages::Selects::RelationOfTypeSelectRepresenter < QueryColumnRepresenter
def _type
'QueryColumn::RelationOfType'
end
@@ -30,7 +30,7 @@ module API
module V3
module Queries
module Columns
class QueryRelationToTypeColumnRepresenter < QueryColumnRepresenter
class QueryQueries::WorkPackages::Selects::RelationToTypeSelectRepresenter < QueryColumnRepresenter
link :type do
{
href: api_v3_paths.type(represented.type.id),
@@ -206,7 +206,7 @@ module OpenProject::Backlogs
::Queries::Register.register(::Query) do
filter OpenProject::Backlogs::WorkPackageFilter
column OpenProject::Backlogs::QueryBacklogsColumn
select OpenProject::Backlogs::QueryBacklogsSelect
end
end
end
@@ -27,10 +27,10 @@
#++
module OpenProject::Backlogs
class QueryBacklogsColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class_attribute :backlogs_columns
class QueryBacklogsSelect < Queries::WorkPackages::Selects::WorkPackageSelect
class_attribute :backlogs_selects
self.backlogs_columns = {
self.backlogs_selects = {
story_points: {
sortable: "#{WorkPackage.table_name}.story_points",
summable: true
@@ -45,7 +45,7 @@ module OpenProject::Backlogs
def self.instances(context = nil)
return [] if context && !context.backlogs_enabled?
backlogs_columns.map do |name, options|
backlogs_selects.map do |name, options|
new(name, options)
end
end
@@ -26,8 +26,8 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module ::Bim::Queries::WorkPackages::Columns
class BcfThumbnailColumn < Queries::WorkPackages::Columns::WorkPackageColumn
module ::Bim::Queries::WorkPackages::Selects
class BcfThumbnailSelect < Queries::WorkPackages::Selects::WorkPackageSelect
def caption
I18n.t('attributes.bcf_thumbnail')
end
+1 -1
View File
@@ -227,7 +227,7 @@ module OpenProject::Bim
::Queries::Register.register(::Query) do
filter ::Bim::Queries::WorkPackages::Filter::BcfIssueAssociatedFilter
column ::Bim::Queries::WorkPackages::Columns::BcfThumbnailColumn
select ::Bim::Queries::WorkPackages::Selects::BcfThumbnailSelect
end
::API::Root.class_eval do
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root + 'spec/models/queries/work_packages/columns/shared_query_column_specs'
require Rails.root.join("spec/models/queries/work_packages/selects/shared_query_select_specs").to_s
RSpec.describe Bim::Queries::WorkPackages::Columns::BcfThumbnailColumn do
RSpec.describe Bim::Queries::WorkPackages::Selects::BcfThumbnailSelect do
let(:instance) { described_class.new(:query_column) }
it_behaves_like 'query column'
+1 -1
View File
@@ -301,7 +301,7 @@ module Costs
end
::Queries::Register.register(::Query) do
column Costs::QueryCurrencyColumn
select Costs::QueryCurrencySelect
end
end
end
@@ -27,7 +27,7 @@
#++
module Costs
class QueryCurrencyColumn < Queries::WorkPackages::Columns::WorkPackageColumn
class QueryCurrencySelect < Queries::WorkPackages::Selects::WorkPackageSelect
include ActionView::Helpers::NumberHelper
alias :super_value :value
@@ -43,9 +43,9 @@ module Costs
super_value work_package
end
class_attribute :currency_columns
class_attribute :currenty_selects
self.currency_columns = {
self.currenty_selects = {
budget: {},
material_costs: {
summable: ->(query, grouped) {
@@ -54,7 +54,7 @@ module Costs
.add_to_work_package_collection(WorkPackage.where(id: query.results.work_packages))
.except(:order, :select)
Queries::WorkPackages::Columns::WorkPackageColumn
Queries::WorkPackages::Selects::WorkPackageSelect
.scoped_column_sum(scope,
"COALESCE(ROUND(SUM(cost_entries_sum), 2)::FLOAT, 0.0) material_costs",
grouped && query.group_by_statement)
@@ -67,7 +67,7 @@ module Costs
.add_to_work_package_collection(WorkPackage.where(id: query.results.work_packages))
.except(:order, :select)
Queries::WorkPackages::Columns::WorkPackageColumn
Queries::WorkPackages::Selects::WorkPackageSelect
.scoped_column_sum(scope,
"COALESCE(ROUND(SUM(time_entries_sum), 2)::FLOAT, 0.0) labor_costs",
grouped && query.group_by_statement)
@@ -83,7 +83,7 @@ module Costs
def self.instances(context = nil)
return [] if context && !context.costs_enabled?
currency_columns.map do |name, options|
currenty_selects.map do |name, options|
new(name, options)
end
end
@@ -28,7 +28,7 @@
require 'spec_helper'
RSpec.describe Costs::QueryCurrencyColumn, type: :model do
RSpec.describe Costs::QueryCurrencySelect, type: :model do
let(:project) do
build_stubbed(:project).tap do |p|
allow(p)
@@ -38,6 +38,8 @@ RSpec.describe Queries::Projects::ProjectQueries::CreateContract do
query.change_by_system do
query.user = query_user
end
query.select(*query_selects)
end
end
@@ -44,7 +44,7 @@ RSpec.describe Queries::Projects::ProjectQueries::LoadingContract do
end
query.order(query_orders)
query.columns = query_columns
query.select(*query_columns)
end
end
let(:contract) { described_class.new(query, current_user) }
@@ -35,6 +35,7 @@ RSpec.shared_examples_for 'project queries contract' do
let(:current_user) { build_stubbed(:user) }
let(:query_name) { "Query name" }
let(:query_user) { current_user }
let(:query_selects) { %i[name project_status created_at] }
describe 'validation' do
it_behaves_like 'contract is valid'
@@ -62,5 +63,11 @@ RSpec.shared_examples_for 'project queries contract' do
it_behaves_like 'contract is invalid', base: :error_unauthorized
end
context 'if the selects do not include the name column' do
let(:query_selects) { %i[project_status created_at] }
it_behaves_like 'contract is invalid', selects: :name_not_included
end
end
end
@@ -265,7 +265,7 @@ RSpec.describe Queries::ParamsParser, type: :model do
end
it 'returns an invalid sort order' do
expect(subject[:columns])
expect(subject[:selects])
.to eql %w[name cf_1 project_status]
end
end
@@ -32,7 +32,7 @@ RSpec.describe API::V3::Queries::Columns::QueryRelationOfTypeColumnRepresenter d
include API::V3::Utilities::PathHelper
let(:type) { { name: :label_relates_to, sym_name: :label_relates_to, order: 1, sym: :relation1 } }
let(:column) { Queries::WorkPackages::Columns::RelationOfTypeColumn.new(type) }
let(:column) { Queries::WorkPackages::Selects::RelationOfTypeSelect.new(type) }
let(:representer) { described_class.new(column) }
subject { representer.to_json }
@@ -32,7 +32,7 @@ RSpec.describe API::V3::Queries::Columns::QueryRelationToTypeColumnRepresenter d
include API::V3::Utilities::PathHelper
let(:type) { build_stubbed(:type) }
let(:column) { Queries::WorkPackages::Columns::RelationToTypeColumn.new(type) }
let(:column) { Queries::WorkPackages::Selects::RelationToTypeSelect.new(type) }
let(:representer) { described_class.new(column) }
subject { representer.to_json }
@@ -353,11 +353,11 @@ RSpec.describe API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:form_embedded) { true }
let(:type) { build_stubbed(:type) }
let(:available_values) do
[Queries::WorkPackages::Columns::PropertyColumn.new(:bogus1),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus2),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus3),
Queries::WorkPackages::Columns::RelationToTypeColumn.new(type),
Queries::WorkPackages::Columns::RelationOfTypeColumn.new(
[Queries::WorkPackages::Selects::PropertySelect.new(:bogus1),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus2),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus3),
Queries::WorkPackages::Selects::RelationToTypeSelect.new(type),
Queries::WorkPackages::Selects::RelationOfTypeSelect.new(
name: :label_relates_to,
sym: :relation1,
sym_name: :label_relates_to
@@ -404,8 +404,8 @@ RSpec.describe API::V3::Queries::Schemas::QuerySchemaRepresenter do
let(:form_embedded) { true }
let(:type) { build_stubbed(:type) }
let(:available_values) do
[Queries::WorkPackages::Columns::PropertyColumn.new(:bogus1, highlightable: true),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus2, highlightable: true)]
[Queries::WorkPackages::Selects::PropertySelect.new(:bogus1, highlightable: true),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus2, highlightable: true)]
end
let(:available_values_method) { :displayable_columns }
@@ -472,9 +472,9 @@ RSpec.describe API::V3::Queries::Schemas::QuerySchemaRepresenter do
it_behaves_like 'has a collection of allowed values' do
let(:available_values) do
[Queries::WorkPackages::Columns::PropertyColumn.new(:bogus1),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus2),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus3)]
[Queries::WorkPackages::Selects::PropertySelect.new(:bogus1),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus2),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus3)]
end
let(:available_values_method) { :groupable_columns }
let(:expected_hrefs) do
@@ -511,9 +511,9 @@ RSpec.describe API::V3::Queries::Schemas::QuerySchemaRepresenter do
end
let(:available_values) do
[Queries::WorkPackages::Columns::PropertyColumn.new(:bogus1),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus2),
Queries::WorkPackages::Columns::PropertyColumn.new(:bogus3)]
[Queries::WorkPackages::Selects::PropertySelect.new(:bogus1),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus2),
Queries::WorkPackages::Selects::PropertySelect.new(:bogus3)]
end
let(:available_values_method) { :sortable_columns }
@@ -33,7 +33,7 @@ RSpec.describe API::V3::Queries::SortBys::QuerySortByRepresenter do
let(:column_name) { 'status' }
let(:direction) { 'desc' }
let(:column) { Queries::WorkPackages::Columns::PropertyColumn.new(column_name) }
let(:column) { Queries::WorkPackages::Selects::PropertySelect.new(column_name) }
let(:decorator) { API::V3::Queries::SortBys::SortByDecorator.new(column, direction) }
let(:representer) do
described_class
+175 -3
View File
@@ -29,8 +29,7 @@
require 'spec_helper'
require 'services/base_services/behaves_like_create_service'
RSpec.describe Queries::Projects::Factory do
let(:current_user) { build_stubbed(:user) }
RSpec.describe Queries::Projects::Factory, with_settings: { enabled_projects_columns: %w[name project_status active] } do
let!(:query_finder) do
scope = instance_double(ActiveRecord::Relation)
@@ -48,12 +47,15 @@ RSpec.describe Queries::Projects::Factory do
build_stubbed(:project_query) do |query|
query.order(id: :asc)
query.where(:project_status, '=', [Project.status_codes[:on_track].to_s])
query.select(:project_status, :name, :created_at)
end
end
let(:id) { nil }
let(:params) { {} }
current_user { build_stubbed(:user) }
describe '.find' do
subject(:find) { described_class.find(id, params:, user: current_user) }
@@ -77,6 +79,22 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'without id and with ee and admin privileges',
with_ee: %i[custom_fields_in_projects_list],
with_settings: { enabled_projects_columns: %w[name created_at cf_1] } do
current_user { build_stubbed(:admin) }
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'active\' id' do
@@ -101,6 +119,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'my\' id' do
@@ -125,6 +148,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'archived\' id' do
@@ -149,6 +177,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'on_track\' id' do
@@ -173,6 +206,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'off_track\' id' do
@@ -197,6 +235,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'at_risk\' id' do
@@ -221,6 +264,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with an integer id for which the user has a query' do
@@ -267,7 +315,8 @@ RSpec.describe Queries::Projects::Factory do
attribute: 'name',
direction: 'desc'
}
]
],
selects: %w[created_at name]
}
end
@@ -290,6 +339,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['id', :asc], ['name', :desc]])
end
it 'has the selects' do
expect(find.selects.map(&:attribute))
.to eq(%i[created_at name])
end
end
context 'with the \'active\' id and with order params' do
@@ -328,6 +382,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['id', :asc], ['name', :desc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'active\' id and with filter params' do
@@ -368,6 +427,45 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([%i[lft asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
context 'with the \'active\' id and with select params' do
let(:id) { nil }
let(:params) do
{
selects: %w[created_at project_status]
}
end
it 'returns a project query' do
expect(find)
.to be_a(Queries::Projects::ProjectQuery)
end
it 'has no name' do
expect(find.name)
.to be_nil
end
it 'has the filters of the default \'active\' query applied' do
expect(find.filters.map { |filter| [filter.field, filter.operator, filter.values] })
.to eq([[:active, '=', ['t']]])
end
it 'has the orders of the default \'active\' query applied' do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([%i[lft asc]])
end
it 'has the selects overwritten' do
expect(find.selects.map(&:attribute))
.to eq(%i[created_at project_status])
end
end
context 'with an integer id for which the user has a query and with filter params' do
@@ -408,6 +506,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq(persisted_query.orders.map { |order| [order.attribute, order.direction] })
end
it 'has the selects of the persisted query' do
expect(find.selects.map(&:attribute))
.to eq(persisted_query.selects.map(&:attribute))
end
end
context 'with an integer id for which the user has a query and with order params' do
@@ -446,6 +549,45 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq [["id", :asc], ["name", :desc]]
end
it 'has the selects of the persisted query' do
expect(find.selects.map(&:attribute))
.to eq(persisted_query.selects.map(&:attribute))
end
end
context 'with an integer id for which the user has a query and with select params' do
let(:id) { 42 }
let(:params) do
{
selects: %w[created_at project_status]
}
end
it 'returns a project query' do
expect(find)
.to be_a(Queries::Projects::ProjectQuery)
end
it 'keeps the name' do
expect(find.name)
.to eql(persisted_query.name)
end
it 'has the filters of the persisted query' do
expect(find.filters.map { |filter| [filter.field, filter.operator, filter.values] })
.to eq(persisted_query.filters.map { |filter| [filter.field, filter.operator, filter.values] })
end
it 'has the orders of the persisted query' do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq(persisted_query.orders.map { |order| [order.attribute, order.direction] })
end
it 'has the selects specified by the params' do
expect(find.selects.map(&:attribute))
.to eq(%i[created_at project_status])
end
end
context 'with an integer id for which the user does not have a query and with params' do
@@ -507,6 +649,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
describe '.static_query_my' do
@@ -531,6 +678,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
describe '.static_query_archived' do
@@ -555,6 +707,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
describe '.static_query_status_on_track' do
@@ -579,6 +736,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
describe '.static_query_status_off_track' do
@@ -603,6 +765,11 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
describe '.static_query_status_at_risk' do
@@ -627,5 +794,10 @@ RSpec.describe Queries::Projects::Factory do
expect(find.orders.map { |order| [order.attribute, order.direction] })
.to eq([['lft', :asc]])
end
it 'has the enabled project columns columns as selects' do
expect(find.selects.map(&:attribute))
.to eq(Setting.enabled_projects_columns.map(&:to_sym))
end
end
end
@@ -31,13 +31,14 @@ require 'spec_helper'
RSpec.describe Queries::Projects::ProjectQuery do
let(:instance) { described_class.new }
shared_let(:current_user) { create(:user) }
shared_let(:user) { create(:user) }
shared_let(:admin) { create(:admin) }
context 'when persisting' do
let(:properties) do
{
name: 'some name',
user: current_user
user:
}
end
@@ -76,5 +77,51 @@ RSpec.describe Queries::Projects::ProjectQuery do
expect(described_class.find(instance.id).orders.map { |o| { o.attribute => o.direction } })
.to eql [{ id: :desc }]
end
it 'takes selects' do
instance = described_class.new(**properties)
instance.select(:name, :public)
instance.save!
expect(described_class.find(instance.id).selects.map(&:attribute))
.to eql %i[name public]
end
end
describe '.available_selects' do
current_user { user }
it 'lists registered selects' do
expect(instance.available_selects)
.to contain_exactly(Queries::Projects::Selects::Default)
end
context 'with the user being admin' do
current_user { admin }
it 'includes admin columns' do
expect(instance.available_selects)
.to contain_exactly(Queries::Projects::Selects::Default,
Queries::Projects::Selects::Admin)
end
end
context 'with an enterprise token',
with_ee: %i[custom_fields_in_projects_list] do
it 'includes custom field columns' do
expect(instance.available_selects)
.to contain_exactly(Queries::Projects::Selects::Default,
Queries::Projects::Selects::CustomField)
end
end
end
describe '.available_select_names' do
it 'lists registered selects' do
expect(instance.available_select_keys)
.to include(:name, :public)
end
end
end
@@ -27,9 +27,9 @@
#++
require 'spec_helper'
require_relative 'shared_query_column_specs'
require_relative 'shared_query_select_specs'
RSpec.describe Queries::WorkPackages::Columns::CustomFieldColumn do
RSpec.describe Queries::WorkPackages::Selects::CustomFieldSelect do
let(:project) { build_stubbed(:project) }
let(:custom_field) { build_stubbed(:string_wp_custom_field) }
let(:instance) { described_class.new(custom_field) }
@@ -27,9 +27,9 @@
#++
require 'spec_helper'
require_relative 'shared_query_column_specs'
require_relative 'shared_query_select_specs'
RSpec.describe Queries::WorkPackages::Columns::PropertyColumn do
RSpec.describe Queries::WorkPackages::Selects::PropertySelect do
let(:instance) { described_class.new(:query_column) }
it_behaves_like 'query column'
@@ -27,9 +27,9 @@
#++
require 'spec_helper'
require_relative 'shared_query_column_specs'
require_relative 'shared_query_select_specs'
RSpec.describe Queries::WorkPackages::Columns::RelationOfTypeColumn do
RSpec.describe Queries::WorkPackages::Selects::RelationOfTypeSelect do
let(:project) { build_stubbed(:project) }
let(:type) { build_stubbed(:type) }
let(:instance) { described_class.new(type) }
@@ -27,9 +27,9 @@
#++
require 'spec_helper'
require_relative 'shared_query_column_specs'
require_relative 'shared_query_select_specs'
RSpec.describe Queries::WorkPackages::Columns::RelationToTypeColumn do
RSpec.describe Queries::WorkPackages::Selects::RelationToTypeSelect do
let(:project) { build_stubbed(:project) }
let(:type) { build_stubbed(:type) }
let(:instance) { described_class.new(type) }
@@ -28,7 +28,7 @@
require 'spec_helper'
RSpec.describe Queries::WorkPackages::Columns::WorkPackageColumn do
RSpec.describe Queries::WorkPackages::Selects::WorkPackageSelect do
it "allows to be constructed with attribute highlightable" do
expect(described_class.new('foo', highlightable: true).highlightable?).to be(true)
end
+1 -1
View File
@@ -262,7 +262,7 @@ RSpec.describe Query,
no_highlight: {}
}
allow(Queries::WorkPackages::Columns::PropertyColumn).to receive(:property_columns)
allow(Queries::WorkPackages::Selects::PropertySelect).to receive(:property_columns)
.and_return(available_columns)
expect(query.available_highlighting_columns.map(&:name)).to eq(%i{highlightable1 highlightable2})
@@ -158,11 +158,25 @@ RSpec.describe Queries::Projects::ProjectQueries::SetAttributesService, type: :m
.to eql [[:active, '=', %w[t]]]
end
it 'assigns default columns' do
it 'assigns default selects including those for admin and ee if allowed',
with_ee: %i[custom_fields_in_projects_list],
with_settings: { enabled_projects_columns: %w[name created_at cf_1] } do
allow(User.current)
.to receive(:admin?)
.and_return(true)
subject
expect(model_instance.columns)
.to eql Setting.enabled_projects_columns
expect(model_instance.selects.map(&:attribute))
.to eql Setting.enabled_projects_columns.map(&:to_sym)
end
it 'assigns default selects excluding those for admin and ee if not allowed',
with_settings: { enabled_projects_columns: %w[name created_at cf_1] } do
subject
expect(model_instance.selects.map(&:attribute))
.to eql [:name]
end
end
@@ -229,23 +243,23 @@ RSpec.describe Queries::Projects::ProjectQueries::SetAttributesService, type: :m
end
end
context 'with the query already having columns and with column params' do
context 'with the query already having selects and with selects params' do
let(:model_instance) do
Queries::Projects::ProjectQuery.new.tap do |query|
query.columns = %w[id name]
query.select(:id, :name)
end
end
let(:params) do
{
columns: %w[project_status created_at]
selects: %w[project_status created_at]
}
end
it 'assigns the columns param' do
it 'assigns the select param' do
subject
expect(model_instance.columns)
.to eql %w[project_status created_at]
expect(model_instance.selects.map(&:attribute))
.to eql %i[project_status created_at]
end
end