Skip to content

Commit 7f98523

Browse files
bsatarnejadHDinger
andauthored
[Dream-719] Allow configuration for WP types to show project attributes (#23778)
* Add a migration to create the join table between custom field and type * Add contract and services * Create row, section and index components * Add a new tab to the work package type page * Add specs * Fix yamllint errors * Remove invalid demo project submodule reference * Specs for contracts * Use filter--filter-list controller and show error when there is an unsuccessful action and use params.permit(:value)[:value] * Raise argumentError in perform_bulk_edit and make create_mapping and destroy_mapping private * Create a helper for project attribute specs * Use BorderBoxListComponent for type project attributes * Use the grouped project custom field ordering helper * Add a tab for project attributes within the WP view * Don't use the overview components but specific ones for the new tab and add some specs * Only show the ProjectAttributes tab when there are project attributes visible to the user * Remove inspect from unsupported project attribute bulk update action messages * Only show project attributes configured for this Wp type * * Allow editing project attributes when the edit permission is present without "edit_project" permission * Do some code optimizations * Add descriptional text similiar to how it was done for the pdf export tab --------- Co-authored-by: Henriette Darge <h.darge@openproject.com>
1 parent ae5b0d6 commit 7f98523

51 files changed

Lines changed: 2190 additions & 23 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<%=
2+
component_wrapper(data: wrapper_data_attributes) do
3+
flex_layout do |flex|
4+
flex.with_row do
5+
render(Primer::OpenProject::SubHeader.new) do |subheader|
6+
subheader.with_filter_input(
7+
name: "border-box-filter",
8+
label: t("types.edit.project_attributes.filter"),
9+
visually_hide_label: true,
10+
placeholder: t("types.edit.project_attributes.filter"),
11+
leading_visual: {
12+
icon: :search,
13+
size: :small
14+
},
15+
show_clear_button: true,
16+
clear_button_id: clear_button_id,
17+
data: {
18+
action: "input->filter--filter-list#filterLists",
19+
"filter--filter-list-target": "filter"
20+
}
21+
)
22+
end
23+
end
24+
25+
@project_custom_field_sections.each do |project_custom_field_section, project_custom_fields|
26+
flex.with_row do
27+
render(
28+
WorkPackageTypes::ProjectAttributes::SectionComponent.new(
29+
type: @type,
30+
project_custom_field_section:,
31+
project_custom_fields:
32+
)
33+
)
34+
end
35+
end
36+
end
37+
end
38+
%>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# See COPYRIGHT and LICENSE files for more details.
11+
#++
12+
13+
module WorkPackageTypes
14+
module ProjectAttributes
15+
class IndexComponent < ApplicationComponent
16+
include ApplicationHelper
17+
include OpPrimer::ComponentHelpers
18+
include OpTurbo::Streamable
19+
20+
def initialize(type:, project_custom_field_sections:)
21+
super
22+
23+
@type = type
24+
@project_custom_field_sections = project_custom_field_sections
25+
end
26+
27+
private
28+
29+
def wrapper_data_attributes
30+
{
31+
controller: "filter--filter-list",
32+
"filter--filter-list-clear-button-id-value": clear_button_id
33+
}
34+
end
35+
36+
def clear_button_id
37+
"project-attributes-filter-clear-button"
38+
end
39+
end
40+
end
41+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<%=
2+
component_wrapper do
3+
flex_layout(
4+
align_items: :center, justify_content: :space_between, classes: "op-project-custom-field", data: {
5+
test_selector: "type-project-attribute-#{@project_custom_field.id}"
6+
}
7+
) do |custom_field_container|
8+
custom_field_container.with_column(flex_layout: true) do |title_container|
9+
title_container.with_column(pt: 1, mr: 2) do
10+
render(Primer::Beta::Text.new(classes: "filter-target-visible-text")) do
11+
@project_custom_field.name
12+
end
13+
end
14+
title_container.with_column(pt: 1, mr: 2, data: { test_selector: "custom-field-type" }) do
15+
render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) do
16+
helpers.label_for_custom_field_format(@project_custom_field.field_format)
17+
end
18+
end
19+
if @project_custom_field.required?
20+
title_container.with_column(pt: 1) do
21+
render(Primer::Beta::Label.new(scheme: :attention, size: :medium)) do
22+
ProjectCustomField.human_attribute_name(:required)
23+
end
24+
end
25+
end
26+
end
27+
28+
custom_field_container.with_column(py: 1, mr: 2) do
29+
render(
30+
Primer::Alpha::ToggleSwitch.new(
31+
src: toggle_path,
32+
csrf_token: form_authenticity_token,
33+
data: toggle_data_attributes,
34+
checked: active_for_type?,
35+
enabled: true,
36+
size: :small,
37+
status_label_position: :start,
38+
classes: "op-primer-adjustments__toggle-switch--hidden-loading-indicator"
39+
)
40+
)
41+
end
42+
end
43+
end
44+
%>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# See COPYRIGHT and LICENSE files for more details.
11+
#++
12+
13+
module WorkPackageTypes
14+
module ProjectAttributes
15+
class RowComponent < ApplicationComponent
16+
include ApplicationHelper
17+
include OpPrimer::ComponentHelpers
18+
include OpTurbo::Streamable
19+
20+
def initialize(type:, project_custom_field:)
21+
super
22+
23+
@type = type
24+
@project_custom_field = project_custom_field
25+
@project_custom_field_type_mappings = type.project_custom_field_type_mappings
26+
end
27+
28+
private
29+
30+
def wrapper_uniq_by
31+
@project_custom_field.id
32+
end
33+
34+
def active_for_type?
35+
@project_custom_field_type_mappings.any? do |mapping|
36+
mapping.custom_field_id == @project_custom_field.id
37+
end
38+
end
39+
40+
def toggle_path
41+
toggle_type_project_attributes_path(
42+
@type,
43+
project_custom_field_type_mapping: {
44+
type_id: @type.id,
45+
custom_field_id: @project_custom_field.id
46+
}
47+
)
48+
end
49+
50+
def toggle_data_attributes
51+
{
52+
"turbo-method": :post,
53+
"turbo-stream": true,
54+
test_selector: "toggle-type-project-attribute-#{@project_custom_field.id}"
55+
}
56+
end
57+
end
58+
end
59+
end
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<%= component_wrapper do %>
2+
<%=
3+
render(
4+
OpenProject::Common::BorderBoxListComponent.new(
5+
container: "type-project-attribute-section-#{@project_custom_field_section.id}",
6+
mb: 3,
7+
classes: "op-project-custom-field-section",
8+
test_selector: "type-project-attribute-section-#{@project_custom_field_section.id}"
9+
)
10+
) do |list|
11+
list.with_header(
12+
title: @project_custom_field_section.name,
13+
title_arguments: { font_weight: :bold }
14+
) do |header|
15+
header.with_action_button(
16+
tag: :a,
17+
href: enable_all_path,
18+
scheme: :invisible,
19+
font_weight: :bold,
20+
color: :subtle,
21+
"aria-label": t("types.edit.project_attributes.actions.label_enable_all"),
22+
data: {
23+
"turbo-method": :put,
24+
"turbo-stream": true,
25+
test_selector: "enable-all-type-project-attributes-#{@project_custom_field_section.id}"
26+
}
27+
) do |button|
28+
button.with_leading_visual_icon(icon: "check-circle", color: :subtle)
29+
t("types.edit.project_attributes.actions.label_enable_all")
30+
end
31+
32+
header.with_action_button(
33+
tag: :a,
34+
href: disable_all_path,
35+
scheme: :invisible,
36+
font_weight: :bold,
37+
color: :subtle,
38+
"aria-label": t("types.edit.project_attributes.actions.label_disable_all"),
39+
data: {
40+
"turbo-method": :put,
41+
"turbo-stream": true,
42+
test_selector: "disable-all-type-project-attributes-#{@project_custom_field_section.id}"
43+
}
44+
) do |button|
45+
button.with_leading_visual_icon(icon: "x-circle", color: :subtle)
46+
t("types.edit.project_attributes.actions.label_disable_all")
47+
end
48+
end
49+
50+
if @project_custom_fields.empty?
51+
list.with_item do
52+
render(Primer::Beta::Text.new(color: :subtle)) do
53+
t("settings.project_attributes.label_no_project_custom_fields")
54+
end
55+
end
56+
else
57+
@project_custom_fields.each do |project_custom_field|
58+
list.with_item(data: { "filter--filter-list-target": "searchItem" }) do
59+
render(
60+
WorkPackageTypes::ProjectAttributes::RowComponent.new(
61+
type: @type,
62+
project_custom_field:
63+
)
64+
)
65+
end
66+
end
67+
end
68+
end
69+
%>
70+
<% end %>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# See COPYRIGHT and LICENSE files for more details.
11+
#++
12+
13+
module WorkPackageTypes
14+
module ProjectAttributes
15+
class SectionComponent < ApplicationComponent
16+
include ApplicationHelper
17+
include OpPrimer::ComponentHelpers
18+
include OpTurbo::Streamable
19+
20+
def initialize(type:, project_custom_field_section:, project_custom_fields:)
21+
super
22+
23+
@type = type
24+
@project_custom_field_section = project_custom_field_section
25+
@project_custom_fields = project_custom_fields
26+
end
27+
28+
private
29+
30+
def enable_all_path
31+
enable_all_of_section_type_project_attributes_path(
32+
@type,
33+
project_custom_field_type_mapping: bulk_action_params
34+
)
35+
end
36+
37+
def disable_all_path
38+
disable_all_of_section_type_project_attributes_path(
39+
@type,
40+
project_custom_field_type_mapping: bulk_action_params
41+
)
42+
end
43+
44+
def bulk_action_params
45+
{
46+
type_id: @type.id,
47+
custom_field_section_id: @project_custom_field_section.id
48+
}
49+
end
50+
51+
def wrapper_uniq_by
52+
@project_custom_field_section.id
53+
end
54+
end
55+
end
56+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<turbo-frame id="work-package-project-attributes-tab-content">
2+
<%=
3+
component_wrapper do
4+
if project_custom_fields_grouped_by_section.any?
5+
render(Primer::OpenProject::SidePanel.new) do |panel|
6+
project_custom_fields_grouped_by_section.each do |project_custom_field_section, project_custom_fields|
7+
panel.with_section(
8+
WorkPackages::ProjectCustomFields::SectionComponent.new(
9+
project: @project,
10+
project_custom_field_section:,
11+
project_custom_fields:
12+
)
13+
)
14+
end
15+
end
16+
end
17+
end
18+
%>
19+
</turbo-frame>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
class WorkPackages::ProjectAttributesTabComponent < ApplicationComponent
32+
include OpPrimer::ComponentHelpers
33+
include OpTurbo::Streamable
34+
35+
def initialize(work_package:)
36+
super
37+
38+
@work_package = work_package
39+
@project = work_package.project
40+
end
41+
42+
def render?
43+
project_custom_fields_grouped_by_section.any?
44+
end
45+
46+
private
47+
48+
def project_custom_fields_grouped_by_section
49+
@project_custom_fields_grouped_by_section ||=
50+
ProjectCustomFieldSection.grouped_in_order(
51+
@project.available_custom_fields_for_type(@work_package.type_id)
52+
)
53+
end
54+
end

0 commit comments

Comments
 (0)