[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:
Henriette Darge
2025-11-19 08:01:42 +01:00
committed by GitHub
parent e6fa04639f
commit caa609d34c
30 changed files with 649 additions and 48 deletions
@@ -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
+14
View File
@@ -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
+10
View File
@@ -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
View File
@@ -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"
@@ -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)
@@ -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
@@ -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
@@ -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
%>
@@ -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
%>
@@ -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
+1
View File
@@ -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
+17 -11
View File
@@ -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']")