diff --git a/app/components/projects/configure_view_modal_component.rb b/app/components/projects/configure_view_modal_component.rb
index 928e3f71d1b..fee9a76dbb0 100644
--- a/app/components/projects/configure_view_modal_component.rb
+++ b/app/components/projects/configure_view_modal_component.rb
@@ -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
diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb
index 597ec588aa7..0e3d444b51e 100644
--- a/app/components/projects/row_component.rb
+++ b/app/components/projects/row_component.rb
@@ -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
diff --git a/app/components/projects/table_component.html.erb b/app/components/projects/table_component.html.erb
index 5c9f81d7037..612f99123f0 100644
--- a/app/components/projects/table_component.html.erb
+++ b/app/components/projects/table_component.html.erb
@@ -32,15 +32,15 @@ See COPYRIGHT and LICENSE files for more details.
>
- <% headers.each do |_name, _options| %>
- >
+ <% columns.each do |column| %>
+ >
<% end %>
-
+
- <% headers.each do |name, options| %>
- <% if name == :hierarchy %>
+ <% columns.each do |column| %>
+ <% if column.attribute == :hierarchy %>
- <% 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 %>
|
diff --git a/app/components/projects/table_component.rb b/app/components/projects/table_component.rb
index 3acbc757316..d62db03a58f 100644
--- a/app/components/projects/table_component.rb
+++ b/app/components/projects/table_component.rb
@@ -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
diff --git a/app/contracts/queries/projects/project_queries/base_contract.rb b/app/contracts/queries/projects/project_queries/base_contract.rb
index 1d007b22dc6..4577e3e82ac 100644
--- a/app/contracts/queries/projects/project_queries/base_contract.rb
+++ b/app/contracts/queries/projects/project_queries/base_contract.rb
@@ -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
diff --git a/app/contracts/queries/projects/project_queries/loading_contract.rb b/app/contracts/queries/projects/project_queries/loading_contract.rb
index 7b0caa6730a..5c4eff0aa06 100644
--- a/app/contracts/queries/projects/project_queries/loading_contract.rb
+++ b/app/contracts/queries/projects/project_queries/loading_contract.rb
@@ -30,6 +30,6 @@ module Queries::Projects::ProjectQueries
class LoadingContract < ::ModelContract
attribute :filters
attribute :orders
- attribute :columns
+ attribute :selects
end
end
diff --git a/app/controllers/queries/params_parser.rb b/app/controllers/queries/params_parser.rb
index e1971a1b466..301d2dd15de 100644
--- a/app/controllers/queries/params_parser.rb
+++ b/app/controllers/queries/params_parser.rb
@@ -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
diff --git a/app/models/queries/base_query.rb b/app/models/queries/base_query.rb
index bc2d1befa9f..d393e11d764 100644
--- a/app/models/queries/base_query.rb
+++ b/app/models/queries/base_query.rb
@@ -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)
diff --git a/app/models/queries/orders/available_orders.rb b/app/models/queries/orders/available_orders.rb
index e505d416748..9528a533dbb 100644
--- a/app/models/queries/orders/available_orders.rb
+++ b/app/models/queries/orders/available_orders.rb
@@ -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)
diff --git a/app/models/queries/projects.rb b/app/models/queries/projects.rb
index e6f77e488bc..1e25ddb19eb 100644
--- a/app/models/queries/projects.rb
+++ b/app/models/queries/projects.rb
@@ -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
diff --git a/app/models/queries/projects/factory.rb b/app/models/queries/projects/factory.rb
index bd5721326ac..af4fdf1eebd 100644
--- a/app/models/queries/projects/factory.rb
+++ b/app/models/queries/projects/factory.rb
@@ -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
diff --git a/app/models/queries/projects/project_query.rb b/app/models/queries/projects/project_query.rb
index 3bf50a435f7..6a44a497199 100644
--- a/app/models/queries/projects/project_query.rb
+++ b/app/models/queries/projects/project_query.rb
@@ -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
diff --git a/app/models/queries/projects/selects/created_at.rb b/app/models/queries/projects/selects/created_at.rb
new file mode 100644
index 00000000000..db06809ffd7
--- /dev/null
+++ b/app/models/queries/projects/selects/created_at.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/work_package_column.rb b/app/models/queries/projects/selects/custom_field.rb
similarity index 58%
rename from app/models/queries/work_packages/columns/work_package_column.rb
rename to app/models/queries/projects/selects/custom_field.rb
index cf754f82811..4e10c2a6ca0 100644
--- a/app/models/queries/work_packages/columns/work_package_column.rb
+++ b/app/models/queries/projects/selects/custom_field.rb
@@ -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
diff --git a/app/models/queries/projects/selects/default.rb b/app/models/queries/projects/selects/default.rb
new file mode 100644
index 00000000000..9a0a93542d4
--- /dev/null
+++ b/app/models/queries/projects/selects/default.rb
@@ -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
diff --git a/app/models/queries/projects/selects/latest_activity_at.rb b/app/models/queries/projects/selects/latest_activity_at.rb
new file mode 100644
index 00000000000..e82fdd4c133
--- /dev/null
+++ b/app/models/queries/projects/selects/latest_activity_at.rb
@@ -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
diff --git a/app/models/queries/projects/selects/required_disk_space.rb b/app/models/queries/projects/selects/required_disk_space.rb
new file mode 100644
index 00000000000..bc433247af0
--- /dev/null
+++ b/app/models/queries/projects/selects/required_disk_space.rb
@@ -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
diff --git a/app/models/queries/projects/selects/status.rb b/app/models/queries/projects/selects/status.rb
new file mode 100644
index 00000000000..cbd85b3f288
--- /dev/null
+++ b/app/models/queries/projects/selects/status.rb
@@ -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
diff --git a/app/models/queries/register.rb b/app/models/queries/register.rb
index 43d1f0ae6bb..fa1d16e075b 100644
--- a/app/models/queries/register.rb
+++ b/app/models/queries/register.rb
@@ -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
diff --git a/app/models/queries/selects/available_selects.rb b/app/models/queries/selects/available_selects.rb
new file mode 100644
index 00000000000..54224313581
--- /dev/null
+++ b/app/models/queries/selects/available_selects.rb
@@ -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
diff --git a/app/models/queries/selects/base.rb b/app/models/queries/selects/base.rb
new file mode 100644
index 00000000000..0776ac5cf46
--- /dev/null
+++ b/app/models/queries/selects/base.rb
@@ -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
diff --git a/app/models/queries/selects/not_existing_select.rb b/app/models/queries/selects/not_existing_select.rb
new file mode 100644
index 00000000000..6fa44ff4b78
--- /dev/null
+++ b/app/models/queries/selects/not_existing_select.rb
@@ -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
diff --git a/app/models/queries/serialization/selects.rb b/app/models/queries/serialization/selects.rb
new file mode 100644
index 00000000000..90da3e105fa
--- /dev/null
+++ b/app/models/queries/serialization/selects.rb
@@ -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
diff --git a/app/models/queries/work_packages.rb b/app/models/queries/work_packages.rb
index 2cf6c651df8..ec8363a8121 100644
--- a/app/models/queries/work_packages.rb
+++ b/app/models/queries/work_packages.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/custom_field_column.rb b/app/models/queries/work_packages/selects/custom_field_select.rb
similarity index 94%
rename from app/models/queries/work_packages/columns/custom_field_column.rb
rename to app/models/queries/work_packages/selects/custom_field_select.rb
index 96108eb855a..6f10eedefc5 100644
--- a/app/models/queries/work_packages/columns/custom_field_column.rb
+++ b/app/models/queries/work_packages/selects/custom_field_select.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/manual_sorting_column.rb b/app/models/queries/work_packages/selects/manual_sorting_select.rb
similarity index 93%
rename from app/models/queries/work_packages/columns/manual_sorting_column.rb
rename to app/models/queries/work_packages/selects/manual_sorting_select.rb
index 33cae657a48..6f1d7560a7a 100644
--- a/app/models/queries/work_packages/columns/manual_sorting_column.rb
+++ b/app/models/queries/work_packages/selects/manual_sorting_select.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/property_column.rb b/app/models/queries/work_packages/selects/property_select.rb
similarity index 95%
rename from app/models/queries/work_packages/columns/property_column.rb
rename to app/models/queries/work_packages/selects/property_select.rb
index 93fc1c8e1d9..a1b0bbd21b5 100644
--- a/app/models/queries/work_packages/columns/property_column.rb
+++ b/app/models/queries/work_packages/selects/property_select.rb
@@ -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))
diff --git a/app/models/queries/work_packages/columns/relation_of_type_column.rb b/app/models/queries/work_packages/selects/relation_of_type_select.rb
similarity index 93%
rename from app/models/queries/work_packages/columns/relation_of_type_column.rb
rename to app/models/queries/work_packages/selects/relation_of_type_select.rb
index 102d1a8c25c..b7e6b432254 100644
--- a/app/models/queries/work_packages/columns/relation_of_type_column.rb
+++ b/app/models/queries/work_packages/selects/relation_of_type_select.rb
@@ -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)
diff --git a/app/models/queries/work_packages/columns/relation_column.rb b/app/models/queries/work_packages/selects/relation_select.rb
similarity index 93%
rename from app/models/queries/work_packages/columns/relation_column.rb
rename to app/models/queries/work_packages/selects/relation_select.rb
index 72385301c0e..ddada8dfa64 100644
--- a/app/models/queries/work_packages/columns/relation_column.rb
+++ b/app/models/queries/work_packages/selects/relation_select.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/relation_to_type_column.rb b/app/models/queries/work_packages/selects/relation_to_type_select.rb
similarity index 93%
rename from app/models/queries/work_packages/columns/relation_to_type_column.rb
rename to app/models/queries/work_packages/selects/relation_to_type_select.rb
index 60a714f92cd..c2cc95651b5 100644
--- a/app/models/queries/work_packages/columns/relation_to_type_column.rb
+++ b/app/models/queries/work_packages/selects/relation_to_type_select.rb
@@ -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
diff --git a/app/models/queries/work_packages/columns/typeahead_column.rb b/app/models/queries/work_packages/selects/typeahead_select.rb
similarity index 94%
rename from app/models/queries/work_packages/columns/typeahead_column.rb
rename to app/models/queries/work_packages/selects/typeahead_select.rb
index 895df19ad50..a9efbe8b5dc 100644
--- a/app/models/queries/work_packages/columns/typeahead_column.rb
+++ b/app/models/queries/work_packages/selects/typeahead_select.rb
@@ -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,
diff --git a/app/models/queries/columns/base.rb b/app/models/queries/work_packages/selects/work_package_select.rb
similarity index 86%
rename from app/models/queries/columns/base.rb
rename to app/models/queries/work_packages/selects/work_package_select.rb
index e56616ea9c4..4f0412fa9d5 100644
--- a/app/models/queries/columns/base.rb
+++ b/app/models/queries/work_packages/selects/work_package_select.rb
@@ -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
diff --git a/app/models/query/manual_sorting.rb b/app/models/query/manual_sorting.rb
index c107398c9bf..94ca3ccaaed 100644
--- a/app/models/query/manual_sorting.rb
+++ b/app/models/query/manual_sorting.rb
@@ -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
diff --git a/app/models/query/results/group_by.rb b/app/models/query/results/group_by.rb
index db8caa22c4a..14d2194d8fd 100644
--- a/app/models/query/results/group_by.rb
+++ b/app/models/query/results/group_by.rb
@@ -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)
diff --git a/app/models/work_package/exports/query_exporter.rb b/app/models/work_package/exports/query_exporter.rb
index db0f2c45381..a72d20d68d2 100644
--- a/app/models/work_package/exports/query_exporter.rb
+++ b/app/models/work_package/exports/query_exporter.rb
@@ -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
diff --git a/app/models/work_package/pdf_export/common.rb b/app/models/work_package/pdf_export/common.rb
index 54c9d44073b..24c24d65aca 100644
--- a/app/models/work_package/pdf_export/common.rb
+++ b/app/models/work_package/pdf_export/common.rb
@@ -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
diff --git a/app/models/work_package/pdf_export/overview_table.rb b/app/models/work_package/pdf_export/overview_table.rb
index e8d167f77ba..1c3bd8b573e 100644
--- a/app/models/work_package/pdf_export/overview_table.rb
+++ b/app/models/work_package/pdf_export/overview_table.rb
@@ -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
diff --git a/app/services/custom_fields/create_service.rb b/app/services/custom_fields/create_service.rb
index 917af372853..f27308e20ca 100644
--- a/app/services/custom_fields/create_service.rb
+++ b/app/services/custom_fields/create_service.rb
@@ -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
diff --git a/app/services/queries/projects/project_queries/set_attributes_service.rb b/app/services/queries/projects/project_queries/set_attributes_service.rb
index 742d4ba9107..cde9ef948a4 100644
--- a/app/services/queries/projects/project_queries/set_attributes_service.rb
+++ b/app/services/queries/projects/project_queries/set_attributes_service.rb
@@ -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
diff --git a/config/constants/settings/definition.rb b/config/constants/settings/definition.rb
index 51891c44194..6d8048ef565 100644
--- a/config/constants/settings/definition.rb
+++ b/config/constants/settings/definition.rb
@@ -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]
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 876c53ad34f..79f0f4cfe48 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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:
diff --git a/db/migrate/20240215155909_rename_columns_on_project_queries.rb b/db/migrate/20240215155909_rename_columns_on_project_queries.rb
new file mode 100644
index 00000000000..a6a54427280
--- /dev/null
+++ b/db/migrate/20240215155909_rename_columns_on_project_queries.rb
@@ -0,0 +1,5 @@
+class RenameColumnsOnProjectQueries < ActiveRecord::Migration[7.1]
+ def change
+ rename_column :project_queries, :columns, :selects
+ end
+end
diff --git a/lib/api/v3/queries/columns/query_columns_factory.rb b/lib/api/v3/queries/columns/query_columns_factory.rb
index 1716d19a951..2ed94b2fc72 100644
--- a/lib/api/v3/queries/columns/query_columns_factory.rb
+++ b/lib/api/v3/queries/columns/query_columns_factory.rb
@@ -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
diff --git a/lib/api/v3/queries/columns/query_relation_of_type_column_representer.rb b/lib/api/v3/queries/columns/query_relation_of_type_column_representer.rb
index f4fe9422039..85e0d1c023b 100644
--- a/lib/api/v3/queries/columns/query_relation_of_type_column_representer.rb
+++ b/lib/api/v3/queries/columns/query_relation_of_type_column_representer.rb
@@ -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
diff --git a/lib/api/v3/queries/columns/query_relation_to_type_column_representer.rb b/lib/api/v3/queries/columns/query_relation_to_type_column_representer.rb
index cc0524ef133..cf6a57406a2 100644
--- a/lib/api/v3/queries/columns/query_relation_to_type_column_representer.rb
+++ b/lib/api/v3/queries/columns/query_relation_to_type_column_representer.rb
@@ -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),
diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb
index 1e85a1db84d..7b76092b3c3 100644
--- a/modules/backlogs/lib/open_project/backlogs/engine.rb
+++ b/modules/backlogs/lib/open_project/backlogs/engine.rb
@@ -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
diff --git a/modules/backlogs/lib/open_project/backlogs/query_backlogs_column.rb b/modules/backlogs/lib/open_project/backlogs/query_backlogs_select.rb
similarity index 90%
rename from modules/backlogs/lib/open_project/backlogs/query_backlogs_column.rb
rename to modules/backlogs/lib/open_project/backlogs/query_backlogs_select.rb
index ad2fe928c41..ed96c9b7faf 100644
--- a/modules/backlogs/lib/open_project/backlogs/query_backlogs_column.rb
+++ b/modules/backlogs/lib/open_project/backlogs/query_backlogs_select.rb
@@ -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
diff --git a/modules/bim/app/models/bim/queries/work_packages/columns/bcf_thumbnail_column.rb b/modules/bim/app/models/bim/queries/work_packages/selects/bcf_thumbnail_select.rb
similarity index 92%
rename from modules/bim/app/models/bim/queries/work_packages/columns/bcf_thumbnail_column.rb
rename to modules/bim/app/models/bim/queries/work_packages/selects/bcf_thumbnail_select.rb
index 5333f27368f..b68efe0b36a 100644
--- a/modules/bim/app/models/bim/queries/work_packages/columns/bcf_thumbnail_column.rb
+++ b/modules/bim/app/models/bim/queries/work_packages/selects/bcf_thumbnail_select.rb
@@ -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
diff --git a/modules/bim/lib/open_project/bim/engine.rb b/modules/bim/lib/open_project/bim/engine.rb
index 206703c9639..accd3e845ab 100644
--- a/modules/bim/lib/open_project/bim/engine.rb
+++ b/modules/bim/lib/open_project/bim/engine.rb
@@ -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
diff --git a/modules/bim/spec/models/queries/work_packages/columns/bcf_thumbnail_column_spec.rb b/modules/bim/spec/models/queries/work_packages/selects/bcf_thumbnail_select_spec.rb
similarity index 76%
rename from modules/bim/spec/models/queries/work_packages/columns/bcf_thumbnail_column_spec.rb
rename to modules/bim/spec/models/queries/work_packages/selects/bcf_thumbnail_select_spec.rb
index 7aba00c9afc..5539de18bde 100644
--- a/modules/bim/spec/models/queries/work_packages/columns/bcf_thumbnail_column_spec.rb
+++ b/modules/bim/spec/models/queries/work_packages/selects/bcf_thumbnail_select_spec.rb
@@ -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'
diff --git a/modules/costs/lib/costs/engine.rb b/modules/costs/lib/costs/engine.rb
index aaee8ae8c8f..a2aa49a45b7 100644
--- a/modules/costs/lib/costs/engine.rb
+++ b/modules/costs/lib/costs/engine.rb
@@ -301,7 +301,7 @@ module Costs
end
::Queries::Register.register(::Query) do
- column Costs::QueryCurrencyColumn
+ select Costs::QueryCurrencySelect
end
end
end
diff --git a/modules/costs/lib/costs/query_currency_column.rb b/modules/costs/lib/costs/query_currency_select.rb
similarity index 90%
rename from modules/costs/lib/costs/query_currency_column.rb
rename to modules/costs/lib/costs/query_currency_select.rb
index 18f8496002e..285c7b7d2f7 100644
--- a/modules/costs/lib/costs/query_currency_column.rb
+++ b/modules/costs/lib/costs/query_currency_select.rb
@@ -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
diff --git a/modules/costs/spec/lib/costs/query_currency_column_spec.rb b/modules/costs/spec/lib/costs/query_currency_select_spec.rb
similarity index 98%
rename from modules/costs/spec/lib/costs/query_currency_column_spec.rb
rename to modules/costs/spec/lib/costs/query_currency_select_spec.rb
index 8d0a736749c..dd706c91a10 100644
--- a/modules/costs/spec/lib/costs/query_currency_column_spec.rb
+++ b/modules/costs/spec/lib/costs/query_currency_select_spec.rb
@@ -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)
diff --git a/spec/contracts/queries/projects/project_queries/create_contract_spec.rb b/spec/contracts/queries/projects/project_queries/create_contract_spec.rb
index 4a991387132..6b56f3cfe07 100644
--- a/spec/contracts/queries/projects/project_queries/create_contract_spec.rb
+++ b/spec/contracts/queries/projects/project_queries/create_contract_spec.rb
@@ -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
diff --git a/spec/contracts/queries/projects/project_queries/loading_contract_spec.rb b/spec/contracts/queries/projects/project_queries/loading_contract_spec.rb
index 29890dbfcdd..fc469506d00 100644
--- a/spec/contracts/queries/projects/project_queries/loading_contract_spec.rb
+++ b/spec/contracts/queries/projects/project_queries/loading_contract_spec.rb
@@ -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) }
diff --git a/spec/contracts/queries/projects/project_queries/shared_contract_examples.rb b/spec/contracts/queries/projects/project_queries/shared_contract_examples.rb
index 562dc8bc91f..fb1a395ec19 100644
--- a/spec/contracts/queries/projects/project_queries/shared_contract_examples.rb
+++ b/spec/contracts/queries/projects/project_queries/shared_contract_examples.rb
@@ -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
diff --git a/spec/controllers/queries/params_parser_spec.rb b/spec/controllers/queries/params_parser_spec.rb
index 5bd136b947c..a4fcae942e0 100644
--- a/spec/controllers/queries/params_parser_spec.rb
+++ b/spec/controllers/queries/params_parser_spec.rb
@@ -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
diff --git a/spec/lib/api/v3/queries/columns/query_relation_of_type_column_representer_spec.rb b/spec/lib/api/v3/queries/columns/query_relation_of_type_column_representer_spec.rb
index 766679eae98..7b95b5fb444 100644
--- a/spec/lib/api/v3/queries/columns/query_relation_of_type_column_representer_spec.rb
+++ b/spec/lib/api/v3/queries/columns/query_relation_of_type_column_representer_spec.rb
@@ -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 }
diff --git a/spec/lib/api/v3/queries/columns/query_relation_to_type_column_representer_spec.rb b/spec/lib/api/v3/queries/columns/query_relation_to_type_column_representer_spec.rb
index 884980ecedf..79d4af9e0b4 100644
--- a/spec/lib/api/v3/queries/columns/query_relation_to_type_column_representer_spec.rb
+++ b/spec/lib/api/v3/queries/columns/query_relation_to_type_column_representer_spec.rb
@@ -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 }
diff --git a/spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb b/spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb
index 7f92d4c5a73..68dae0b4adb 100644
--- a/spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb
+++ b/spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb
@@ -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 }
diff --git a/spec/lib/api/v3/queries/sort_bys/query_sort_by_representer_spec.rb b/spec/lib/api/v3/queries/sort_bys/query_sort_by_representer_spec.rb
index 9904a30e1cd..75376e8418a 100644
--- a/spec/lib/api/v3/queries/sort_bys/query_sort_by_representer_spec.rb
+++ b/spec/lib/api/v3/queries/sort_bys/query_sort_by_representer_spec.rb
@@ -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
diff --git a/spec/models/queries/projects/factory_spec.rb b/spec/models/queries/projects/factory_spec.rb
index bec481070d6..c194386911b 100644
--- a/spec/models/queries/projects/factory_spec.rb
+++ b/spec/models/queries/projects/factory_spec.rb
@@ -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
diff --git a/spec/models/queries/projects/project_query_spec.rb b/spec/models/queries/projects/project_query_spec.rb
index 972421823cb..3327b219763 100644
--- a/spec/models/queries/projects/project_query_spec.rb
+++ b/spec/models/queries/projects/project_query_spec.rb
@@ -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
diff --git a/spec/models/queries/work_packages/columns/custom_field_column_spec.rb b/spec/models/queries/work_packages/selects/custom_field_select_spec.rb
similarity index 96%
rename from spec/models/queries/work_packages/columns/custom_field_column_spec.rb
rename to spec/models/queries/work_packages/selects/custom_field_select_spec.rb
index f590f41cfb4..618ada5177a 100644
--- a/spec/models/queries/work_packages/columns/custom_field_column_spec.rb
+++ b/spec/models/queries/work_packages/selects/custom_field_select_spec.rb
@@ -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) }
diff --git a/spec/models/queries/work_packages/columns/property_column_spec.rb b/spec/models/queries/work_packages/selects/property_select_spec.rb
similarity index 95%
rename from spec/models/queries/work_packages/columns/property_column_spec.rb
rename to spec/models/queries/work_packages/selects/property_select_spec.rb
index 26ac053659a..bd22e08b3e3 100644
--- a/spec/models/queries/work_packages/columns/property_column_spec.rb
+++ b/spec/models/queries/work_packages/selects/property_select_spec.rb
@@ -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'
diff --git a/spec/models/queries/work_packages/columns/relation_of_type_column_spec.rb b/spec/models/queries/work_packages/selects/relation_of_type_select_spec.rb
similarity index 95%
rename from spec/models/queries/work_packages/columns/relation_of_type_column_spec.rb
rename to spec/models/queries/work_packages/selects/relation_of_type_select_spec.rb
index 4fc33e9c4da..c31b53e4dcc 100644
--- a/spec/models/queries/work_packages/columns/relation_of_type_column_spec.rb
+++ b/spec/models/queries/work_packages/selects/relation_of_type_select_spec.rb
@@ -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) }
diff --git a/spec/models/queries/work_packages/columns/relation_to_type_column_spec.rb b/spec/models/queries/work_packages/selects/relation_to_type_select_spec.rb
similarity index 96%
rename from spec/models/queries/work_packages/columns/relation_to_type_column_spec.rb
rename to spec/models/queries/work_packages/selects/relation_to_type_select_spec.rb
index 4e20a2a806a..c96454b53b5 100644
--- a/spec/models/queries/work_packages/columns/relation_to_type_column_spec.rb
+++ b/spec/models/queries/work_packages/selects/relation_to_type_select_spec.rb
@@ -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) }
diff --git a/spec/models/queries/work_packages/columns/shared_query_column_specs.rb b/spec/models/queries/work_packages/selects/shared_query_select_specs.rb
similarity index 100%
rename from spec/models/queries/work_packages/columns/shared_query_column_specs.rb
rename to spec/models/queries/work_packages/selects/shared_query_select_specs.rb
diff --git a/spec/models/queries/work_packages/columns/work_package_column_spec.rb b/spec/models/queries/work_packages/selects/work_package_select_spec.rb
similarity index 96%
rename from spec/models/queries/work_packages/columns/work_package_column_spec.rb
rename to spec/models/queries/work_packages/selects/work_package_select_spec.rb
index b875991ff79..57d8eb4dd33 100644
--- a/spec/models/queries/work_packages/columns/work_package_column_spec.rb
+++ b/spec/models/queries/work_packages/selects/work_package_select_spec.rb
@@ -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
diff --git a/spec/models/query_spec.rb b/spec/models/query_spec.rb
index e80bb6b6b13..12bed04655b 100644
--- a/spec/models/query_spec.rb
+++ b/spec/models/query_spec.rb
@@ -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})
diff --git a/spec/services/queries/projects/project_queries/set_attributes_service_spec.rb b/spec/services/queries/projects/project_queries/set_attributes_service_spec.rb
index a195281ec65..76866cf7b54 100644
--- a/spec/services/queries/projects/project_queries/set_attributes_service_spec.rb
+++ b/spec/services/queries/projects/project_queries/set_attributes_service_spec.rb
@@ -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
|