diff --git a/app/components/projects/life_cycle_type_component.html.erb b/app/components/projects/life_cycle_type_component.html.erb new file mode 100644 index 000000000000..921c1cf191a2 --- /dev/null +++ b/app/components/projects/life_cycle_type_component.html.erb @@ -0,0 +1,8 @@ +<%= flex_layout(align_items: :center) do |type_container| + type_container.with_column(mr: 1, classes: icon_color_class) do + render Primer::Beta::Octicon.new(icon: icon) + end + type_container.with_column do + render(Primer::Beta::Text.new(**text_options)) { text } + end +end %> diff --git a/app/components/projects/life_cycle_type_component.rb b/app/components/projects/life_cycle_type_component.rb new file mode 100644 index 000000000000..453f5d90c5d6 --- /dev/null +++ b/app/components/projects/life_cycle_type_component.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# -- 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 Projects + class LifeCycleTypeComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def text + model.model_name.human + end + + def icon + case model + when Project::StageDefinition + :"git-commit" + when Project::GateDefinition + :diamond + else + raise NotImplementedError, "Unknown model #{model.class} to render a LifeCycleTypeComponent with" + end + end + + def icon_color_class + helpers.hl_inline_class("life_cycle_step_definition", model) + end + + def text_options + # The tag: :div is is a hack to fix the line height difference + # caused by font_size: :small. That line height difference + # would otherwise lead to the text being not on the same height as the icon + { color: :muted, font_size: :small, tag: :div }.merge(options) + end + end +end diff --git a/app/components/projects/settings/life_cycles/index_component.html.erb b/app/components/projects/settings/life_cycles/index_component.html.erb new file mode 100644 index 000000000000..da00e1047a3e --- /dev/null +++ b/app/components/projects/settings/life_cycles/index_component.html.erb @@ -0,0 +1,88 @@ +<%= + flex_layout(data: wrapper_data_attributes) do |flex| + flex.with_row do + render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_filter_input(name: "border-box-filter", + label: t('projects.settings.life_cycle.filter.label'), + visually_hide_label: true, + placeholder: t('projects.settings.life_cycle.filter.label'), + leading_visual: { + icon: :search, + size: :small + }, + show_clear_button: true, + clear_button_id: clear_button_id, + data: { + action: "input->projects--settings--border-box-filter#filterLists", + "projects--settings--border-box-filter-target": "filter" + }) + end + end + + flex.with_row do + render(border_box_container(mb: 3, data: { test_selector: "project-life-cycle-administration" })) do |component| + component.with_header(font_weight: :bold, py: 2) do + flex_layout(justify_content: :space_between, align_items: :center) do |header_container| + header_container.with_column(py: 2) do + # adding py: 2 here to match the padding of the actions_container + # otherwise the header height changes when the actions gets hidden when filtering + render(Primer::Beta::Text.new(font_weight: :bold)) do + I18n.t('projects.settings.life_cycle.section_header') + end + end + header_container.with_column(flex_layout: true, justify_content: :flex_end) do |actions_container| + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do + render(Primer::Beta::Button.new( + tag: :a, + href: enable_all_project_settings_life_cycle_path(project_id: project), + scheme: :invisible, + font_weight: :bold, + color: :subtle, + 'aria-label': t('projects.settings.actions.label_enable_all'), + data: { 'turbo-method': :post, test_selector: "enable-all-life-cycle-steps" } + )) do |button| + button.with_leading_visual_icon(icon: 'check-circle', color: :subtle) + t('projects.settings.actions.label_enable_all') + end + end + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do + render(Primer::Beta::Button.new( + tag: :a, + href: disable_all_project_settings_life_cycle_path(project_id: project), + scheme: :invisible, + font_weight: :bold, + color: :subtle, + 'aria-label': t('projects.settings.actions.label_disable_all'), + data: { 'turbo-method': :post, test_selector: "disable-all-life-cycle-steps" } + )) do |button| + button.with_leading_visual_icon(icon: 'x-circle', color: :subtle) + t('projects.settings.actions.label_disable_all') + end + end + end + end + end + if life_cycle_definitions.empty? + component.with_row do + render(Primer::Beta::Text.new(color: :subtle)) { t("projects.settings.life_cycle.non_defined") } + end + else + life_cycle_definitions.each do |definition| + component.with_row(data: { 'projects--settings--border-box-filter-target': 'searchItem' }, + test_selector: "project-life-cycle-step-#{definition.id}") do + render(Projects::Settings::LifeCycles::StepComponent.new(project:, definition:)) + end + end + end + end + end + flex.with_row do + render Primer::Beta::Text.new(display: :none, + data: { + "projects--settings--border-box-filter-target": "noResultsText", + }) do + I18n.t("js.autocompleter.notFoundText") + end + end + end +%> diff --git a/app/components/projects/settings/life_cycles/index_component.rb b/app/components/projects/settings/life_cycles/index_component.rb new file mode 100644 index 000000000000..403c26152629 --- /dev/null +++ b/app/components/projects/settings/life_cycles/index_component.rb @@ -0,0 +1,56 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects + module Settings + module LifeCycles + class IndexComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :project, + :life_cycle_definitions + + private + + def wrapper_data_attributes + { + controller: "projects--settings--border-box-filter", + "application-target": "dynamic", + "projects--settings--border-box-filter-clear-button-id-value": clear_button_id + } + end + + def clear_button_id + "border-box-filter-clear-button" + end + end + end + end +end diff --git a/app/components/projects/settings/life_cycles/show_page_header_component.html.erb b/app/components/projects/settings/life_cycles/show_page_header_component.html.erb new file mode 100644 index 000000000000..b6845944e263 --- /dev/null +++ b/app/components/projects/settings/life_cycles/show_page_header_component.html.erb @@ -0,0 +1,9 @@ +<%= render Primer::OpenProject::PageHeader.new do |header| %> + <%= header.with_title { t('projects.settings.life_cycle.header.title') } %> + <%= header.with_description do + t('projects.settings.life_cycle.header.description_html', + overview_url: project_path(project), + admin_settings_url: admin_settings_project_life_cycles_path) + end %> + <%= header.with_breadcrumbs(breadcrumb_items) %> +<% end %> diff --git a/app/components/projects/settings/life_cycles/show_page_header_component.rb b/app/components/projects/settings/life_cycles/show_page_header_component.rb new file mode 100644 index 000000000000..9641ab894d4a --- /dev/null +++ b/app/components/projects/settings/life_cycles/show_page_header_component.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# -- 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 Projects::Settings::LifeCycles + class ShowPageHeaderComponent < ApplicationComponent + include ApplicationHelper + + options :project + + def breadcrumb_items + [{ href: project_overview_path(project), text: project.name }, + { href: project_settings_general_path(project), text: I18n.t("label_project_settings") }, + t("projects.settings.life_cycle.header.title")] + end + end +end diff --git a/app/components/projects/settings/life_cycles/step_component.html.erb b/app/components/projects/settings/life_cycles/step_component.html.erb new file mode 100644 index 000000000000..d8bf4ebdd55f --- /dev/null +++ b/app/components/projects/settings/life_cycles/step_component.html.erb @@ -0,0 +1,30 @@ +<%= + flex_layout(align_items: :center, + justify_content: :space_between) do |step_container| + step_container.with_column(flex_layout: true) do |title_container| + title_container.with_column(pt: 1, mr: 3) do + render(Primer::Beta::Text.new(classes: 'filter-target-visible-text')) { definition.name } + end + title_container.with_column(pt: 1) do + render(Projects::LifeCycleTypeComponent.new(definition)) + end + end + # py: 1 quick fix: prevents the row from bouncing as the toggle switch currently changes height while toggling + step_container.with_column(py: 1, mr: 2) do + # buggy currently: + # small variant + status_label_position: :start leads to a small bounce while toggling + # behavior can be seen on primer's viewbook as well -> https://view-components-storybook.eastus.cloudapp.azure.com/view-components/lookbook/inspect/primer/alpha/toggle_switch/small + # quick fix: don't display loading indicator which is causing the bounce + render(Primer::Alpha::ToggleSwitch.new( + src: toggle_project_settings_life_cycle_path(definition_id: definition.id), + csrf_token: form_authenticity_token, + data: { test_selector: "toggle-project-life-cycle-#{definition.id}" }, + aria: { label: toggle_aria_label }, + checked: active_in_project?, + size: :small, + status_label_position: :start, + classes: "op-primer-adjustments__toggle-switch--hidden-loading-indicator", + )) + end + end +%> diff --git a/app/components/projects/settings/life_cycles/step_component.rb b/app/components/projects/settings/life_cycles/step_component.rb new file mode 100644 index 000000000000..3f32e3d5c24f --- /dev/null +++ b/app/components/projects/settings/life_cycles/step_component.rb @@ -0,0 +1,53 @@ +# -- 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 Projects + module Settings + module LifeCycles + class StepComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :definition, + :project + + def active_in_project? + project + .life_cycle_steps + .detect { |project_lc| project_lc.definition_id == definition.id } + &.active + end + + def toggle_aria_label + I18n.t("projects.settings.life_cycle.step.use_in_project", step: definition.name) + end + end + end + end +end diff --git a/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb b/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb index 374bb4ed0642..fae8cdf15c38 100644 --- a/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb @@ -5,7 +5,7 @@ }) do |custom_field_container| custom_field_container.with_column(flex_layout: true) do |title_container| title_container.with_column(pt: 1, mr: 2) do - render(Primer::Beta::Text.new) do + render(Primer::Beta::Text.new(classes: 'filter-target-visible-text')) do @project_custom_field.name end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_component.html.erb b/app/components/projects/settings/project_custom_field_sections/index_component.html.erb index b4044ee99669..f54e78c9f20b 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/index_component.html.erb @@ -3,7 +3,7 @@ flex_layout do |flex| flex.with_row do render(Primer::OpenProject::SubHeader.new) do |subheader| - subheader.with_filter_input(name: "project-custom-fields-mapping-filter", + subheader.with_filter_input(name: "border-box-filter", label: t('projects.settings.project_custom_fields.filter.label'), visually_hide_label: true, placeholder: t('projects.settings.project_custom_fields.filter.label'), @@ -14,8 +14,8 @@ show_clear_button: true, clear_button_id: clear_button_id, data: { - action: "input->projects--settings--project-custom-fields-mapping-filter#filterLists", - "projects--settings--project-custom-fields-mapping-filter-target": "filter" + action: "input->projects--settings--border-box-filter#filterLists", + "projects--settings--border-box-filter-target": "filter" }) end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_component.rb b/app/components/projects/settings/project_custom_field_sections/index_component.rb index 25b8a86e8b71..b405aa381d38 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_component.rb +++ b/app/components/projects/settings/project_custom_field_sections/index_component.rb @@ -45,14 +45,14 @@ def initialize(project:, project_custom_field_sections:) def wrapper_data_attributes { - controller: "projects--settings--project-custom-fields-mapping-filter", + controller: "projects--settings--border-box-filter", "application-target": "dynamic", - "projects--settings--project-custom-fields-mapping-filter-clear-button-id-value": clear_button_id + "projects--settings--border-box-filter-clear-button-id-value": clear_button_id } end def clear_button_id - "project-custom-fields-mapping-filter-clear-button" + "border-box-filter-clear-button" end end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb index 7cbbd5903e1d..e73cbd6c5e58 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb @@ -1,8 +1,7 @@ <%= render Primer::OpenProject::PageHeader.new do |header| %> <%= header.with_title(variant: :default) { t('projects.settings.project_custom_fields.header.title') } %> - <%= header.with_description { t('projects.settings.project_custom_fields.header.description', - overview_url: project_path(@project), - admin_settings_url: admin_settings_project_custom_fields_path - ).html_safe } %> + <%= header.with_description { t('projects.settings.project_custom_fields.header.description_html', + overview_url: project_path(project), + admin_settings_url: admin_settings_project_custom_fields_path) } %> <%= header.with_breadcrumbs(breadcrumb_items) %> <% end %> diff --git a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb index 13014aae6aab..fe1b93434a1f 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb +++ b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb @@ -33,14 +33,11 @@ module Projects::Settings::ProjectCustomFieldSections class IndexPageHeaderComponent < ApplicationComponent include ApplicationHelper - def initialize(project: nil) - super - @project = project - end + options :project def breadcrumb_items - [{ href: project_overview_path(@project.id), text: @project.name }, - { href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") }, + [{ href: project_overview_path(project), text: project.name }, + { href: project_settings_general_path(project), text: I18n.t("label_project_settings") }, t("settings.project_attributes.heading")] end end diff --git a/app/components/projects/settings/project_custom_field_sections/show_component.html.erb b/app/components/projects/settings/project_custom_field_sections/show_component.html.erb index 727d8901228b..f2c5ee4e3a7f 100644 --- a/app/components/projects/settings/project_custom_field_sections/show_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/show_component.html.erb @@ -13,7 +13,7 @@ end end section_header_container.with_column(flex_layout: true, justify_content: :flex_end) do |actions_container| - actions_container.with_column(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'bulkActionContainer' }) do + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do render(Primer::Beta::Button.new( tag: :a, href: enable_all_of_section_project_settings_project_custom_fields_path( @@ -25,14 +25,14 @@ scheme: :invisible, font_weight: :bold, color: :subtle, - 'aria-label': t('projects.settings.project_custom_fields.actions.label_enable_all'), + 'aria-label': t('projects.settings.actions.label_enable_all'), data: { 'turbo-method': :put, 'turbo-stream': true, test_selector: "enable-all-project-custom-field-mappings-#{@project_custom_field_section.id}" } )) do |button| button.with_leading_visual_icon(icon: 'check-circle', color: :subtle) - t('projects.settings.project_custom_fields.actions.label_enable_all') + t('projects.settings.actions.label_enable_all') end end - actions_container.with_column(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'bulkActionContainer' }) do + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do render(Primer::Beta::Button.new( tag: :a, href: disable_all_of_section_project_settings_project_custom_fields_path( @@ -44,11 +44,11 @@ scheme: :invisible, font_weight: :bold, color: :subtle, - 'aria-label': t('projects.settings.project_custom_fields.actions.label_disable_all'), + 'aria-label': t('projects.settings.actions.label_disable_all'), data: { 'turbo-method': :put, 'turbo-stream': true, test_selector: "disable-all-project-custom-field-mappings-#{@project_custom_field_section.id}" } )) do |button| button.with_leading_visual_icon(icon: 'x-circle', color: :subtle) - t('projects.settings.project_custom_fields.actions.label_disable_all') + t('projects.settings.actions.label_disable_all') end end end @@ -60,7 +60,7 @@ end else @project_custom_fields.each do |project_custom_field| - component.with_row(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'searchItem' }) do + component.with_row(data: { 'projects--settings--border-box-filter-target': 'searchItem' }) do render(Projects::Settings::ProjectCustomFieldSections::CustomFieldRowComponent.new( project: @project, project_custom_field:, diff --git a/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb new file mode 100644 index 000000000000..8c2da7abe2e4 --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/form_header_component.html.erb @@ -0,0 +1,38 @@ +<%#-- 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. + +++#%> + +<% html_title t(:label_administration), + t("settings.project_life_cycle_step_definitions.heading"), + t("settings.project_life_cycle_step_definitions.#{heading_scope}.heading") %> + +<%= render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t("settings.project_life_cycle_step_definitions.#{heading_scope}.heading") } + header.with_description { t("settings.project_life_cycle_step_definitions.new.description") } + header.with_breadcrumbs(breadcrumbs_items) +end %> diff --git a/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb b/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb new file mode 100644 index 000000000000..1c7dba5d796f --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/form_header_component.rb @@ -0,0 +1,52 @@ +#-- 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 Settings + module ProjectLifeCycleStepDefinitions + class FormHeaderComponent < ApplicationComponent + include MetaTagsHelper + + def heading_scope + action = model.persisted? ? :edit : :new + + type = model.is_a?(Project::StageDefinition) ? :stage : :gate + + "#{action}_#{type}" + end + + def breadcrumbs_items + [ + { href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_project_custom_fields_path, text: t("label_project_plural") }, + { href: admin_settings_project_life_cycle_step_definitions_path, text: t("settings.project_life_cycle_step_definitions.heading") }, + t("settings.project_life_cycle_step_definitions.#{heading_scope}.heading") + ] + end + end + end +end diff --git a/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb new file mode 100644 index 000000000000..3da4d4ceb4f5 --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb @@ -0,0 +1,91 @@ +<%#-- 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. + +++#%> + +<%= + flex_layout(data: wrapper_data_attributes) do |flex| + flex.with_row do + render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_filter_input( + name: "border-box-filter", + label: t("settings.project_life_cycle_step_definitions.filter.label"), + visually_hide_label: true, + placeholder: t("settings.project_life_cycle_step_definitions.filter.label"), + leading_visual: { + icon: :search, + size: :small + }, + show_clear_button: true, + data: { + action: "input->projects--settings--border-box-filter#filterLists", + "projects--settings--border-box-filter-target": "filter" + } + ) + end + end + + flex.with_row do + render(border_box_container(mb: 3, data: drop_target_config)) do |component| + component.with_header(font_weight: :bold, py: 2) do + flex_layout(justify_content: :space_between, align_items: :center) do |header_container| + header_container.with_column do + render(Primer::Beta::Text.new(font_weight: :bold)) do + I18n.t("settings.project_life_cycle_step_definitions.section_header") + end + end + end + end + if definitions.empty? + component.with_row do + render(Primer::Beta::Text.new(color: :subtle)) { t("settings.project_life_cycle_step_definitions.non_defined") } + end + else + definitions.each do |definition| + component.with_row(data: { "projects--settings--border-box-filter-target": "searchItem", **draggable_item_config(definition) }) do + render(Settings::ProjectLifeCycleStepDefinitions::RowComponent.new( + definition, + first?: definition == definitions.first, + last?: definition == definitions.last, + )) + end + end + end + end + end + flex.with_row do + render Primer::Beta::Text.new( + display: :none, + data: { + "projects--settings--border-box-filter-target": "noResultsText", + } + ) do + I18n.t("js.autocompleter.notFoundText") + end + end + end +%> diff --git a/app/components/settings/project_life_cycle_step_definitions/index_component.rb b/app/components/settings/project_life_cycle_step_definitions/index_component.rb new file mode 100644 index 000000000000..f34bf7f8a911 --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/index_component.rb @@ -0,0 +1,61 @@ +#-- 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 Settings + module ProjectLifeCycleStepDefinitions + class IndexComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + options :definitions + + private + + def wrapper_data_attributes + { + controller: "projects--settings--border-box-filter generic-drag-and-drop", + "application-target": "dynamic" + } + end + + def drop_target_config + { + "is-drag-and-drop-target": true, + "target-container-accessor": "& > ul", + "target-allowed-drag-type": "life-cycle-step-definition" + } + end + + def draggable_item_config(definition) + { + "draggable-type": "life-cycle-step-definition", + "drop-url": drop_admin_settings_project_life_cycle_step_definition_path(definition) + } + end + end + end +end diff --git a/app/components/settings/project_life_cycle_step_definitions/index_header_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/index_header_component.html.erb new file mode 100644 index 000000000000..7c1d55449bde --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/index_header_component.html.erb @@ -0,0 +1,69 @@ +<%#-- 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. + +++#%> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t("settings.project_life_cycle_step_definitions.heading") } + header.with_description { t("settings.project_life_cycle_step_definitions.heading_description") } + header.with_breadcrumbs(breadcrumbs_items) + end +%> + +<%= + render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_action_component do + render(Primer::Alpha::ActionMenu.new( + anchor_align: :end) + ) do |menu| + menu.with_show_button( + scheme: :primary, + aria: { label: I18n.t("settings.project_life_cycle_step_definitions.label_add_description") }, + ) do |button| + button.with_leading_visual_icon(icon: :plus) + button.with_trailing_action_icon(icon: :"triangle-down") + I18n.t("settings.project_life_cycle_step_definitions.label_add") + end + + menu.with_item( + label: I18n.t("settings.project_life_cycle_step_definitions.label_add_stage"), + href: new_stage_admin_settings_project_life_cycle_step_definitions_path + ) do |item| + item.with_leading_visual_icon(icon: "git-commit") + end + + menu.with_item( + label: I18n.t("settings.project_life_cycle_step_definitions.label_add_gate"), + href: new_gate_admin_settings_project_life_cycle_step_definitions_path + ) do |item| + item.with_leading_visual_icon(icon: "diamond") + end + end + end + end +%> diff --git a/app/components/settings/project_life_cycle_step_definitions/index_header_component.rb b/app/components/settings/project_life_cycle_step_definitions/index_header_component.rb new file mode 100644 index 000000000000..6622a0f61b88 --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/index_header_component.rb @@ -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. +#++ + +module Settings + module ProjectLifeCycleStepDefinitions + class IndexHeaderComponent < ApplicationComponent + + def breadcrumbs_items + [ + { href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_project_custom_fields_path, text: t("label_project_plural") }, + t("settings.project_life_cycle_step_definitions.heading") + ] + end + end + end +end diff --git a/app/components/settings/project_life_cycle_step_definitions/row_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/row_component.html.erb new file mode 100644 index 000000000000..91c3064f4c4e --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/row_component.html.erb @@ -0,0 +1,88 @@ +<%#-- 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. + +++#%> + +<%= + flex_layout(align_items: :center, justify_content: :space_between) do |row_container| + row_container.with_column(flex_layout: true, classes: "gap-2") do |title_container| + title_container.with_column do + render(Primer::OpenProject::DragHandle.new) + end + title_container.with_column do + render(Primer::Beta::Link.new( + classes: 'filter-target-visible-text', + href: edit_admin_settings_project_life_cycle_step_definition_path(definition), + font_weight: :bold + )) do + definition.name + end + end + title_container.with_column do + render(Projects::LifeCycleTypeComponent.new(definition)) + end + title_container.with_column do + render(Primer::Beta::Text.new) { t("project.count", count: definition.project_count) } + end + end + row_container.with_column do + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button(icon: "kebab-horizontal", "aria-label": t(:button_actions), scheme: :invisible) + + menu.with_item( + label: t(:label_edit), + href: edit_admin_settings_project_life_cycle_step_definition_path(definition) + ) do |item| + item.with_leading_visual_icon(icon: :pencil) + end + + unless first? + move_action(menu:, move_to: :highest, label: t("label_agenda_item_move_to_top"), icon: "move-to-top") + move_action(menu:, move_to: :higher, label: t("label_agenda_item_move_up"), icon: "chevron-up") + end + unless last? + move_action(menu:, move_to: :lower, label: t("label_agenda_item_move_down"), icon: "chevron-down") + move_action(menu:, move_to: :lowest, label: t("label_agenda_item_move_to_bottom"), icon: "move-to-bottom") + end + + menu.with_item( + label: t(:text_destroy), + scheme: :danger, + href: admin_settings_project_life_cycle_step_definition_path(definition), + form_arguments: { + method: :delete, + data: { + confirm: t("text_are_you_sure_with_project_life_cycle_step") + } + } + ) do |item| + item.with_leading_visual_icon(icon: :trash) + end + end + end + end +%> diff --git a/app/components/settings/project_life_cycle_step_definitions/row_component.rb b/app/components/settings/project_life_cycle_step_definitions/row_component.rb new file mode 100644 index 000000000000..1f165751a6c4 --- /dev/null +++ b/app/components/settings/project_life_cycle_step_definitions/row_component.rb @@ -0,0 +1,53 @@ +#-- 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 Settings + module ProjectLifeCycleStepDefinitions + class RowComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + alias_method :definition, :model + + options :first?, :last? + + private + + def move_action(menu:, move_to:, label:, icon:) + menu.with_item( + label:, + href: move_admin_settings_project_life_cycle_step_definition_path(definition, move_to:), + form_arguments: { + method: :patch + } + ) do |item| + item.with_leading_visual_icon(icon:) + end + end + end + end +end diff --git a/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb b/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb new file mode 100644 index 000000000000..402101a25870 --- /dev/null +++ b/app/controllers/admin/settings/project_life_cycle_step_definitions_controller.rb @@ -0,0 +1,127 @@ +#-- 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 Admin::Settings + class ProjectLifeCycleStepDefinitionsController < ::Admin::SettingsController + menu_item :project_life_cycle_step_definitions_settings + + before_action :check_feature_flag + + before_action :find_definitions, only: %i[index] + before_action :find_definition, only: %i[edit update destroy move drop] + + def index; end + + def new_stage + @definition = Project::StageDefinition.new + + render :form + end + + def new_gate + @definition = Project::GateDefinition.new + + render :form + end + + def edit + render :form + end + + def create + @definition = Project::LifeCycleStepDefinition.new(definition_params) + + if @definition.save + flash[:notice] = I18n.t(:notice_successful_create) + redirect_to action: :index, status: :see_other + else + render :form, status: :unprocessable_entity + end + end + + def update + if @definition.update(definition_params) + flash[:notice] = I18n.t(:notice_successful_update) + redirect_to action: :index, status: :see_other + else + render :form, status: :unprocessable_entity + end + end + + def destroy + if @definition.destroy + flash[:notice] = I18n.t(:notice_successful_delete) + else + # TODO: handle better + flash[:error] = I18n.t(:notice_bad_request) + end + + redirect_to action: :index, status: :see_other + end + + def move + if @definition.update(params.permit(:move_to)) + flash[:notice] = I18n.t(:notice_successful_update) + else + # TODO: handle better + flash[:error] = I18n.t(:notice_bad_request) + end + + redirect_to action: :index, status: :see_other + end + + def drop + if @definition.update(params.permit(:position)) + flash[:notice] = I18n.t(:notice_successful_update) + else + # TODO: handle better + flash[:error] = I18n.t(:notice_bad_request) + end + + redirect_to action: :index, status: :see_other + end + + private + + def check_feature_flag + render_404 unless OpenProject::FeatureDecisions.stages_and_gates_active? + end + + def find_definitions + @definitions = Project::LifeCycleStepDefinition.with_project_count + end + + def find_definition + @definition = Project::LifeCycleStepDefinition.find(params[:id]) + end + + def definition_params + params.require(:project_life_cycle_step_definition).permit(:type, :name, :color_id) + end + end +end diff --git a/app/controllers/projects/settings/life_cycles_controller.rb b/app/controllers/projects/settings/life_cycles_controller.rb new file mode 100644 index 000000000000..c8f85edd0e89 --- /dev/null +++ b/app/controllers/projects/settings/life_cycles_controller.rb @@ -0,0 +1,81 @@ +# -- 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 Projects::Settings::LifeCyclesController < Projects::SettingsController + include OpTurbo::ComponentStream + + before_action :deny_access_on_feature_flag + + before_action :load_life_cycle_definitions, only: %i[show enable_all disable_all] + + menu_item :settings_life_cycles + + def show; end + + def toggle + definition = Project::LifeCycleStepDefinition.where(id: params[:definition_id]) + + upsert_steps(definition, active: params["value"]) + end + + def disable_all + upsert_steps(@life_cycle_definitions, active: false) + + redirect_to action: :show + end + + def enable_all + upsert_steps(@life_cycle_definitions, active: true) + + redirect_to action: :show + end + + private + + def load_life_cycle_definitions + @life_cycle_definitions = Project::LifeCycleStepDefinition.order(position: :asc) + end + + def deny_access_on_feature_flag + deny_access(not_found: true) unless OpenProject::FeatureDecisions.stages_and_gates_active? + end + + def upsert_steps(definitions, active:) + Project::LifeCycleStep.upsert_all( + definitions.map do |definition| + { + project_id: @project.id, + definition_id: definition.id, + active:, + type: definition.step_class + } + end, + unique_by: %i[project_id definition_id] + ) + end +end diff --git a/app/forms/projects/life_cycle_step_definitions/form.rb b/app/forms/projects/life_cycle_step_definitions/form.rb new file mode 100644 index 000000000000..56e39be9706e --- /dev/null +++ b/app/forms/projects/life_cycle_step_definitions/form.rb @@ -0,0 +1,58 @@ +#-- 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 Projects::LifeCycleStepDefinitions + class Form < ApplicationForm + form do |f| + f.hidden(name: :type) unless model.persisted? + + f.text_field( + label: attribute_name(:name), + name: :name, + required: true, + input_width: :medium + ) + + f.color_select_list( + label: attribute_name(:color), + name: :color, + required: true + ) + + f.submit( + scheme: :primary, + name: :submit, + label: submit_label + ) + end + + def submit_label + I18n.t(model.persisted? ? :button_update : :button_create) + end + end +end diff --git a/app/models/project/gate_definition.rb b/app/models/project/gate_definition.rb index a51561075728..a0cfef69352e 100644 --- a/app/models/project/gate_definition.rb +++ b/app/models/project/gate_definition.rb @@ -32,4 +32,8 @@ class Project::GateDefinition < Project::LifeCycleStepDefinition foreign_key: :definition_id, inverse_of: :definition, dependent: :destroy + + def step_class + Project::Gate + end end diff --git a/app/models/project/life_cycle_step_definition.rb b/app/models/project/life_cycle_step_definition.rb index d511cc284cc4..1b2829616cfc 100644 --- a/app/models/project/life_cycle_step_definition.rb +++ b/app/models/project/life_cycle_step_definition.rb @@ -27,6 +27,8 @@ #++ class Project::LifeCycleStepDefinition < ApplicationRecord + include ::Scopes::Scoped + has_many :life_cycle_steps, class_name: "Project::LifeCycleStep", foreign_key: :definition_id, @@ -42,13 +44,21 @@ class Project::LifeCycleStepDefinition < ApplicationRecord acts_as_list - def initialize(*args) - if instance_of? Project::LifeCycleStepDefinition - # Do not allow directly instantiating this class - raise NotImplementedError, "Cannot instantiate the base Project::LifeCycleStepDefinition class directly. " \ - "Use Project::StageDefinition or Project::GateDefinition instead." - end + default_scope { order(:position) } + + scopes :with_project_count + + # def initialize(*args) + # if instance_of? Project::LifeCycleStepDefinition + # # Do not allow directly instantiating this class + # raise NotImplementedError, "Cannot instantiate the base Project::LifeCycleStepDefinition class directly. " \ + # "Use Project::StageDefinition or Project::GateDefinition instead." + # end + # + # super + # end - super + def step_class + raise NotImplementedError end end diff --git a/app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb b/app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb new file mode 100644 index 000000000000..53cf99c582dc --- /dev/null +++ b/app/models/project/life_cycle_step_definitions/scopes/with_project_count.rb @@ -0,0 +1,48 @@ +#-- 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 Project::LifeCycleStepDefinitions::Scopes + module WithProjectCount + extend ActiveSupport::Concern + + class_methods do + def with_project_count + project_counts = Project::LifeCycleStep + .where(active: true) + .group(:definition_id) + .select(:definition_id, "COUNT(project_id) AS count") + + Project::LifeCycleStepDefinition + .with(project_counts:) + .joins("LEFT OUTER JOIN project_counts ON #{quoted_table_name}.id = project_counts.definition_id") + .select("#{quoted_table_name}.*") + .select("COALESCE(project_counts.count, 0) AS project_count") + end + end + end +end diff --git a/app/models/project/stage_definition.rb b/app/models/project/stage_definition.rb index 91ae98584def..f373d13cd646 100644 --- a/app/models/project/stage_definition.rb +++ b/app/models/project/stage_definition.rb @@ -32,4 +32,8 @@ class Project::StageDefinition < Project::LifeCycleStepDefinition foreign_key: :definition_id, inverse_of: :definition, dependent: :destroy + + def step_class + Project::Stage + end end diff --git a/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb b/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb new file mode 100644 index 000000000000..571dadab4433 --- /dev/null +++ b/app/views/admin/settings/project_life_cycle_step_definitions/form.html.erb @@ -0,0 +1,40 @@ +<%#-- 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. + +++#%> + +<%= render Settings::ProjectLifeCycleStepDefinitions::FormHeaderComponent.new(@definition) %> + +<%= error_messages_for @definition %> + +<%= + primer_form_with( + model: [:admin, :settings, @definition.becomes(Project::LifeCycleStepDefinition)] + ) do |f| + render Projects::LifeCycleStepDefinitions::Form.new(f) + end +%> diff --git a/app/views/admin/settings/project_life_cycle_step_definitions/index.html.erb b/app/views/admin/settings/project_life_cycle_step_definitions/index.html.erb new file mode 100644 index 000000000000..cf8877a719e5 --- /dev/null +++ b/app/views/admin/settings/project_life_cycle_step_definitions/index.html.erb @@ -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. + +++#%> + +<% html_title t(:label_administration), t("settings.project_life_cycle_step_definitions.heading") %> + +<%= render Settings::ProjectLifeCycleStepDefinitions::IndexHeaderComponent.new %> +<%= render Settings::ProjectLifeCycleStepDefinitions::IndexComponent.new(definitions: @definitions) %> diff --git a/app/views/projects/settings/life_cycles/show.html.erb b/app/views/projects/settings/life_cycles/show.html.erb new file mode 100644 index 000000000000..47d2965a3c50 --- /dev/null +++ b/app/views/projects/settings/life_cycles/show.html.erb @@ -0,0 +1,34 @@ +<%#-- 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. + +++#%> +