From 61d2daa4cdb78d4e5181a7c2867a6510363354a2 Mon Sep 17 00:00:00 2001 From: Christophe Bliard Date: Wed, 11 Sep 2024 18:37:22 +0200 Subject: [PATCH] Display warning text when progress calculation changes There is also a new helper to display HTML content in forms. --- .../progress_calculation_mode_form.rb | 44 ------------ app/forms/settings_form_decorator.rb | 15 ++-- .../settings/progress_tracking/show.html.erb | 68 ++++++++---------- .../admin/progress-tracking.controller.ts | 24 ++++--- .../forms/dsl/html_content_input.rb | 28 ++++++++ .../open_project/forms/dsl/input_methods.rb | 4 ++ .../open_project/forms/html_content.html.erb | 1 + lib/primer/open_project/forms/html_content.rb | 21 ++++++ spec/features/admin/progress_tracking_spec.rb | 72 +++++++++++++++++++ 9 files changed, 178 insertions(+), 99 deletions(-) delete mode 100644 app/forms/admin/settings/progress_tracking/progress_calculation_mode_form.rb create mode 100644 lib/primer/open_project/forms/dsl/html_content_input.rb create mode 100644 lib/primer/open_project/forms/html_content.html.erb create mode 100644 lib/primer/open_project/forms/html_content.rb create mode 100644 spec/features/admin/progress_tracking_spec.rb diff --git a/app/forms/admin/settings/progress_tracking/progress_calculation_mode_form.rb b/app/forms/admin/settings/progress_tracking/progress_calculation_mode_form.rb deleted file mode 100644 index 1c49792873a7..000000000000 --- a/app/forms/admin/settings/progress_tracking/progress_calculation_mode_form.rb +++ /dev/null @@ -1,44 +0,0 @@ -# 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 Admin - module Settings - module ProgressTracking - class ProgressCalculationModeForm < ApplicationForm - settings_form do |form| - form.radio_button_group( - name: "work_package_done_ratio", - values: WorkPackage::DONE_RATIO_OPTIONS - ) - end - end - end - end -end diff --git a/app/forms/settings_form_decorator.rb b/app/forms/settings_form_decorator.rb index 610b1c6e3050..3d79ef6d4018 100644 --- a/app/forms/settings_form_decorator.rb +++ b/app/forms/settings_form_decorator.rb @@ -61,7 +61,7 @@ def check_box(name:, **options) form.check_box(name:, **options) end - def radio_button_group(name:, values:, **options) + def radio_button_group(name:, values:, button_options: {}, **options) radio_group_options = options.reverse_merge( label: setting_label(name) ) @@ -71,13 +71,14 @@ def radio_button_group(name:, values:, **options) **radio_group_options ) do |radio_group| values.each do |value| - button_options = options.merge( - value:, - checked: setting_value(name) == value, - label: setting_label(name, value), - caption: setting_caption_html(name, value) + radio_group.radio_button( + **button_options.reverse_merge( + value:, + checked: setting_value(name) == value, + label: setting_label(name, value), + caption: setting_caption_html(name, value) + ) ) - radio_group.radio_button(**button_options) end end end diff --git a/app/views/admin/settings/progress_tracking/show.html.erb b/app/views/admin/settings/progress_tracking/show.html.erb index bc67d8dd0b71..1231e32b00f1 100644 --- a/app/views/admin/settings/progress_tracking/show.html.erb +++ b/app/views/admin/settings/progress_tracking/show.html.erb @@ -38,41 +38,35 @@ See COPYRIGHT and LICENSE files for more details. end %> -<%= primer_form_with(scope: :settings, action: :update, method: :patch) do |f| %> - <%= render Admin::Settings::ProgressTracking::ProgressCalculationModeForm.new(f) %> - <%= - render_inline_settings_form(f, &:submit) - %> -<% end %> - -<%= styled_form_tag({ action: :update }, method: :patch) do %> -
-
- <%= setting_select :work_package_done_ratio, - WorkPackage::DONE_RATIO_OPTIONS.collect { |i| [t("setting_work_package_done_ratio_#{i}"), i] }, - container_class: "-middle", - data: { admin__progress_tracking_target: "progressCalculationModeSelect", - action: "admin--progress-tracking#displayWarning" } %> -
<%= - if OpenProject::FeatureDecisions.percent_complete_edition_active? - t("setting_work_package_done_ratio_explanation_html") - else - # This condition branch to be removed in 15.0 with :percent_complete_edition feature flag removal - t("setting_work_package_done_ratio_explanation_pre_14_4_without_percent_complete_edition_html") +<%= +primer_form_with( + scope: :settings, action: :update, method: :patch, + data: { + application_target: "dynamic", + controller: "admin--progress-tracking", + admin__progress_tracking_target: "progressCalculationModeRadioGroup", + admin__progress_tracking_percent_complete_edition_active_value: OpenProject::FeatureDecisions.percent_complete_edition_active?, + admin__progress_tracking_initial_mode_value: Setting.work_package_done_ratio + } +) do |f| + render_inline_settings_form(f) do |form| + form.radio_button_group( + name: "work_package_done_ratio", + values: WorkPackage::DONE_RATIO_OPTIONS, + button_options: { + data: { action: "admin--progress-tracking#displayWarning" } + } + ) + form.html_content do + tag.div(class: "op-toast -warning -with-bottom-spacing", + hidden: true, + data: { admin__progress_tracking_target: "warningToast" }) do + tag.div(class: "op-toast--content") do + tag.p(data: { admin__progress_tracking_target: "warningText" }) end - %>
-
- -
- - <%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %> -<% end %> + end + end + form.submit + end +end +%> diff --git a/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts index fcba621f68d6..05f3b5f97b63 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts @@ -32,25 +32,22 @@ import { Controller } from '@hotwired/stimulus'; export default class ProgressTrackingController extends Controller { static values = { + initialMode: String, percentCompleteEditionActive: Boolean, }; static targets = [ - 'progressCalculationModeSelect', + 'progressCalculationModeRadioGroup', 'warningText', 'warningToast', ]; + declare readonly initialModeValue:string; declare readonly percentCompleteEditionActiveValue:boolean; - declare readonly progressCalculationModeSelectTarget:HTMLSelectElement; + declare readonly progressCalculationModeRadioGroupTarget:HTMLElement; declare readonly warningTextTarget:HTMLElement; declare readonly warningToastTarget:HTMLElement; - private initialMode:string; - - connect() { - this.initialMode = this.progressCalculationModeSelectTarget.value; - } displayWarning() { const warningMessageHtml = this.getWarningMessageHtml(); @@ -62,21 +59,26 @@ export default class ProgressTrackingController extends Controller { } } + getSelectedMode() { + const checkedRadio = this.progressCalculationModeRadioGroupTarget.querySelector('input:checked') as HTMLInputElement; + return checkedRadio?.value || ''; + } + getWarningMessageHtml():string { - const newMode = this.progressCalculationModeSelectTarget.value; - if (newMode === this.initialMode) { + const selectedMode = this.getSelectedMode(); + if (selectedMode === this.initialModeValue || !selectedMode) { return ''; } // to be removed in 15.0 with :percent_complete_edition feature flag removal - if (!this.percentCompleteEditionActiveValue && newMode === 'field') { + if (!this.percentCompleteEditionActiveValue && selectedMode === 'field') { return I18n.t( 'js.admin.work_packages_settings.warning_progress_calculation_mode_change_from_status_to_field_pre_14_4_without_percent_complete_edition_html', ); } return I18n.t( - `js.admin.work_packages_settings.warning_progress_calculation_mode_change_from_${this.initialMode}_to_${newMode}_html`, + `js.admin.work_packages_settings.warning_progress_calculation_mode_change_from_${this.initialModeValue}_to_${selectedMode}_html`, ); } } diff --git a/lib/primer/open_project/forms/dsl/html_content_input.rb b/lib/primer/open_project/forms/dsl/html_content_input.rb new file mode 100644 index 000000000000..95e720501e79 --- /dev/null +++ b/lib/primer/open_project/forms/dsl/html_content_input.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + module Dsl + class HtmlContentInput < Primer::Forms::Dsl::Input + def initialize(**system_arguments, &html_block) + @html_block = html_block + + super(**system_arguments) + end + + def to_component + HtmlContent.new(&@html_block) + end + + def type + :html_content + end + + def name = nil + def form_control? = false + end + end + end + end +end diff --git a/lib/primer/open_project/forms/dsl/input_methods.rb b/lib/primer/open_project/forms/dsl/input_methods.rb index 8b9a74cf3d9a..bfb569e38ad0 100644 --- a/lib/primer/open_project/forms/dsl/input_methods.rb +++ b/lib/primer/open_project/forms/dsl/input_methods.rb @@ -24,6 +24,10 @@ def rich_text_area(**) def storage_manual_project_folder_selection(**) add_input StorageManualProjectFolderSelectionInput.new(builder: @builder, form: @form, **) end + + def html_content(**, &) + add_input HtmlContentInput.new(builder: @builder, form: @form, **, &) + end end end end diff --git a/lib/primer/open_project/forms/html_content.html.erb b/lib/primer/open_project/forms/html_content.html.erb new file mode 100644 index 000000000000..0d6a044df122 --- /dev/null +++ b/lib/primer/open_project/forms/html_content.html.erb @@ -0,0 +1 @@ +<%= rendered_html_content %> diff --git a/lib/primer/open_project/forms/html_content.rb b/lib/primer/open_project/forms/html_content.rb new file mode 100644 index 000000000000..e25c65bf857f --- /dev/null +++ b/lib/primer/open_project/forms/html_content.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Primer + module OpenProject + module Forms + # :nodoc: + class HtmlContent < Primer::Forms::BaseComponent + def initialize(&html_block) + super() + @html_block = html_block + end + + def rendered_html_content + @view_context.capture do + @view_context.instance_exec(&@html_block) + end + end + end + end + end +end diff --git a/spec/features/admin/progress_tracking_spec.rb b/spec/features/admin/progress_tracking_spec.rb new file mode 100644 index 000000000000..8cfa7d3db5f1 --- /dev/null +++ b/spec/features/admin/progress_tracking_spec.rb @@ -0,0 +1,72 @@ +#-- 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" + +RSpec.describe "Progress tracking admin page", :cuprite, :js, + with_flag: { percent_complete_edition: true } do + include ActionView::Helpers::SanitizeHelper + include Toasts::Expectations + + shared_let(:admin) { create(:admin) } + + it "displays a warning when changing progress calculation mode" do + Setting.work_package_done_ratio = "field" + login_as(admin) + visit admin_settings_progress_tracking_path + + # change from work-based to status-based + expect(page).to have_field("Work-based", checked: true) + expect(page).to have_field("Status-based", checked: false) + + find(:radio_button, "Status-based").click + + expected_warning_text = + I18n.t("js.admin.work_packages_settings.warning_progress_calculation_mode_change_from_field_to_status_html") + expected_warning_text = strip_tags(expected_warning_text)[..80] # take only the beginning of the text + expect(page).to have_text(expected_warning_text) + + click_on "Save" + expect_and_dismiss_toaster(message: "Successful update.") + expect(Setting.find_by(name: "work_package_done_ratio").value).to eq("status") + + # now change from status-based to work-based + expect(page).to have_field("Work-based", checked: false) + expect(page).to have_field("Status-based", checked: true) + + find(:radio_button, "Work-based").click + expected_warning_text = + I18n.t("js.admin.work_packages_settings.warning_progress_calculation_mode_change_from_status_to_field_html") + expected_warning_text = strip_tags(expected_warning_text)[..80] # take only the beginning of the text + expect(page).to have_text(expected_warning_text) + + click_on "Save" + expect_and_dismiss_toaster(message: "Successful update.") + expect(Setting.find_by(name: "work_package_done_ratio").value).to eq("field") + end +end