mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
[61445] Configure project attribute sections to be shown as widgets on the project overview page (#20899)
* Show project attributes as sections in project overview * Introduce a column "shown_in" for CustomFieldSection which defines whether the section shall be shown in the sidebar or the main section of the project overview * Show only those attributes in main section that are configured to be shown there. Same for the sidebar. * Update project attribute sections after editing them * Write a test for the new positioning behavior of project attribute sections * Rename "shown_in" with "display_representation" and make it a hash * Provide proper default when creating a new section * Enforce project attribute widgets to be in a new line and wrap properly * Hide logic for moivng sections to the main area behind the feature flag * Rename "main section" to "main area" * Extract custom margins into normal sass * Rename "side-panel" to "side panel" * Update test to changed wording
This commit is contained in:
@@ -13,7 +13,42 @@
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
section_header_container.with_column(flex_layout: true, justify_content: :flex_end) do |actions_container|
|
||||
if OpenProject::FeatureDecisions.new_project_overview_active?
|
||||
actions_container.with_column do
|
||||
render(Primer::Alpha::ActionMenu.new(select_variant: :single, size: :small, test_selector: "section-position-selector")) do |menu|
|
||||
menu.with_show_button(
|
||||
mr: 2,
|
||||
aria: { label: t("settings.project_attributes.sections.display_representation.overview.label") }
|
||||
) do |button|
|
||||
button.with_trailing_visual_icon(icon: :"triangle-down")
|
||||
button.with_leading_visual_icon(icon: display_representation_icon_for_section(@project_custom_field_section))
|
||||
display_representation_label_for_section(@project_custom_field_section)
|
||||
end
|
||||
|
||||
menu.with_item(
|
||||
label: t("settings.project_attributes.sections.display_representation.overview.side_panel.label"),
|
||||
active: @project_custom_field_section.shown_in_overview_sidebar?,
|
||||
test_selector: "section-position-selector--side-panel-option",
|
||||
**menu_item_options_for(@project_custom_field_section, ProjectCustomFieldSection::OVERVIEW__SIDEBAR_KEY)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :"op-view-split")
|
||||
item.with_description.with_content(t("settings.project_attributes.sections.display_representation.overview.side_panel.description"))
|
||||
end
|
||||
menu.with_item(
|
||||
label: t("settings.project_attributes.sections.display_representation.overview.main_area.label"),
|
||||
active: @project_custom_field_section.shown_in_overview_main_area?,
|
||||
test_selector: "section-position-selector--main-section-option",
|
||||
**menu_item_options_for(@project_custom_field_section, ProjectCustomFieldSection::OVERVIEW__MAIN_AREA_KEY)
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :"op-view-cards")
|
||||
item.with_description.with_content(t("settings.project_attributes.sections.display_representation.overview.main_area.description"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
actions_container.with_column do
|
||||
render(Primer::Alpha::ActionMenu.new(data: { test_selector: "project-custom-field-section-action-menu" })) do |menu|
|
||||
menu.with_show_button(icon: "kebab-horizontal", "aria-label": t("settings.project_attributes.label_section_actions"), scheme: :invisible)
|
||||
@@ -26,6 +61,7 @@
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
actions_container.with_column do
|
||||
render(
|
||||
Primer::Alpha::Dialog.new(
|
||||
|
||||
@@ -161,6 +161,31 @@ module Settings
|
||||
test_selector: "new-project-custom-field-in-section-button-#{format.name}" } }
|
||||
)
|
||||
end
|
||||
|
||||
def display_representation_icon_for_section(section)
|
||||
section.shown_in_overview_sidebar? ? :"op-view-split" : :"op-view-cards"
|
||||
end
|
||||
|
||||
def display_representation_label_for_section(section)
|
||||
if section.shown_in_overview_sidebar?
|
||||
t("settings.project_attributes.sections.display_representation.overview.side_panel.label")
|
||||
else
|
||||
t("settings.project_attributes.sections.display_representation.overview.main_area.label")
|
||||
end
|
||||
end
|
||||
|
||||
def menu_item_options_for(section, key)
|
||||
{
|
||||
href: admin_settings_project_custom_field_section_path(section),
|
||||
form_arguments: {
|
||||
method: :put,
|
||||
inputs: [{
|
||||
name: "project_custom_field_section[overview]",
|
||||
value: key
|
||||
}]
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,5 +35,6 @@ module ProjectCustomFieldSections
|
||||
attribute :name
|
||||
attribute :position
|
||||
attribute :type
|
||||
attribute :display_representation
|
||||
end
|
||||
end
|
||||
|
||||
@@ -121,7 +121,9 @@ module Admin::Settings
|
||||
end
|
||||
|
||||
def project_custom_field_section_params
|
||||
params.require(:project_custom_field_section).permit(:name)
|
||||
# Set the sidebar as default
|
||||
params.require(:project_custom_field_section)[:overview] ||= CustomFieldSection::OVERVIEW__SIDEBAR_KEY
|
||||
params.expect(project_custom_field_section: %i[name overview])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,9 +29,23 @@
|
||||
#++
|
||||
|
||||
class CustomFieldSection < ApplicationRecord
|
||||
OVERVIEW__SIDEBAR_KEY = "sidebar"
|
||||
OVERVIEW__MAIN_AREA_KEY = "main_area"
|
||||
DEFAULT_OVERVIEW_KEY = OVERVIEW__SIDEBAR_KEY.freeze
|
||||
|
||||
acts_as_list scope: [:type]
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
default_scope { order(:position) }
|
||||
|
||||
store_attribute :display_representation, :overview, :string
|
||||
|
||||
def shown_in_overview_sidebar?
|
||||
overview == OVERVIEW__SIDEBAR_KEY
|
||||
end
|
||||
|
||||
def shown_in_overview_main_area?
|
||||
overview == OVERVIEW__MAIN_AREA_KEY
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 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 ProjectCustomFields
|
||||
class LoadService
|
||||
def initialize(project:, project_custom_fields:)
|
||||
super()
|
||||
@project = project
|
||||
@project_custom_fields = project_custom_fields
|
||||
eager_load_project_custom_field_values
|
||||
end
|
||||
|
||||
def get_eager_loaded_project_custom_field_values_for(custom_field_id)
|
||||
@eager_loaded_project_custom_field_values.select { |pcfv| pcfv.custom_field_id == custom_field_id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eager_load_project_custom_field_values
|
||||
@eager_loaded_project_custom_field_values = CustomValue
|
||||
.includes(custom_field: :custom_options)
|
||||
.where(
|
||||
custom_field_id: @project_custom_fields.pluck(:id),
|
||||
customized_id: @project.id
|
||||
)
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4610,6 +4610,16 @@ en:
|
||||
new:
|
||||
heading: "New attribute"
|
||||
description: "Changes to this project attribute will be reflected in all projects where it is enabled. Required attributes cannot be disabled on a per-project basis."
|
||||
sections:
|
||||
display_representation:
|
||||
overview:
|
||||
label: "Project attribute shown in:"
|
||||
main_area:
|
||||
label: "Main area"
|
||||
description: "Add all the project attributes as individual widgets in the main section of the project overview."
|
||||
side_panel:
|
||||
label: "Side panel"
|
||||
description: "Add all the project attributes in a section inside the right side panel in the project overview."
|
||||
project_initiation_request:
|
||||
blankslate:
|
||||
title: "Initiation request not enabled"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddDisplayRepresentationColumnToCfSection < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :custom_field_sections,
|
||||
:display_representation,
|
||||
:jsonb,
|
||||
default: { overview: CustomFieldSection::DEFAULT_OVERVIEW_KEY },
|
||||
null: false
|
||||
end
|
||||
end
|
||||
@@ -1 +1,2 @@
|
||||
@import "./grids/widgets/news/item.sass"
|
||||
@import "./grids/project_attribute_widgets.sass"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) 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.
|
||||
|
||||
++#%>
|
||||
<%= component_wrapper(class: "grids-project-attribute-widgets", data: { test_selector: "grids-project-attribute-widgets" }) do %>
|
||||
<% widgets.each do |widget| %>
|
||||
<%= render widget %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 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 Grids
|
||||
class ProjectAttributeWidgets < Grids::WidgetComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
renders_many :widgets, Grids::Widgets::ProjectAttributeSection
|
||||
|
||||
param :project
|
||||
|
||||
def title
|
||||
""
|
||||
end
|
||||
|
||||
# For each configured section, call the the `with_widget` slot
|
||||
def before_render
|
||||
available_project_attributes_grouped_by_section.each do |section, project_custom_fields|
|
||||
with_widget(section, project_custom_fields, @project)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_project_attributes_grouped_by_section
|
||||
if OpenProject::FeatureDecisions.new_project_overview_active?
|
||||
@available_project_attributes_grouped_by_section ||=
|
||||
@project.available_custom_fields
|
||||
.group_by(&:project_custom_field_section)
|
||||
.select { |section, _| section.shown_in_overview_main_area? }
|
||||
else
|
||||
@available_project_attributes_grouped_by_section ||=
|
||||
@project.available_custom_fields
|
||||
.group_by(&:project_custom_field_section)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
.grids-project-attribute-widgets
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
flex-basis: 100%
|
||||
@@ -43,6 +43,7 @@ module Grids
|
||||
|
||||
delegate :wrapper_key, to: :class
|
||||
|
||||
option :tag, default: -> { :div }
|
||||
option :current_user, default: -> { User.current }
|
||||
|
||||
# @abstract Subclasses must implement this method.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
widget_wrapper(
|
||||
classes: "op-project-custom-field-section-container",
|
||||
test_selector: "project-custom-field-section-widget-#{section.id}"
|
||||
) do
|
||||
render(
|
||||
Overviews::ProjectCustomFields::ItemsComponent.new(
|
||||
project_custom_fields:,
|
||||
project:
|
||||
)
|
||||
)
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 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 Grids
|
||||
module Widgets
|
||||
class ProjectAttributeSection < Grids::WidgetComponent
|
||||
param :section
|
||||
param :project_custom_fields
|
||||
param :project
|
||||
|
||||
def title
|
||||
section.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "./overviews/project_custom_fields/item_component.sass"
|
||||
@import "./overviews/project_custom_fields/items_component.sass"
|
||||
@import "./overviews/project_phases/item_component.sass"
|
||||
@import "./overviews/overview_grid_component.sass"
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<%=
|
||||
flex_layout(classes: "project-custom-field-items") do |flex|
|
||||
@project_custom_fields.each do |project_custom_field|
|
||||
flex.with_row do
|
||||
render(
|
||||
Overviews::ProjectCustomFields::ItemComponent.new(
|
||||
project_custom_field:,
|
||||
project_custom_field_values: attribute_load_service.get_eager_loaded_project_custom_field_values_for(project_custom_field.id),
|
||||
project: @project
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 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 Overviews
|
||||
module ProjectCustomFields
|
||||
class ItemsComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(project_custom_fields:, project:)
|
||||
super
|
||||
|
||||
@project_custom_fields = project_custom_fields
|
||||
@project = project
|
||||
end
|
||||
|
||||
def attribute_load_service
|
||||
@attribute_load_service ||= ::ProjectCustomFields::LoadService.new(project: @project,
|
||||
project_custom_fields: @project_custom_fields)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
.project-custom-field-items
|
||||
gap: var(--base-size-16, 16px)
|
||||
+6
-14
@@ -8,20 +8,12 @@
|
||||
) do |section|
|
||||
section.with_title { @project_custom_field_section.name }
|
||||
|
||||
flex_layout do |details_container|
|
||||
@project_custom_fields.each_with_index do |project_custom_field, i|
|
||||
margin = i == @project_custom_fields.size - 1 ? 0 : 3
|
||||
details_container.with_row(mb: margin) do
|
||||
render(
|
||||
Overviews::ProjectCustomFields::ItemComponent.new(
|
||||
project_custom_field:,
|
||||
project_custom_field_values: get_eager_loaded_project_custom_field_values_for(project_custom_field.id),
|
||||
project: @project
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
render(
|
||||
Overviews::ProjectCustomFields::ItemsComponent.new(
|
||||
project_custom_fields: @project_custom_fields,
|
||||
project: @project
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
@@ -41,25 +41,6 @@ module Overviews
|
||||
@project = project
|
||||
@project_custom_field_section = project_custom_field_section
|
||||
@project_custom_fields = project_custom_fields
|
||||
|
||||
eager_load_project_custom_field_values
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eager_load_project_custom_field_values
|
||||
# TODO: move to service
|
||||
@eager_loaded_project_custom_field_values = CustomValue
|
||||
.includes(custom_field: :custom_options)
|
||||
.where(
|
||||
custom_field_id: @project_custom_fields.pluck(:id),
|
||||
customized_id: @project.id
|
||||
)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def get_eager_loaded_project_custom_field_values_for(custom_field_id)
|
||||
@eager_loaded_project_custom_field_values.select { |pcfv| pcfv.custom_field_id == custom_field_id }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+10
-2
@@ -49,8 +49,16 @@ module Overviews
|
||||
private
|
||||
|
||||
def available_project_custom_fields_grouped_by_section
|
||||
@available_project_custom_fields_grouped_by_section ||=
|
||||
@project.available_custom_fields.group_by(&:project_custom_field_section)
|
||||
if OpenProject::FeatureDecisions.new_project_overview_active?
|
||||
@available_project_custom_fields_grouped_by_section ||=
|
||||
@project.available_custom_fields
|
||||
.group_by(&:project_custom_field_section)
|
||||
.select { |section, _| section.shown_in_overview_sidebar? }
|
||||
else
|
||||
@available_project_custom_fields_grouped_by_section ||=
|
||||
@project.available_custom_fields
|
||||
.group_by(&:project_custom_field_section)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+2
@@ -33,5 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
grid.with_widget(Grids::Widgets::ProjectStatus, @portfolio)
|
||||
grid.with_widget(Grids::Widgets::Subitems, @portfolio)
|
||||
grid.with_widget(Grids::Widgets::Members, @portfolio)
|
||||
|
||||
grid.with_widget(Grids::ProjectAttributeWidgets, @project)
|
||||
end
|
||||
%>
|
||||
|
||||
+2
@@ -33,5 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
grid.with_widget(Grids::Widgets::ProjectStatus, @program)
|
||||
grid.with_widget(Grids::Widgets::Subitems, @program)
|
||||
grid.with_widget(Grids::Widgets::Members, @program)
|
||||
|
||||
grid.with_widget(Grids::ProjectAttributeWidgets, @project)
|
||||
end
|
||||
%>
|
||||
|
||||
+2
@@ -34,5 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
grid.with_widget(Grids::Widgets::Subitems, @project)
|
||||
grid.with_widget(Grids::Widgets::Members, @project)
|
||||
grid.with_widget(Grids::Widgets::News, @project)
|
||||
|
||||
grid.with_widget(Grids::ProjectAttributeWidgets, @project)
|
||||
end
|
||||
%>
|
||||
|
||||
@@ -60,7 +60,11 @@ class Overviews::ProjectCustomFieldsController < ApplicationController
|
||||
.call(permitted_params.project)
|
||||
|
||||
if service_call.success?
|
||||
update_sidebar_component
|
||||
if field_shown_in_sidebar?(@custom_field)
|
||||
update_sidebar_component
|
||||
else
|
||||
update_widgets_component
|
||||
end
|
||||
else
|
||||
handle_errors(service_call.result, @custom_field)
|
||||
end
|
||||
@@ -98,4 +102,14 @@ class Overviews::ProjectCustomFieldsController < ApplicationController
|
||||
component: Overviews::ProjectCustomFields::SidePanelComponent.new(project: @project)
|
||||
)
|
||||
end
|
||||
|
||||
def update_widgets_component
|
||||
update_via_turbo_stream(
|
||||
component: Grids::ProjectAttributeWidgets.new(@project)
|
||||
)
|
||||
end
|
||||
|
||||
def field_shown_in_sidebar?(custom_field)
|
||||
CustomFieldSection.find(custom_field.custom_field_section_id).shown_in_overview_sidebar?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
FactoryBot.define do
|
||||
factory :custom_field_section do
|
||||
sequence(:name) { |n| "Section No. #{n}" }
|
||||
overview { "sidebar" }
|
||||
created_at { Time.zone.now }
|
||||
updated_at { Time.zone.now }
|
||||
|
||||
|
||||
@@ -275,6 +275,14 @@ RSpec.shared_context "with seeded projects, members and project custom fields" d
|
||||
field
|
||||
end
|
||||
|
||||
let!(:sections) do
|
||||
[
|
||||
section_for_input_fields,
|
||||
section_for_select_fields,
|
||||
section_for_multi_select_fields
|
||||
]
|
||||
end
|
||||
|
||||
let!(:input_fields) do
|
||||
[
|
||||
boolean_project_custom_field,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) 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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
require_relative "shared_context"
|
||||
|
||||
RSpec.describe "Show project custom fields on project overview page", :js, with_flag: { new_project_overview: true } do
|
||||
include TestSelectorFinders
|
||||
|
||||
include_context "with seeded projects, members and project custom fields"
|
||||
|
||||
let(:overview_page) { Pages::Projects::Show.new(project) }
|
||||
|
||||
before do
|
||||
login_as admin
|
||||
end
|
||||
|
||||
describe "within the Administration" do
|
||||
before do
|
||||
visit admin_settings_project_custom_fields_path
|
||||
end
|
||||
|
||||
it "shows an ActionMenu for each section" do
|
||||
sections.each do |section|
|
||||
within_test_selector("project-custom-field-section-container-#{section.id}") do
|
||||
# Per default, the section is shown in the side panel
|
||||
expect(page).to have_test_selector("section-position-selector", text: "Side panel")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "can change the position to main section" do
|
||||
within_test_selector("project-custom-field-section-container-#{section_for_input_fields.id}") do
|
||||
# Change it to main section
|
||||
page.find_test_selector("section-position-selector").click
|
||||
expect(page).to have_test_selector("section-position-selector--side-panel-option")
|
||||
expect(page).to have_test_selector("section-position-selector--main-section-option")
|
||||
|
||||
page.find_test_selector("section-position-selector--main-section-option").click
|
||||
wait_for_network_idle
|
||||
|
||||
# The section is updated directly
|
||||
section = CustomFieldSection.find(section_for_input_fields.id)
|
||||
expect(section.shown_in_overview_main_area?).to be(true)
|
||||
expect(page).to have_test_selector("section-position-selector", text: "Main area")
|
||||
end
|
||||
end
|
||||
|
||||
it "can add a new section which is shown per default in the sidebar" do
|
||||
within "#settings-project-custom-fields-header-component" do
|
||||
page.find_test_selector("project-attributes-add-menu-button").click
|
||||
click_on("dialog-show-project-custom-field-section-dialog")
|
||||
end
|
||||
|
||||
fill_in("project_custom_field_section_name", with: "An awesome new section")
|
||||
|
||||
click_on("Save")
|
||||
|
||||
expect(page).to have_text("An awesome new section")
|
||||
|
||||
# The section is shown in the sidebar per default
|
||||
section = CustomFieldSection.last
|
||||
expect(section.shown_in_overview_sidebar?).to be(true)
|
||||
within_test_selector("project-custom-field-section-container-#{section.id}") do
|
||||
expect(page).to have_test_selector("section-position-selector", text: "Side panel")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "within the Overview page" do
|
||||
before do
|
||||
# Move one section to the main section
|
||||
section = CustomFieldSection.find(section_for_input_fields.id)
|
||||
section.display_representation = { overview: "main_area" }
|
||||
section.save!
|
||||
|
||||
overview_page.visit!
|
||||
end
|
||||
|
||||
it "shows the sections in either the sidebar or the main section" do
|
||||
# The section is shown in the main section of the overview page ...
|
||||
overview_page.within_main_area do
|
||||
sections = page.all(".op-project-custom-field-section-container")
|
||||
|
||||
expect(sections.size).to eq(1)
|
||||
|
||||
expect(sections[0].text).to include("Input fields")
|
||||
end
|
||||
|
||||
# ... while the others remain in the sidebar
|
||||
overview_page.within_project_attributes_sidebar do
|
||||
sections = page.all(".op-project-custom-field-section-container")
|
||||
|
||||
expect(sections.size).to eq(2)
|
||||
|
||||
expect(sections[0].text).to include("Select fields")
|
||||
expect(sections[1].text).to include("Multi select fields")
|
||||
end
|
||||
end
|
||||
|
||||
it "shows the project custom fields in the correct order within the widget" do
|
||||
overview_page.within_main_area do
|
||||
overview_page.within_custom_field_section_widget(section_for_input_fields) do
|
||||
fields = page.all(".op-project-custom-field-container")
|
||||
|
||||
expect(fields.size).to eq(9)
|
||||
|
||||
expect(fields[0].text).to include("Boolean field")
|
||||
expect(fields[1].text).to include("String field")
|
||||
expect(fields[2].text).to include("Integer field")
|
||||
expect(fields[3].text).to include("Float field")
|
||||
expect(fields[4].text).to include("Date field")
|
||||
expect(fields[5].text).to include("Link field")
|
||||
expect(fields[6].text).to include("Text field")
|
||||
expect(fields[7].text).to include("Calculated field using int")
|
||||
expect(fields[8].text).to include("Calculated field using int and float")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "does not show project custom fields not enabled for this project in a widget" do
|
||||
create(:string_project_custom_field, projects: [other_project], name: "String field enabled for other project")
|
||||
|
||||
overview_page.visit_page
|
||||
|
||||
overview_page.within_main_area do
|
||||
expect(page).to have_no_text "String field enabled for other project"
|
||||
end
|
||||
end
|
||||
|
||||
it "can edit a project custom field from within the widget" do
|
||||
overview_page.open_edit_dialog_for_custom_field(string_project_custom_field)
|
||||
page.fill_in(string_project_custom_field.name, with: "My super awesome new value")
|
||||
page.click_on "Save"
|
||||
|
||||
# The new value is shown in the widget
|
||||
overview_page.within_main_area do
|
||||
overview_page.within_custom_field_section_widget(section_for_input_fields) do
|
||||
expect(page).to have_text "My super awesome new value"
|
||||
end
|
||||
end
|
||||
|
||||
expect(project.reload.custom_value_for(string_project_custom_field).value).to eq("My super awesome new value")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -65,10 +65,18 @@ module Pages
|
||||
within_test_selector("project-custom-fields-sidebar", &)
|
||||
end
|
||||
|
||||
def within_main_area(&)
|
||||
within_test_selector("grids-project-attribute-widgets", &)
|
||||
end
|
||||
|
||||
def within_custom_field_section_container(section, &)
|
||||
within_test_selector("project-custom-field-section-#{section.id}", &)
|
||||
end
|
||||
|
||||
def within_custom_field_section_widget(section, &)
|
||||
within_test_selector("project-custom-field-section-widget-#{section.id}", &)
|
||||
end
|
||||
|
||||
def within_custom_field_container(custom_field, &)
|
||||
within_test_selector("project-custom-field-#{custom_field.id}", &)
|
||||
end
|
||||
@@ -78,17 +86,15 @@ module Pages
|
||||
end
|
||||
|
||||
def open_edit_dialog_for_custom_field(custom_field)
|
||||
within_project_attributes_sidebar do
|
||||
scroll_to_element(page.find("[data-test-selector='project-custom-field-#{custom_field.id}']"))
|
||||
within_custom_field_container(custom_field) do
|
||||
# Link and user type custom fields might contain a clickable link inside the edit container.
|
||||
# Use JavaScript to directly trigger the click event on the container to avoid nested links.
|
||||
# Once we create the project custom field inline editing, this can be reverted to a normal
|
||||
# capybara click method call.
|
||||
page.execute_script(
|
||||
"document.querySelector('[data-test-selector=\"project-custom-field-edit-button-#{custom_field.id}\"]').click()"
|
||||
)
|
||||
end
|
||||
scroll_to_element(page.find("[data-test-selector='project-custom-field-#{custom_field.id}']"))
|
||||
within_custom_field_container(custom_field) do
|
||||
# Link and user type custom fields might contain a clickable link inside the edit container.
|
||||
# Use JavaScript to directly trigger the click event on the container to avoid nested links.
|
||||
# Once we create the project custom field inline editing, this can be reverted to a normal
|
||||
# capybara click method call.
|
||||
page.execute_script(
|
||||
"document.querySelector('[data-test-selector=\"project-custom-field-edit-button-#{custom_field.id}\"]').click()"
|
||||
)
|
||||
end
|
||||
|
||||
wait_for_size_animation_completion("[data-test-selector='async-dialog-content']")
|
||||
|
||||
Reference in New Issue
Block a user