mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
column registration
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+30
-21
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+4
-4
@@ -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))
|
||||
+1
-1
@@ -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)
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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,
|
||||
+41
-22
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
+2
-2
@@ -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
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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'
|
||||
@@ -301,7 +301,7 @@ module Costs
|
||||
end
|
||||
|
||||
::Queries::Register.register(::Query) do
|
||||
column Costs::QueryCurrencyColumn
|
||||
select Costs::QueryCurrencySelect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+6
-6
@@ -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
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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) }
|
||||
+2
-2
@@ -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'
|
||||
+2
-2
@@ -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) }
|
||||
+2
-2
@@ -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) }
|
||||
+1
-1
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user