From 6d84e0b13517790472395eef8bd4aa98569a3957 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:09 -0300 Subject: [PATCH 01/24] [#59885] Impl. DeleteDialog for structured meetings Implements the `DangerConfirmationDialog` for both one-time meetings and recurring meeting occurences. N.B. this does not change the UI for classic meetings. --- .../meetings/delete_dialog_component.html.erb | 46 ++++++++++ .../meetings/delete_dialog_component.rb | 73 +++++++++++++++ .../meetings/header_component.html.erb | 7 +- .../app/components/meetings/row_component.rb | 15 +-- .../app/controllers/meetings_controller.rb | 9 +- modules/meeting/config/locales/en.yml | 17 +++- modules/meeting/config/routes.rb | 6 +- .../lib/open_project/meeting/engine.rb | 2 +- .../meetings/delete_dialog_component_spec.rb | 92 +++++++++++++++++++ .../structured_meeting_crud_spec.rb | 10 +- .../spec/support/pages/meetings/index.rb | 2 +- 11 files changed, 255 insertions(+), 24 deletions(-) create mode 100644 modules/meeting/app/components/meetings/delete_dialog_component.html.erb create mode 100644 modules/meeting/app/components/meetings/delete_dialog_component.rb create mode 100644 modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb diff --git a/modules/meeting/app/components/meetings/delete_dialog_component.html.erb b/modules/meeting/app/components/meetings/delete_dialog_component.html.erb new file mode 100644 index 00000000000..17de1f160a1 --- /dev/null +++ b/modules/meeting/app/components/meetings/delete_dialog_component.html.erb @@ -0,0 +1,46 @@ +<%#-- 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::DangerConfirmationDialog.new( + id:, + title:, + form_arguments: { + action: polymorphic_path([@project, @meeting.becomes(Meeting)]), + method: :delete, + data: { turbo: true } + } + )) do |dialog| + dialog.with_confirmation_message do |message| + message.with_heading(tag: :h2) { heading } + message.with_description_content(confirmation_message) + end + dialog.with_confirmation_check_box_content(I18n.t("text_permanent_delete_confirmation_checkbox_label")) + end +%> diff --git a/modules/meeting/app/components/meetings/delete_dialog_component.rb b/modules/meeting/app/components/meetings/delete_dialog_component.rb new file mode 100644 index 00000000000..d78eb65da65 --- /dev/null +++ b/modules/meeting/app/components/meetings/delete_dialog_component.rb @@ -0,0 +1,73 @@ +# 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 Meetings + class DeleteDialogComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + + def initialize(meeting:, project:) + super + + @meeting = meeting + @project = project + end + + delegate :recurring_meeting, to: :@meeting + + private + + def id = "delete-meeting-dialog" + + def title + if recurring_meeting.present? + I18n.t("meeting.delete_dialog.occurrence.title") + else + I18n.t("meeting.delete_dialog.one_time.title") + end + end + + def heading + if recurring_meeting.present? + I18n.t("meeting.delete_dialog.occurrence.heading") + else + I18n.t("meeting.delete_dialog.one_time.heading") + end + end + + def confirmation_message + if recurring_meeting.present? + t("meeting.delete_dialog.occurrence.confirmation_message_html", name: recurring_meeting.title) + else + t("meeting.delete_dialog.one_time.confirmation_message_html") + end + end + end +end diff --git a/modules/meeting/app/components/meetings/header_component.html.erb b/modules/meeting/app/components/meetings/header_component.html.erb index 4f9e57a7c39..f4ae73d211b 100644 --- a/modules/meeting/app/components/meetings/header_component.html.erb +++ b/modules/meeting/app/components/meetings/header_component.html.erb @@ -95,9 +95,10 @@ menu.with_item(label: delete_label, scheme: :danger, - href: meeting_path(@meeting), - form_arguments: { - method: :delete, data: { confirm: t("text_are_you_sure"), turbo: 'false' } + href: polymorphic_path([:delete_dialog, @project, @meeting.becomes(Meeting)]), + tag: :a, + content_arguments: { + data: { controller: "async-dialog" } }) do |item| item.with_leading_visual_icon(icon: :trash) end if delete_enabled? diff --git a/modules/meeting/app/components/meetings/row_component.rb b/modules/meeting/app/components/meetings/row_component.rb index 60abe47f6ad..ea4ac880968 100644 --- a/modules/meeting/app/components/meetings/row_component.rb +++ b/modules/meeting/app/components/meetings/row_component.rb @@ -140,22 +140,15 @@ module Meetings menu.with_item(label: recurring_meeting.present? ? I18n.t(:label_recurring_meeting_delete) : I18n.t(:label_meeting_delete), scheme: :danger, - href: meeting_path(model), - form_arguments: { - method: :delete, data: { confirm: delete_confirm_message, turbo: false } + href: delete_dialog_meeting_path(model), + tag: :a, + content_arguments: { + data: { controller: "async-dialog" } }) do |item| item.with_leading_visual_icon(icon: :trash) end end - def delete_confirm_message - if recurring_meeting.present? - I18n.t(:label_recurring_meeting_delete_confirmation, name: recurring_meeting.title) - else - I18n.t("text_are_you_sure") - end - end - def recurring_label render(Primer::BaseComponent.new(tag: :span, color: :muted)) do concat render(Primer::Beta::Octicon.new(icon: :iterations, mr: 1, ml: 1)) diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index af3e93508f0..c635696abd8 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -27,7 +27,7 @@ #++ class MeetingsController < ApplicationController - before_action :load_and_authorize_in_optional_project, only: %i[index new new_dialog show create history] + before_action :load_and_authorize_in_optional_project, only: %i[index new new_dialog show create delete_dialog destroy history] before_action :verify_activities_module_activated, only: %i[history] before_action :determine_date_range, only: %i[history] before_action :determine_author, only: %i[history] @@ -178,6 +178,13 @@ class MeetingsController < ApplicationController end end + def delete_dialog + respond_with_dialog Meetings::DeleteDialogComponent.new( + meeting: @meeting, + project: @project + ) + end + def destroy # rubocop:disable Metrics/AbcSize recurring = @meeting.recurring_meeting diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 7a73e3e7348..f0bcc6d9f40 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -136,10 +136,6 @@ en: label_recurring_meeting_copy: "Copy as one-off" label_recurring_meeting_cancel: "Cancel this occurrence" label_recurring_meeting_delete: "Delete occurrence" - label_recurring_meeting_delete_confirmation: > - This meeting is part of a series called %{name}. - This will only delete this particular occurrence and not the entire series. - Do you want to continue? label_recurring_occurrence_delete_confirmation: > Any meeting information not in the template will be lost. Do you want to continue? @@ -222,6 +218,19 @@ en: structured_text: "Organize your meeting as a dynamic list of agenda items, optionally linking them to a work package." structured_text_copy: "Copying a meeting will currently not copy the associated meeting agenda items, just the details" copied: "Copied from Meeting #%{id}" + delete_dialog: + one_time: + title: "Delete meeting" + heading: "Delete this meeting?" + confirmation_message_html: > + Do you want to continue? + occurrence: + title: "Delete occurrence" + heading: "Delete this meeting occurrence?" + confirmation_message_html: > + This meeting is part of a series called %{name}. + This will only delete this particular occurrence and not the entire series. + Do you want to continue? meeting_section: untitled_title: "Untitled section" diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index 5bb5ebfeb3b..e8092b7c802 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -28,10 +28,13 @@ Rails.application.routes.draw do resources :projects, only: %i[] do - resources :meetings, only: %i[index new create show] do + resources :meetings, only: %i[index new create show destroy] do collection do get "menu" => "meetings/menus#show" end + member do + get :delete_dialog + end end resources :recurring_meetings, only: %i[index new create show destroy] end @@ -86,6 +89,7 @@ Rails.application.routes.draw do put :change_state post :notify get :history + get :delete_dialog end resources :agenda_items, controller: "meeting_agenda_items" do collection do diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index fccbea65629..6324fe0a6eb 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -70,7 +70,7 @@ module OpenProject::Meeting require: :member permission :delete_meetings, { - meetings: [:destroy], + meetings: %i[delete_dialog destroy], recurring_meetings: %i[destroy delete_scheduled] }, permissible_on: :project, diff --git a/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb b/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb new file mode 100644 index 00000000000..0f37640dd70 --- /dev/null +++ b/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb @@ -0,0 +1,92 @@ +# 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 "rails_helper" + +RSpec.describe Meetings::DeleteDialogComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:project) { build_stubbed(:project) } + let(:user) { build_stubbed(:user) } + + subject do + render_inline(described_class.new(meeting:, project:)) + page + end + + before do + login_as(user) + end + + describe "dialog form" do + let(:meeting) { build_stubbed(:meeting, project:) } + + context "without a current project" do + let(:project) { nil } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: meeting_path(meeting) + end + end + + context "with a current project" do + let(:project) { build_stubbed(:project) } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: project_meeting_path(project, meeting) + end + end + end + + describe "with a one-off meeting" do + let(:meeting) { build_stubbed(:meeting, project:) } + + it "shows a heading" do + expect(subject).to have_text "Delete this meeting?" + end + + it "shows a simple confirmation message" do + expect(subject).to have_text "Do you want to continue?" + end + end + + context "with an associated recurring/templated meeting" do + let(:series) { build_stubbed(:recurring_meeting) } + let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series) } + + it "shows a heading" do + expect(subject).to have_text "Delete this meeting occurrence?" + end + + it "shows a message that the meeting is part of a series" do + expect(subject).to have_text "meeting is part of a series" + end + end +end diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb index 8c6d89c72fc..6ea8d36a52d 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb @@ -212,11 +212,17 @@ RSpec.describe "Structured meetings CRUD", it "can delete a meeting and get back to the index page" do click_on("op-meetings-header-action-trigger") - accept_confirm(I18n.t("text_are_you_sure")) do - click_on "Delete meeting" + click_on "Delete meeting" + + within "#delete-meeting-dialog" do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" end expect(page).to have_current_path project_meetings_path(project) + + expect_flash(type: :success, message: "Successful deletion.") end context "when exporting as ICS" do diff --git a/modules/meeting/spec/support/pages/meetings/index.rb b/modules/meeting/spec/support/pages/meetings/index.rb index 0333069d815..c82efef56ef 100644 --- a/modules/meeting/spec/support/pages/meetings/index.rb +++ b/modules/meeting/spec/support/pages/meetings/index.rb @@ -130,7 +130,7 @@ module Pages::Meetings def expect_delete_action(meeting) within more_menu(meeting) do - expect(page).to have_button("Delete meeting") + expect(page).to have_link("Delete meeting") end end From 416e9ece475df1678fd088800cabae43e8e51acc Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:09 -0300 Subject: [PATCH 02/24] [#59885] Show DeleteDialog for meeting occurrences --- .../recurring_meetings/row_component.rb | 7 ++++--- modules/meeting/config/locales/en.yml | 3 --- .../recurring_meeting_crud_spec.rb | 19 ++++++++++++++----- .../support/pages/recurring_meeting/show.rb | 4 ++++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/modules/meeting/app/components/recurring_meetings/row_component.rb b/modules/meeting/app/components/recurring_meetings/row_component.rb index 5056956be62..a5ed2374d37 100644 --- a/modules/meeting/app/components/recurring_meetings/row_component.rb +++ b/modules/meeting/app/components/recurring_meetings/row_component.rb @@ -177,9 +177,10 @@ module RecurringMeetings menu.with_item( label: past? ? I18n.t(:label_recurring_meeting_delete) : I18n.t(:label_recurring_meeting_cancel), scheme: :danger, - href: current_project_meeting_path(meeting), - form_arguments: { - method: :delete, data: { confirm: I18n.t(:label_recurring_occurrence_delete_confirmation), turbo: false } + href: polymorphic_path([:delete_dialog, current_project, meeting.becomes(Meeting)]), + tag: :a, + content_arguments: { + data: { controller: "async-dialog" } } ) do |item| item.with_leading_visual_icon(icon: :trash) diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index f0bcc6d9f40..6b25735ced8 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -136,9 +136,6 @@ en: label_recurring_meeting_copy: "Copy as one-off" label_recurring_meeting_cancel: "Cancel this occurrence" label_recurring_meeting_delete: "Delete occurrence" - label_recurring_occurrence_delete_confirmation: > - Any meeting information not in the template will be lost. - Do you want to continue? label_recurring_meeting_restore: "Restore this occurrence" label_recurring_meeting_schedule: "Schedule" label_recurring_meeting_next_occurrence: "Next occurrence" diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index fdde6ba76c7..e880874c604 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -114,8 +114,11 @@ RSpec.describe "Recurring meetings CRUD", it "can cancel an occurrence" do show_page.visit! - accept_confirm(I18n.t(:label_recurring_occurrence_delete_confirmation)) do - show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.in_delete_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" end expect_flash(type: :success, message: "Successful cancellation.") @@ -154,11 +157,17 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_scheduled_meeting date: "01/07/2025 01:30 PM" show_page.expect_scheduled_actions date: "01/07/2025 01:30 PM" - accept_confirm(I18n.t(:label_recurring_occurrence_delete_confirmation)) do - show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.in_delete_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" end - wait_for_network_idle + expect_flash(type: :success, message: "Successful cancellation.") + + expect(page).to have_current_path(show_page.project_path) + show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" show_page.expect_cancelled_actions date: "12/31/2024 01:30 PM" end diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index f3319dac331..dd5d1085f31 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -117,6 +117,10 @@ module Pages::RecurringMeeting page.within("#new-meeting-dialog", &) end + def in_delete_dialog(&) + page.within("#delete-meeting-dialog", &) + end + def expect_no_meeting(date:) expect(page).to have_no_css("li", text: date) end From b995ab5720e0b0ffb24bf63b6838993822056a09 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:09 -0300 Subject: [PATCH 03/24] [#59885] Impl. DeleteDialog for recurring meetings Implements the primerised `DangerConfirmationDialog` for deletion of recurring meetings. --- .../delete_dialog_component.html.erb | 46 ++++++++++++ .../delete_dialog_component.rb | 55 ++++++++++++++ .../show_page_header_component.html.erb | 7 +- .../recurring_meetings_controller.rb | 11 ++- modules/meeting/config/locales/en.yml | 7 ++ modules/meeting/config/routes.rb | 7 +- .../lib/open_project/meeting/engine.rb | 2 +- .../delete_dialog_component_spec.rb | 75 +++++++++++++++++++ .../recurring_meeting_crud_spec.rb | 10 ++- .../support/pages/recurring_meeting/show.rb | 11 +++ 10 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 modules/meeting/app/components/recurring_meetings/delete_dialog_component.html.erb create mode 100644 modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb create mode 100644 modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb diff --git a/modules/meeting/app/components/recurring_meetings/delete_dialog_component.html.erb b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.html.erb new file mode 100644 index 00000000000..3533491c400 --- /dev/null +++ b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.html.erb @@ -0,0 +1,46 @@ +<%#-- 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::DangerConfirmationDialog.new( + id:, + title:, + form_arguments: { + action: polymorphic_path([@project, @recurring_meeting]), + method: :delete, + data: { turbo: true } + } + )) do |dialog| + dialog.with_confirmation_message do |message| + message.with_heading(tag: :h2) { I18n.t("recurring_meeting.delete_dialog.heading") } + message.with_description_content(confirmation_message) + end + dialog.with_confirmation_check_box_content(I18n.t("text_permanent_delete_confirmation_checkbox_label")) + end +%> diff --git a/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb new file mode 100644 index 00000000000..72368d123c1 --- /dev/null +++ b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb @@ -0,0 +1,55 @@ +# 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 RecurringMeetings + class DeleteDialogComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + + def initialize(recurring_meeting:, project:) + super + + @recurring_meeting = recurring_meeting + @project = project + end + + private + + def id = "delete-recurring-meeting-dialog" + + def title = I18n.t("recurring_meeting.delete_dialog.title") + + def confirmation_message + t("recurring_meeting.delete_dialog.confirmation_message_html", + title: @recurring_meeting.title, + count: @recurring_meeting.remaining_occurrences.count) + end + end +end diff --git a/modules/meeting/app/components/recurring_meetings/show_page_header_component.html.erb b/modules/meeting/app/components/recurring_meetings/show_page_header_component.html.erb index 12f5f0a0dd6..232fd2110bd 100644 --- a/modules/meeting/app/components/recurring_meetings/show_page_header_component.html.erb +++ b/modules/meeting/app/components/recurring_meetings/show_page_header_component.html.erb @@ -61,10 +61,11 @@ menu.with_item( label: I18n.t(:label_recurring_meeting_series_delete), - href: polymorphic_path([@project, @meeting]), + href: polymorphic_path([:delete_dialog, @project, @meeting]), scheme: :danger, - form_arguments: { - method: :delete, data: { confirm: t("text_are_you_sure"), turbo: 'false' } + tag: :a, + content_arguments: { + data: { controller: 'async-dialog' } } ) do |item| item.with_leading_visual_icon(icon: :trash) diff --git a/modules/meeting/app/controllers/recurring_meetings_controller.rb b/modules/meeting/app/controllers/recurring_meetings_controller.rb index 771b307967b..0bc946aca6a 100644 --- a/modules/meeting/app/controllers/recurring_meetings_controller.rb +++ b/modules/meeting/app/controllers/recurring_meetings_controller.rb @@ -9,10 +9,10 @@ class RecurringMeetingsController < ApplicationController include OpTurbo::DialogStreamHelper before_action :find_meeting, - only: %i[show update details_dialog destroy edit init + only: %i[show update details_dialog delete_dialog destroy edit init delete_scheduled template_completed download_ics notify end_series end_series_dialog] before_action :find_optional_project, - only: %i[index show new create update details_dialog destroy edit delete_scheduled notify] + only: %i[index show new create update details_dialog delete_dialog destroy edit delete_scheduled notify] before_action :authorize_global, only: %i[index new create] before_action :authorize, except: %i[index new create] before_action :get_scheduled_meeting, only: %i[delete_scheduled] @@ -156,6 +156,13 @@ class RecurringMeetingsController < ApplicationController redirect_to action: :show end + def delete_dialog + respond_with_dialog RecurringMeetings::DeleteDialogComponent.new( + recurring_meeting: @recurring_meeting, + project: @project + ) + end + def destroy if @recurring_meeting.destroy flash[:notice] = I18n.t(:notice_successful_delete) diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 6b25735ced8..663f46e8cb6 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -287,6 +287,13 @@ en: The following meetings are planned in the recurring meeting schedule but are not open yet. Every time a planned meeting starts, the next one will automatically be opened for you. You can also open planned meetings manually to import the template and start editing the agenda. + delete_dialog: + title: "Delete meeting series" + heading: "Permanently delete this meeting series?" + confirmation_message_html: > + Deleting %{title} will delete all %{count} meetings in this series. + + This action is not reversible. Please proceed with caution. notice_successful_notification: "Notification sent successfully" notice_timezone_missing: No time zone is set and %{zone} is assumed. To choose your time zone, please click here. diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index e8092b7c802..f24df91659d 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -36,7 +36,11 @@ Rails.application.routes.draw do get :delete_dialog end end - resources :recurring_meetings, only: %i[index new create show destroy] + resources :recurring_meetings, only: %i[index new create show destroy] do + member do + get :delete_dialog + end + end end resources :work_packages, only: %i[] do @@ -63,6 +67,7 @@ Rails.application.routes.draw do member do get :details_dialog get :download_ics + get :delete_dialog post :init post :delete_scheduled post :template_completed diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 6324fe0a6eb..f3082bb31f5 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -71,7 +71,7 @@ module OpenProject::Meeting permission :delete_meetings, { meetings: %i[delete_dialog destroy], - recurring_meetings: %i[destroy delete_scheduled] + recurring_meetings: %i[delete_dialog destroy delete_scheduled] }, permissible_on: :project, require: :member diff --git a/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb new file mode 100644 index 00000000000..4996ce890b7 --- /dev/null +++ b/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb @@ -0,0 +1,75 @@ +# 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 "rails_helper" + +RSpec.describe RecurringMeetings::DeleteDialogComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:project) { build_stubbed(:project) } + let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 6) } + let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting:) } + let(:user) { build_stubbed(:user) } + + subject do + render_inline(described_class.new(recurring_meeting:, project:)) + page + end + + before do + login_as(user) + end + + describe "dialog form" do + context "without a current project" do + let(:project) { nil } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: recurring_meeting_path(recurring_meeting) + end + end + + context "with a current project" do + let(:project) { build_stubbed(:project) } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: project_recurring_meeting_path(project, recurring_meeting) + end + end + end + + it "shows a heading" do + expect(subject).to have_text "Permanently delete this meeting series?" + end + + it "shows a confirmation message detailing the remaining occurrences in the series" do + expect(subject).to have_text "will delete all 6 meetings in this series" + end +end diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index e880874c604..7f14c2128e2 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -88,13 +88,17 @@ RSpec.describe "Recurring meetings CRUD", it "can delete a recurring meeting from the show page and return to the index page" do show_page.visit! - click_on "recurring-meeting-action-menu" + show_page.delete_meeting_series + show_page.in_delete_recurring_dialog do + page.check "I understand that this deletion cannot be reversed" - accept_confirm(I18n.t("text_are_you_sure")) do - click_on "Delete meeting series" + click_on "Delete permanently" end expect(page).to have_current_path meetings_path # check path + + expect_flash(type: :success, message: "Successful deletion.") + show_page.expect_no_meeting date: "12/31/2024 01:30 PM" end it "can use the 'Create from template' button" do diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index dd5d1085f31..c3753aa2d2d 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -113,6 +113,13 @@ module Pages::RecurringMeeting expect(page).to have_css("#new-meeting-dialog") end + def delete_meeting_series + page.find_test_selector("recurring-meeting-action-menu").click + click_on "Delete meeting series" + + expect(page).to have_css("#delete-recurring-meeting-dialog") + end + def in_edit_dialog(&) page.within("#new-meeting-dialog", &) end @@ -121,6 +128,10 @@ module Pages::RecurringMeeting page.within("#delete-meeting-dialog", &) end + def in_delete_recurring_dialog(&) + page.within("#delete-recurring-meeting-dialog", &) + end + def expect_no_meeting(date:) expect(page).to have_no_css("li", text: date) end From 76ed84402fb67441a918b625e587f3d3648bcbb7 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:09 -0300 Subject: [PATCH 04/24] Spec project-scoped recurring meeting deletion --- .../recurring_meeting_crud_spec.rb | 42 +++++++++++++++++-- .../support/pages/recurring_meeting/show.rb | 6 +++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index 7f14c2128e2..ac2ecbf8abd 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -85,7 +85,7 @@ RSpec.describe "Recurring meetings CRUD", RecurringMeetings::InitNextOccurrenceJob.perform_now(meeting, meeting.first_occurrence.to_time) end - it "can delete a recurring meeting from the show page and return to the index page" do + it "can delete a recurring meeting from the global show page and return to the index page" do show_page.visit! show_page.delete_meeting_series @@ -95,7 +95,23 @@ RSpec.describe "Recurring meetings CRUD", click_on "Delete permanently" end - expect(page).to have_current_path meetings_path # check path + expect(page).to have_current_path meetings_path + + expect_flash(type: :success, message: "Successful deletion.") + show_page.expect_no_meeting date: "12/31/2024 01:30 PM" + end + + it "can delete a recurring meeting from the project show page and return to the index page" do + show_page.visit_project! + + show_page.delete_meeting_series + show_page.in_delete_recurring_dialog do + page.check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" + end + + expect(page).to have_current_path project_meetings_path(project) expect_flash(type: :success, message: "Successful deletion.") show_page.expect_no_meeting date: "12/31/2024 01:30 PM" @@ -115,7 +131,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_open_meeting date: "01/07/2025 01:30 PM" end - it "can cancel an occurrence" do + it "can cancel an occurrence from the global show page" do show_page.visit! show_page.cancel_occurrence date: "12/31/2024 01:30 PM" @@ -127,6 +143,24 @@ RSpec.describe "Recurring meetings CRUD", expect_flash(type: :success, message: "Successful cancellation.") + expect(page).to have_current_path(show_page.path) + + show_page.expect_no_open_meeting date: "12/31/2024 01:30 PM" + show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" + end + + it "can cancel an occurrence from the project show page" do + show_page.visit_project! + + show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.in_delete_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" + end + + expect_flash(type: :success, message: "Successful cancellation.") + expect(page).to have_current_path(show_page.project_path) show_page.expect_no_open_meeting date: "12/31/2024 01:30 PM" @@ -170,7 +204,7 @@ RSpec.describe "Recurring meetings CRUD", expect_flash(type: :success, message: "Successful cancellation.") - expect(page).to have_current_path(show_page.project_path) + expect(page).to have_current_path(show_page.path) show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" show_page.expect_cancelled_actions date: "12/31/2024 01:30 PM" diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index c3753aa2d2d..677971057b8 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -38,6 +38,12 @@ module Pages::RecurringMeeting self.meeting = meeting end + def visit_project! + visit(project_path) + + wait_for_reload + end + def path recurring_meeting_path(meeting) end From abfe0d8f5919fe3c949e95ff0ed4a4571743185b Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:10 -0300 Subject: [PATCH 05/24] Fix missing data-test-selector attribute --- .../meeting/app/forms/meeting/project_autocompleter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/meeting/app/forms/meeting/project_autocompleter.rb b/modules/meeting/app/forms/meeting/project_autocompleter.rb index 728d9ff2922..3ff98e26ba0 100644 --- a/modules/meeting/app/forms/meeting/project_autocompleter.rb +++ b/modules/meeting/app/forms/meeting/project_autocompleter.rb @@ -33,9 +33,6 @@ class Meeting::ProjectAutocompleter < ApplicationForm id: "project_id", label: Project.model_name.human, required: true, - data: { - "test-selector": "project_id" - }, autocomplete_options: { with_search_icon: true, openDirectly: false, @@ -44,7 +41,10 @@ class Meeting::ProjectAutocompleter < ApplicationForm inputName: "project_id", inputValue: @project&.id, appendTo: "#new-meeting-dialog", - filters: [{ name: "user_action", operator: "=", values: ["meetings/create"] }] + filters: [{ name: "user_action", operator: "=", values: ["meetings/create"] }], + data: { + "test-selector": "project_id" + } } ) end From 5fbd892d4ed4f62f49c01ee3b82b1370e82b1bdb Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:10 -0300 Subject: [PATCH 06/24] Spec global structured meeting deletion --- .../structured_meeting_global_crud_spec.rb | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb new file mode 100644 index 00000000000..22ac4e822d5 --- /dev/null +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb @@ -0,0 +1,110 @@ +# 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 "../../support/pages/meetings/new" +require_relative "../../support/pages/structured_meeting/show" +require_relative "../../support/pages/meetings/index" + +RSpec.describe "Structured meetings global CRUD", + :js, + with_flag: { recurring_meetings: true } do + include Components::Autocompleter::NgSelectAutocompleteHelpers + + shared_let(:project) { create(:project, enabled_module_names: %w[meetings work_package_tracking]) } + shared_let(:user) do + create(:user, + lastname: "First", + member_with_permissions: { project => %i[view_meetings create_meetings edit_meetings delete_meetings manage_agendas + view_work_packages] }).tap do |u| + u.pref[:time_zone] = "Etc/UTC" + + u.save! + end + end + shared_let(:other_user) do + create(:user, + lastname: "Second", + member_with_permissions: { project => %i[view_meetings view_work_packages] }) + end + shared_let(:no_member_user) do + create(:user, + lastname: "Third") + end + shared_let(:work_package) do + create(:work_package, project:, subject: "Important task") + end + + let(:current_user) { user } + let(:new_page) { Pages::Meetings::New.new(project) } + let(:meeting) { StructuredMeeting.last } + let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) } + let(:meetings_page) { Pages::Meetings::Index.new(project: nil) } + + before do |test| + login_as current_user + meetings_page.visit! + expect(page).to have_current_path(meetings_page.path) # rubocop:disable RSpec/ExpectInHook + meetings_page.click_on "add-meeting-button" + meetings_page.click_on "One-time" + + meetings_page.set_project project + + meetings_page.set_title "Some title" + + meetings_page.set_start_date "2013-03-28" + meetings_page.set_start_time "13:30" + meetings_page.set_duration "1.5" + + if test.metadata[:checked] + expect(page).to have_unchecked_field "send_notifications" # rubocop:disable RSpec/ExpectInHook + check "send_notifications" + end + + meetings_page.click_create + end + + it "can delete a meeting and get back to the index page" do + click_on("op-meetings-header-action-trigger") + + click_on "Delete meeting" + + within "#delete-meeting-dialog" do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" + end + + expect(page).to have_current_path meetings_path + + expect_flash(type: :success, message: "Successful deletion.") + end +end From 96308af5121445d7b916360fe9a113c55303632c Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 17:10:16 -0300 Subject: [PATCH 07/24] Fix global-scoped MeetingsController actions Always setting `@project` generates incorrect form actions and URLs on these pages. --- .../meeting/app/controllers/meetings_controller.rb | 12 +++++++++--- modules/meeting/app/views/meetings/show.html.erb | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index c635696abd8..9eb1a8817b4 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -33,6 +33,7 @@ class MeetingsController < ApplicationController before_action :determine_author, only: %i[history] before_action :build_meeting, only: %i[new new_dialog] before_action :find_meeting, except: %i[index new create new_dialog] + before_action :set_project, only: %i[copy history update update_participants] before_action :set_activity, only: %i[history] before_action :find_copy_from_meeting, only: %i[create] before_action :convert_params, only: %i[create update update_participants] @@ -79,8 +80,9 @@ class MeetingsController < ApplicationController else render(Meetings::ShowComponent.new(meeting: @meeting, project: @project), layout: true) end - elsif @meeting.agenda.present? && @meeting.agenda.locked? - params[:tab] ||= "minutes" + else + @project = @meeting.project + params[:tab] ||= "minutes" if @meeting.agenda.present? && @meeting.agenda.locked? end end end @@ -211,6 +213,7 @@ class MeetingsController < ApplicationController render turbo_stream: @turbo_streams end format.html do + @project = @meeting.project render :edit end end @@ -393,11 +396,14 @@ class MeetingsController < ApplicationController @meeting = Meeting .includes([:project, :author, { participants: :user }, :agenda, :minutes]) .find(params[:id]) - @project = @meeting.project rescue ActiveRecord::RecordNotFound render_404 end + def set_project + @project = @meeting.project + end + def convert_params # rubocop:disable Metrics/AbcSize # We do some preprocessing of `meeting_params` that we will store in this # instance variable. diff --git a/modules/meeting/app/views/meetings/show.html.erb b/modules/meeting/app/views/meetings/show.html.erb index ecb297e3b64..f142534224f 100644 --- a/modules/meeting/app/views/meetings/show.html.erb +++ b/modules/meeting/app/views/meetings/show.html.erb @@ -82,7 +82,7 @@ See COPYRIGHT and LICENSE files for more details. <% if authorize_for(:meetings, :destroy) %>
  • <%= link_to(t(:button_delete), - meeting_path(@meeting), + polymorphic_path([@project, @meeting]), class: 'icon-context icon-delete', method: :delete, data: { confirm: t(:text_are_you_sure) }) %> From 851c1b6a5b0de45ff889042a4b7f002b1bc79c48 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:10 -0300 Subject: [PATCH 08/24] Initial spec for Meetings::RowComponent --- .../components/meetings/row_component_spec.rb | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 modules/meeting/spec/components/meetings/row_component_spec.rb diff --git a/modules/meeting/spec/components/meetings/row_component_spec.rb b/modules/meeting/spec/components/meetings/row_component_spec.rb new file mode 100644 index 00000000000..ead083d0fbe --- /dev/null +++ b/modules/meeting/spec/components/meetings/row_component_spec.rb @@ -0,0 +1,111 @@ +# 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 "rails_helper" + +RSpec.describe Meetings::RowComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:table) { instance_double(Meetings::TableComponent, columns: [], grid_class: "test", has_actions?: true) } + let(:project) { build_stubbed(:project) } + let(:user) { build_stubbed(:user) } + + subject do + render_inline(described_class.new(row: meeting, table:)) + page + end + + before do + login_as(user) + end + + describe "actions" do + context "with default permissions" do + context "with a one-off meeting" do + let(:meeting) { build_stubbed(:meeting, project:) } + + it "shows default menu items" do + expect(subject).to have_link "Download iCalendar event" + end + end + + context "with an associated recurring/templated meeting" do + let(:series) { build_stubbed(:recurring_meeting, project:) } + let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series, project:) } + + it "shows default menu items" do + expect(subject).to have_link "View meeting series" + expect(subject).to have_link "Download iCalendar event" + end + end + end + + context "with project delete meetings permissions" do + before do + mock_permissions_for(user) do |mock| + mock.allow_in_project(:delete_meetings, project:) + end + end + + context "with a one-off meeting" do + let(:meeting) { build_stubbed(:meeting, project:) } + + it "shows delete menu item" do + expect(subject).to have_link "Delete meeting", href: delete_dialog_meeting_path(meeting) + end + end + + context "with an associated recurring/templated meeting" do + let(:series) { build_stubbed(:recurring_meeting, project:) } + let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series, project:) } + + it "shows delete menu item" do + expect(subject).to have_link "Delete occurrence", href: delete_dialog_meeting_path(meeting) + end + end + end + + context "with project create meetings permissions" do + before do + mock_permissions_for(user) do |mock| + mock.allow_in_project(:create_meetings, project:) + end + end + + context "with a one-off meeting" do + let(:meeting) { build_stubbed(:meeting, project:) } + + it "shows copy menu item" do + expect(subject).to have_link "Copy meeting" + end + end + end + end +end From dfc8c8eda24744349ecced74a11087d94dbd5ce6 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:11 -0300 Subject: [PATCH 09/24] Fix project-scoped delete dialogs invoked from row --- .../app/components/meetings/row_component.rb | 4 ++- .../components/meetings/row_component_spec.rb | 33 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/meeting/app/components/meetings/row_component.rb b/modules/meeting/app/components/meetings/row_component.rb index ea4ac880968..61c25accba8 100644 --- a/modules/meeting/app/components/meetings/row_component.rb +++ b/modules/meeting/app/components/meetings/row_component.rb @@ -30,6 +30,8 @@ module Meetings class RowComponent < ::OpPrimer::BorderBoxRowComponent + delegate :current_project, to: :table + def project_name helpers.link_to_project model.project, {}, {}, false end @@ -140,7 +142,7 @@ module Meetings menu.with_item(label: recurring_meeting.present? ? I18n.t(:label_recurring_meeting_delete) : I18n.t(:label_meeting_delete), scheme: :danger, - href: delete_dialog_meeting_path(model), + href: polymorphic_path([:delete_dialog, current_project, model.becomes(Meeting)]), tag: :a, content_arguments: { data: { controller: "async-dialog" } diff --git a/modules/meeting/spec/components/meetings/row_component_spec.rb b/modules/meeting/spec/components/meetings/row_component_spec.rb index ead083d0fbe..191d142180a 100644 --- a/modules/meeting/spec/components/meetings/row_component_spec.rb +++ b/modules/meeting/spec/components/meetings/row_component_spec.rb @@ -33,8 +33,11 @@ require "rails_helper" RSpec.describe Meetings::RowComponent, type: :component do include Rails.application.routes.url_helpers - let(:table) { instance_double(Meetings::TableComponent, columns: [], grid_class: "test", has_actions?: true) } let(:project) { build_stubbed(:project) } + let(:table) do + instance_double(Meetings::TableComponent, columns: [], grid_class: "test", has_actions?: true, current_project:) + end + let(:current_project) { nil } let(:user) { build_stubbed(:user) } subject do @@ -77,8 +80,18 @@ RSpec.describe Meetings::RowComponent, type: :component do context "with a one-off meeting" do let(:meeting) { build_stubbed(:meeting, project:) } - it "shows delete menu item" do - expect(subject).to have_link "Delete meeting", href: delete_dialog_meeting_path(meeting) + context "without a current project" do + it "shows delete menu item" do + expect(subject).to have_link "Delete meeting", href: delete_dialog_meeting_path(meeting) + end + end + + context "with a current project" do + let(:current_project) { project } + + it "shows delete menu item" do + expect(subject).to have_link "Delete meeting", href: delete_dialog_project_meeting_path(project, meeting) + end end end @@ -86,8 +99,18 @@ RSpec.describe Meetings::RowComponent, type: :component do let(:series) { build_stubbed(:recurring_meeting, project:) } let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series, project:) } - it "shows delete menu item" do - expect(subject).to have_link "Delete occurrence", href: delete_dialog_meeting_path(meeting) + context "without a current project" do + it "shows delete menu item" do + expect(subject).to have_link "Delete occurrence", href: delete_dialog_meeting_path(meeting) + end + end + + context "with a current project" do + let(:current_project) { project } + + it "shows delete menu item" do + expect(subject).to have_link "Delete occurrence", href: delete_dialog_project_meeting_path(project, meeting) + end end end end From 0d8b7e69619782520432bb00f555def0561c3ded Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 27 Jan 2025 16:54:10 -0300 Subject: [PATCH 10/24] [#59885] Impl. DeleteDialog for scheduled meetings --- ...delete_scheduled_dialog_component.html.erb | 51 ++++++++++++ .../delete_scheduled_dialog_component.rb | 55 +++++++++++++ .../recurring_meetings/row_component.rb | 8 +- .../recurring_meetings_controller.rb | 15 +++- modules/meeting/config/locales/en.yml | 8 ++ modules/meeting/config/routes.rb | 3 + .../lib/open_project/meeting/engine.rb | 2 +- .../delete_scheduled_dialog_component_spec.rb | 77 +++++++++++++++++++ .../recurring_meeting_crud_spec.rb | 34 ++++++++ .../support/pages/recurring_meeting/show.rb | 4 + 10 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb create mode 100644 modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb create mode 100644 modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb diff --git a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb new file mode 100644 index 00000000000..2e6798f91ee --- /dev/null +++ b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb @@ -0,0 +1,51 @@ +<%#-- 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::DangerConfirmationDialog.new( + id:, + title:, + confirm_button_text:, + cancel_button_text:, + form_arguments: { + action: polymorphic_path( + [:delete_scheduled, @project, @scheduled.recurring_meeting], + start_time: @scheduled.start_time.iso8601 + ), + method: :post, + data: { turbo: true } + } + )) do |dialog| + dialog.with_confirmation_message do |message| + message.with_heading(tag: :h2) { I18n.t("recurring_meeting.scheduled_delete_dialog.heading") } + message.with_description_content(confirmation_message) + end + dialog.with_confirmation_check_box_content(I18n.t("text_permanent_delete_confirmation_checkbox_label")) + end +%> diff --git a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb new file mode 100644 index 00000000000..853209e1195 --- /dev/null +++ b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb @@ -0,0 +1,55 @@ +# 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 RecurringMeetings + class DeleteScheduledDialogComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + + def initialize(scheduled:, project:) + super + + @scheduled = scheduled + @project = project + end + + private + + def id = "delete-scheduled-recurring-meeting-dialog" + + def title = I18n.t("recurring_meeting.scheduled_delete_dialog.title") + + def confirmation_message = t("recurring_meeting.scheduled_delete_dialog.confirmation_message_html") + + def confirm_button_text = I18n.t("recurring_meeting.scheduled_delete_dialog.confirm_button") + + def cancel_button_text = I18n.t(:button_close) + end +end diff --git a/modules/meeting/app/components/recurring_meetings/row_component.rb b/modules/meeting/app/components/recurring_meetings/row_component.rb index a5ed2374d37..d93e5742448 100644 --- a/modules/meeting/app/components/recurring_meetings/row_component.rb +++ b/modules/meeting/app/components/recurring_meetings/row_component.rb @@ -193,9 +193,11 @@ module RecurringMeetings menu.with_item( label: I18n.t(:label_recurring_meeting_cancel), scheme: :danger, - href: delete_scheduled_recurring_meeting_path(model.recurring_meeting.id, start_time: model.start_time.iso8601), - form_arguments: { - method: :post, data: { confirm: I18n.t("text_are_you_sure"), turbo: false } + href: polymorphic_path([:delete_scheduled_dialog, current_project, model.recurring_meeting], + start_time: model.start_time.iso8601), + tag: :a, + content_arguments: { + data: { controller: "async-dialog" } } ) do |item| item.with_leading_visual_icon(icon: :trash) diff --git a/modules/meeting/app/controllers/recurring_meetings_controller.rb b/modules/meeting/app/controllers/recurring_meetings_controller.rb index 0bc946aca6a..35788cf0ec8 100644 --- a/modules/meeting/app/controllers/recurring_meetings_controller.rb +++ b/modules/meeting/app/controllers/recurring_meetings_controller.rb @@ -10,12 +10,14 @@ class RecurringMeetingsController < ApplicationController before_action :find_meeting, only: %i[show update details_dialog delete_dialog destroy edit init - delete_scheduled template_completed download_ics notify end_series end_series_dialog] + delete_scheduled_dialog delete_scheduled template_completed download_ics notify end_series + end_series_dialog] before_action :find_optional_project, - only: %i[index show new create update details_dialog delete_dialog destroy edit delete_scheduled notify] + only: %i[index show new create update details_dialog delete_dialog destroy edit delete_scheduled_dialog + delete_scheduled notify] before_action :authorize_global, only: %i[index new create] before_action :authorize, except: %i[index new create] - before_action :get_scheduled_meeting, only: %i[delete_scheduled] + before_action :get_scheduled_meeting, only: %i[delete_scheduled_dialog delete_scheduled] before_action :convert_params, only: %i[create update] before_action :check_template_completable, only: %i[template_completed] @@ -194,6 +196,13 @@ class RecurringMeetingsController < ApplicationController redirect_to action: :show, id: @recurring_meeting, status: :see_other end + def delete_scheduled_dialog + respond_with_dialog RecurringMeetings::DeleteScheduledDialogComponent.new( + scheduled: @scheduled, + project: @project + ) + end + def delete_scheduled if @scheduled.update(cancelled: true) flash[:notice] = I18n.t(:notice_successful_cancel) diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 663f46e8cb6..209b5f8f705 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -294,6 +294,14 @@ en: Deleting %{title} will delete all %{count} meetings in this series. This action is not reversible. Please proceed with caution. + scheduled_delete_dialog: + title: "Cancel meeting occurrence" + heading: "Cancel this meeting occurrence?" + confirmation_message_html: > + Any meeting information not in the template will be lost. + + Do you want to continue? + confirm_button: "Cancel occurrence" notice_successful_notification: "Notification sent successfully" notice_timezone_missing: No time zone is set and %{zone} is assumed. To choose your time zone, please click here. diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index f24df91659d..97fa4b2f7ef 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -39,6 +39,8 @@ Rails.application.routes.draw do resources :recurring_meetings, only: %i[index new create show destroy] do member do get :delete_dialog + get :delete_scheduled_dialog + post :delete_scheduled end end end @@ -68,6 +70,7 @@ Rails.application.routes.draw do get :details_dialog get :download_ics get :delete_dialog + get :delete_scheduled_dialog post :init post :delete_scheduled post :template_completed diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index f3082bb31f5..36929ea6006 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -71,7 +71,7 @@ module OpenProject::Meeting permission :delete_meetings, { meetings: %i[delete_dialog destroy], - recurring_meetings: %i[delete_dialog destroy delete_scheduled] + recurring_meetings: %i[delete_dialog destroy delete_scheduled_dialog delete_scheduled] }, permissible_on: :project, require: :member diff --git a/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb new file mode 100644 index 00000000000..d3cf5b53ecd --- /dev/null +++ b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb @@ -0,0 +1,77 @@ +# 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 "rails_helper" + +RSpec.describe RecurringMeetings::DeleteScheduledDialogComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:project) { build_stubbed(:project) } + let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 6) } + let(:start_time) { 1.day.from_now } + let(:scheduled) { recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: start_time) } + let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting:) } + let(:user) { build_stubbed(:user) } + + subject do + render_inline(described_class.new(scheduled:, project:)) + page + end + + before do + login_as(user) + end + + describe "dialog form" do + context "without a current project" do + let(:project) { nil } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: delete_scheduled_recurring_meeting_path( + recurring_meeting, start_time: start_time.iso8601 + ) + end + end + + context "with a current project" do + let(:project) { build_stubbed(:project) } + + it "renders the correct form action" do + expect(subject).to have_element "form", action: delete_scheduled_project_recurring_meeting_path( + project, recurring_meeting, start_time: start_time.iso8601 + ) + end + end + end + + it "shows a heading" do + expect(subject).to have_text "Cancel this meeting occurrence?" + end +end diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index ac2ecbf8abd..0ec3a738f39 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -149,6 +149,23 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" end + it "can cancel a scheduled occurrence from the global show page" do + show_page.visit! + + show_page.cancel_occurrence date: "01/07/2025 01:30 PM" + show_page.in_delete_scheduled_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Cancel occurrence" + end + + expect_flash(type: :success, message: "Successful cancellation.") + + expect(page).to have_current_path(show_page.path) + + show_page.expect_cancelled_meeting date: "01/07/2025 01:30 PM" + end + it "can cancel an occurrence from the project show page" do show_page.visit_project! @@ -167,6 +184,23 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" end + it "can cancel a scheduled occurrence from the project show page" do + show_page.visit_project! + + show_page.cancel_occurrence date: "01/07/2025 01:30 PM" + show_page.in_delete_scheduled_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Cancel occurrence" + end + + expect_flash(type: :success, message: "Successful cancellation.") + + expect(page).to have_current_path(show_page.project_path) + + show_page.expect_cancelled_meeting date: "01/07/2025 01:30 PM" + end + it "can edit the details of a recurring meeting" do show_page.visit! diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index 677971057b8..2bb19f2e69c 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -138,6 +138,10 @@ module Pages::RecurringMeeting page.within("#delete-recurring-meeting-dialog", &) end + def in_delete_scheduled_dialog(&) + page.within("#delete-scheduled-recurring-meeting-dialog", &) + end + def expect_no_meeting(date:) expect(page).to have_no_css("li", text: date) end From 02ca63a2519c5abace7900a57e7f6e2b1de75996 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 28 Jan 2025 15:53:52 -0300 Subject: [PATCH 11/24] Initial spec for RecurringMeetings::RowComponent --- .../recurring_meetings/row_component_spec.rb | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 modules/meeting/spec/components/recurring_meetings/row_component_spec.rb diff --git a/modules/meeting/spec/components/recurring_meetings/row_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/row_component_spec.rb new file mode 100644 index 00000000000..a9066e95b35 --- /dev/null +++ b/modules/meeting/spec/components/recurring_meetings/row_component_spec.rb @@ -0,0 +1,108 @@ +# 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 "rails_helper" + +RSpec.describe RecurringMeetings::RowComponent, type: :component do + include Rails.application.routes.url_helpers + + let(:project) { build_stubbed(:project) } + let(:table) do + instance_double(RecurringMeetings::TableComponent, + columns: [], grid_class: "test", has_actions?: true, current_project:) + end + let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:) } + let(:current_project) { nil } + let(:user) { build_stubbed(:user) } + + subject do + render_inline(described_class.new(row: scheduled_meeting, table:)) + page + end + + before do + login_as(user) + end + + describe "actions" do + context "with project delete meetings permissions" do + before do + mock_permissions_for(user) do |mock| + mock.allow_in_project(:delete_meetings, project:) + end + end + + context "with a scheduled meeting" do + let(:scheduled_meeting) { build_stubbed(:scheduled_meeting, :scheduled, recurring_meeting:) } + + context "without a current project" do + it "shows cancel menu item" do + expect(subject).to have_link "Cancel this occurrence", + href: delete_scheduled_dialog_recurring_meeting_path( + recurring_meeting, start_time: scheduled_meeting.start_time.iso8601 + ) + end + end + + context "with a current project" do + let(:current_project) { project } + + it "shows cancel menu item" do + expect(subject).to have_link "Cancel this occurrence", + href: delete_scheduled_dialog_project_recurring_meeting_path( + project, recurring_meeting, start_time: scheduled_meeting.start_time.iso8601 + ) + end + end + end + + context "with an instantiated meeting" do + let(:scheduled_meeting) { build_stubbed(:scheduled_meeting, recurring_meeting:, meeting:) } + let(:meeting) { build_stubbed(:meeting) } + + context "without a current project" do + it "shows cancel menu item" do + expect(subject).to have_link "Cancel this occurrence", + href: delete_dialog_meeting_path(scheduled_meeting.meeting) + end + end + + context "with a current project" do + let(:current_project) { project } + + it "shows cancel menu item" do + expect(subject).to have_link "Cancel this occurrence", + href: delete_dialog_project_meeting_path(project, scheduled_meeting.meeting) + end + end + end + end + end +end From 181012827b5093792e7b46c5b5a28821fd68311a Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 28 Jan 2025 20:29:21 -0300 Subject: [PATCH 12/24] Remove extraneous before_action for history action There is no project-scoped route mapped for `MeetingsController#history`. As such `params[:project_id]` will always be `nil`. --- modules/meeting/app/controllers/meetings_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index 9eb1a8817b4..46ecda1a521 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -27,7 +27,7 @@ #++ class MeetingsController < ApplicationController - before_action :load_and_authorize_in_optional_project, only: %i[index new new_dialog show create delete_dialog destroy history] + before_action :load_and_authorize_in_optional_project, only: %i[index new new_dialog show create delete_dialog destroy] before_action :verify_activities_module_activated, only: %i[history] before_action :determine_date_range, only: %i[history] before_action :determine_author, only: %i[history] From 3a8e49a16180b3c02e48b63ba095c6ffb3a22262 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 30 Jan 2025 19:20:56 -0300 Subject: [PATCH 13/24] Rename action to destroy_scheduled for consistency Also configures route to use HTTP `DELETE` method. --- .../delete_scheduled_dialog_component.html.erb | 4 ++-- .../app/controllers/recurring_meetings_controller.rb | 8 ++++---- modules/meeting/config/routes.rb | 4 ++-- modules/meeting/lib/open_project/meeting/engine.rb | 2 +- .../delete_scheduled_dialog_component_spec.rb | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb index 2e6798f91ee..5f8785c87e7 100644 --- a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb +++ b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb @@ -35,10 +35,10 @@ See COPYRIGHT and LICENSE files for more details. cancel_button_text:, form_arguments: { action: polymorphic_path( - [:delete_scheduled, @project, @scheduled.recurring_meeting], + [:destroy_scheduled, @project, @scheduled.recurring_meeting], start_time: @scheduled.start_time.iso8601 ), - method: :post, + method: :delete, data: { turbo: true } } )) do |dialog| diff --git a/modules/meeting/app/controllers/recurring_meetings_controller.rb b/modules/meeting/app/controllers/recurring_meetings_controller.rb index 35788cf0ec8..138fc802c63 100644 --- a/modules/meeting/app/controllers/recurring_meetings_controller.rb +++ b/modules/meeting/app/controllers/recurring_meetings_controller.rb @@ -10,14 +10,14 @@ class RecurringMeetingsController < ApplicationController before_action :find_meeting, only: %i[show update details_dialog delete_dialog destroy edit init - delete_scheduled_dialog delete_scheduled template_completed download_ics notify end_series + delete_scheduled_dialog destroy_scheduled template_completed download_ics notify end_series end_series_dialog] before_action :find_optional_project, only: %i[index show new create update details_dialog delete_dialog destroy edit delete_scheduled_dialog - delete_scheduled notify] + destroy_scheduled notify] before_action :authorize_global, only: %i[index new create] before_action :authorize, except: %i[index new create] - before_action :get_scheduled_meeting, only: %i[delete_scheduled_dialog delete_scheduled] + before_action :get_scheduled_meeting, only: %i[delete_scheduled_dialog destroy_scheduled] before_action :convert_params, only: %i[create update] before_action :check_template_completable, only: %i[template_completed] @@ -203,7 +203,7 @@ class RecurringMeetingsController < ApplicationController ) end - def delete_scheduled + def destroy_scheduled if @scheduled.update(cancelled: true) flash[:notice] = I18n.t(:notice_successful_cancel) else diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index 97fa4b2f7ef..ea40f4bc545 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -40,7 +40,7 @@ Rails.application.routes.draw do member do get :delete_dialog get :delete_scheduled_dialog - post :delete_scheduled + delete :destroy_scheduled end end end @@ -72,7 +72,7 @@ Rails.application.routes.draw do get :delete_dialog get :delete_scheduled_dialog post :init - post :delete_scheduled + delete :destroy_scheduled post :template_completed post :notify post :end_series diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 36929ea6006..9ae7b81e7d4 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -71,7 +71,7 @@ module OpenProject::Meeting permission :delete_meetings, { meetings: %i[delete_dialog destroy], - recurring_meetings: %i[delete_dialog destroy delete_scheduled_dialog delete_scheduled] + recurring_meetings: %i[delete_dialog destroy delete_scheduled_dialog destroy_scheduled] }, permissible_on: :project, require: :member diff --git a/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb index d3cf5b53ecd..6be923f16f0 100644 --- a/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb +++ b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb @@ -54,7 +54,7 @@ RSpec.describe RecurringMeetings::DeleteScheduledDialogComponent, type: :compone let(:project) { nil } it "renders the correct form action" do - expect(subject).to have_element "form", action: delete_scheduled_recurring_meeting_path( + expect(subject).to have_element "form", action: destroy_scheduled_recurring_meeting_path( recurring_meeting, start_time: start_time.iso8601 ) end @@ -64,7 +64,7 @@ RSpec.describe RecurringMeetings::DeleteScheduledDialogComponent, type: :compone let(:project) { build_stubbed(:project) } it "renders the correct form action" do - expect(subject).to have_element "form", action: delete_scheduled_project_recurring_meeting_path( + expect(subject).to have_element "form", action: destroy_scheduled_project_recurring_meeting_path( project, recurring_meeting, start_time: start_time.iso8601 ) end From bcea022d8aecc1db8029f6af89d547d85b848715 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 30 Jan 2025 19:35:07 -0300 Subject: [PATCH 14/24] Split global recurring meeting feature specs --- .../recurring_meeting_crud_spec.rb | 59 +------- .../recurring_meeting_global_crud_spec.rb | 140 ++++++++++++++++++ .../support/pages/recurring_meeting/show.rb | 20 +-- 3 files changed, 151 insertions(+), 68 deletions(-) create mode 100644 modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index 0ec3a738f39..90dac180904 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -74,7 +74,7 @@ RSpec.describe "Recurring meetings CRUD", end let(:current_user) { user } - let(:show_page) { Pages::RecurringMeeting::Show.new(meeting) } + let(:show_page) { Pages::RecurringMeeting::Show.new(meeting, project:) } let(:meetings_page) { Pages::Meetings::Index.new(project:) } before do @@ -85,7 +85,7 @@ RSpec.describe "Recurring meetings CRUD", RecurringMeetings::InitNextOccurrenceJob.perform_now(meeting, meeting.first_occurrence.to_time) end - it "can delete a recurring meeting from the global show page and return to the index page" do + it "can delete a recurring meeting from the show page and return to the index page" do show_page.visit! show_page.delete_meeting_series @@ -95,22 +95,6 @@ RSpec.describe "Recurring meetings CRUD", click_on "Delete permanently" end - expect(page).to have_current_path meetings_path - - expect_flash(type: :success, message: "Successful deletion.") - show_page.expect_no_meeting date: "12/31/2024 01:30 PM" - end - - it "can delete a recurring meeting from the project show page and return to the index page" do - show_page.visit_project! - - show_page.delete_meeting_series - show_page.in_delete_recurring_dialog do - page.check "I understand that this deletion cannot be reversed" - - click_on "Delete permanently" - end - expect(page).to have_current_path project_meetings_path(project) expect_flash(type: :success, message: "Successful deletion.") @@ -131,7 +115,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_open_meeting date: "01/07/2025 01:30 PM" end - it "can cancel an occurrence from the global show page" do + it "can cancel an occurrence from the show page" do show_page.visit! show_page.cancel_occurrence date: "12/31/2024 01:30 PM" @@ -149,7 +133,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" end - it "can cancel a scheduled occurrence from the global show page" do + it "can cancel a scheduled occurrence from the show page" do show_page.visit! show_page.cancel_occurrence date: "01/07/2025 01:30 PM" @@ -166,41 +150,6 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_cancelled_meeting date: "01/07/2025 01:30 PM" end - it "can cancel an occurrence from the project show page" do - show_page.visit_project! - - show_page.cancel_occurrence date: "12/31/2024 01:30 PM" - show_page.in_delete_dialog do - check "I understand that this deletion cannot be reversed" - - click_on "Delete permanently" - end - - expect_flash(type: :success, message: "Successful cancellation.") - - expect(page).to have_current_path(show_page.project_path) - - show_page.expect_no_open_meeting date: "12/31/2024 01:30 PM" - show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" - end - - it "can cancel a scheduled occurrence from the project show page" do - show_page.visit_project! - - show_page.cancel_occurrence date: "01/07/2025 01:30 PM" - show_page.in_delete_scheduled_dialog do - check "I understand that this deletion cannot be reversed" - - click_on "Cancel occurrence" - end - - expect_flash(type: :success, message: "Successful cancellation.") - - expect(page).to have_current_path(show_page.project_path) - - show_page.expect_cancelled_meeting date: "01/07/2025 01:30 PM" - end - it "can edit the details of a recurring meeting" do show_page.visit! diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb new file mode 100644 index 00000000000..407f401c018 --- /dev/null +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb @@ -0,0 +1,140 @@ +# 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 "../../support/pages/meetings/new" +require_relative "../../support/pages/structured_meeting/show" +require_relative "../../support/pages/recurring_meeting/show" +require_relative "../../support/pages/meetings/index" + +RSpec.describe "Recurring meetings global CRUD", + :js, + with_flag: { recurring_meetings: true } do + include Components::Autocompleter::NgSelectAutocompleteHelpers + + before_all do + travel_to(Date.new(2024, 12, 1)) + end + + after(:all) do # rubocop:disable RSpec/BeforeAfterAll + travel_back + end + + shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) } + shared_let(:user) do + create :user, + lastname: "First", + preferences: { time_zone: "Etc/UTC" }, + member_with_permissions: { project => %i[view_meetings create_meetings edit_meetings delete_meetings] } + end + shared_let(:other_user) do + create(:user, + lastname: "Second", + member_with_permissions: { project => %i[view_meetings] }) + end + shared_let(:no_member_user) do + create(:user, + lastname: "Third") + end + shared_let(:meeting) do + create :recurring_meeting, + project:, + start_time: "2024-12-31T13:30:00Z", + duration: 1.5, + frequency: "weekly", + end_after: "specific_date", + end_date: "2025-01-15", + author: user + end + + let(:current_user) { user } + let(:show_page) { Pages::RecurringMeeting::Show.new(meeting, project: nil) } + let(:meetings_page) { Pages::Meetings::Index.new(project: nil) } + + before do + travel_to(Date.new(2024, 12, 1)) + login_as current_user + + # Assuming the first init job has run + RecurringMeetings::InitNextOccurrenceJob.perform_now(meeting, meeting.first_occurrence.to_time) + end + + it "can delete a recurring meeting from the show page and return to the index page" do + show_page.visit! + + show_page.delete_meeting_series + show_page.in_delete_recurring_dialog do + page.check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" + end + + expect(page).to have_current_path meetings_path + + expect_flash(type: :success, message: "Successful deletion.") + show_page.expect_no_meeting date: "12/31/2024 01:30 PM" + end + + it "can cancel an occurrence from the show page" do + show_page.visit! + + show_page.cancel_occurrence date: "12/31/2024 01:30 PM" + show_page.in_delete_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Delete permanently" + end + + expect_flash(type: :success, message: "Successful cancellation.") + + expect(page).to have_current_path(show_page.path) + + show_page.expect_no_open_meeting date: "12/31/2024 01:30 PM" + show_page.expect_cancelled_meeting date: "12/31/2024 01:30 PM" + end + + it "can cancel a scheduled occurrence from the show page" do + show_page.visit! + + show_page.cancel_occurrence date: "01/07/2025 01:30 PM" + show_page.in_delete_scheduled_dialog do + check "I understand that this deletion cannot be reversed" + + click_on "Cancel occurrence" + end + + expect_flash(type: :success, message: "Successful cancellation.") + + expect(page).to have_current_path(show_page.path) + + show_page.expect_cancelled_meeting date: "01/07/2025 01:30 PM" + end +end diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index 2bb19f2e69c..43524c8f67a 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -32,24 +32,18 @@ module Pages::RecurringMeeting class Show < ::Pages::Meetings::Base attr_accessor :meeting - def initialize(meeting) - super + def initialize(meeting, project: nil) + super(project) self.meeting = meeting end - def visit_project! - visit(project_path) - - wait_for_reload - end - def path - recurring_meeting_path(meeting) - end - - def project_path - project_recurring_meeting_path(meeting.project, meeting) + if project + project_recurring_meeting_path(project, meeting) + else + recurring_meeting_path(meeting) + end end def expect_scheduled_meeting(date:) From 6cd2fe6c2782263e40e2de412570b3e1b36db0d6 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sat, 1 Feb 2025 17:32:38 -0300 Subject: [PATCH 15/24] Rename DeleteScheduledDialog param, controller ivar Renamed for consistency with other delete dialogs. --- .../delete_scheduled_dialog_component.html.erb | 4 ++-- .../delete_scheduled_dialog_component.rb | 4 ++-- .../app/controllers/recurring_meetings_controller.rb | 8 ++++---- .../delete_scheduled_dialog_component_spec.rb | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb index 5f8785c87e7..321a4d087f9 100644 --- a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb +++ b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.html.erb @@ -35,8 +35,8 @@ See COPYRIGHT and LICENSE files for more details. cancel_button_text:, form_arguments: { action: polymorphic_path( - [:destroy_scheduled, @project, @scheduled.recurring_meeting], - start_time: @scheduled.start_time.iso8601 + [:destroy_scheduled, @project, @scheduled_meeting.recurring_meeting], + start_time: @scheduled_meeting.start_time.iso8601 ), method: :delete, data: { turbo: true } diff --git a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb index 853209e1195..43651864f7f 100644 --- a/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb +++ b/modules/meeting/app/components/recurring_meetings/delete_scheduled_dialog_component.rb @@ -33,10 +33,10 @@ module RecurringMeetings include ApplicationHelper include OpTurbo::Streamable - def initialize(scheduled:, project:) + def initialize(scheduled_meeting:, project:) super - @scheduled = scheduled + @scheduled_meeting = scheduled_meeting @project = project end diff --git a/modules/meeting/app/controllers/recurring_meetings_controller.rb b/modules/meeting/app/controllers/recurring_meetings_controller.rb index 138fc802c63..82e4a5d7d3c 100644 --- a/modules/meeting/app/controllers/recurring_meetings_controller.rb +++ b/modules/meeting/app/controllers/recurring_meetings_controller.rb @@ -198,13 +198,13 @@ class RecurringMeetingsController < ApplicationController def delete_scheduled_dialog respond_with_dialog RecurringMeetings::DeleteScheduledDialogComponent.new( - scheduled: @scheduled, + scheduled_meeting: @scheduled_meeting, project: @project ) end def destroy_scheduled - if @scheduled.update(cancelled: true) + if @scheduled_meeting.update(cancelled: true) flash[:notice] = I18n.t(:notice_successful_cancel) else flash[:error] = I18n.t(:error_failed_to_delete_entry) @@ -288,9 +288,9 @@ class RecurringMeetingsController < ApplicationController end def get_scheduled_meeting - @scheduled = @recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: params[:start_time]) + @scheduled_meeting = @recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: params[:start_time]) - render_400 unless @scheduled.meeting_id.nil? + render_400 unless @scheduled_meeting.meeting_id.nil? end def find_optional_project diff --git a/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb index 6be923f16f0..0bb40390a6d 100644 --- a/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb +++ b/modules/meeting/spec/components/recurring_meetings/delete_scheduled_dialog_component_spec.rb @@ -36,12 +36,12 @@ RSpec.describe RecurringMeetings::DeleteScheduledDialogComponent, type: :compone let(:project) { build_stubbed(:project) } let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 6) } let(:start_time) { 1.day.from_now } - let(:scheduled) { recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: start_time) } + let(:scheduled_meeting) { recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: start_time) } let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting:) } let(:user) { build_stubbed(:user) } subject do - render_inline(described_class.new(scheduled:, project:)) + render_inline(described_class.new(scheduled_meeting:, project:)) page end From bc3e7cc17fe9bb8e6409763f91383223289d481e Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 08:48:19 -0300 Subject: [PATCH 16/24] Fix delete meeting occurrence a11y title Makes title consistent with other delete dialog titles. --- modules/meeting/config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 209b5f8f705..d0c2c1f2b86 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -222,7 +222,7 @@ en: confirmation_message_html: > Do you want to continue? occurrence: - title: "Delete occurrence" + title: "Delete meeting occurrence" heading: "Delete this meeting occurrence?" confirmation_message_html: > This meeting is part of a series called %{name}. From be49054da2b010e02fb3967810c15e2f0db11963 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 10:12:17 -0300 Subject: [PATCH 17/24] Fix end meeting series dialog a11y title --- .../recurring_meetings/end_series_dialog_component.html.erb | 2 +- modules/meeting/config/locales/en.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/meeting/app/components/recurring_meetings/end_series_dialog_component.html.erb b/modules/meeting/app/components/recurring_meetings/end_series_dialog_component.html.erb index 1cc054c95a2..2dcb8feeb77 100644 --- a/modules/meeting/app/components/recurring_meetings/end_series_dialog_component.html.erb +++ b/modules/meeting/app/components/recurring_meetings/end_series_dialog_component.html.erb @@ -31,7 +31,7 @@ See COPYRIGHT and LICENSE files for more details. render(Primer::OpenProject::DangerConfirmationDialog.new( id: "recurring-meetings-end-series-dialog", confirm_button_text: I18n.t(:label_recurring_meeting_series_end_now), - title: I18n.t(:text_are_you_sure), + title: I18n.t("recurring_meeting.end_series_dialog.title"), form_arguments: { action: end_series_recurring_meeting_path(@series), method: :post diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index d0c2c1f2b86..e8efab0c491 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -302,6 +302,8 @@ en: Do you want to continue? confirm_button: "Cancel occurrence" + end_series_dialog: + title: "End meeting series" notice_successful_notification: "Notification sent successfully" notice_timezone_missing: No time zone is set and %{zone} is assumed. To choose your time zone, please click here. From a22091565d59b3c1fc882957586ac995e5173a01 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 08:47:23 -0300 Subject: [PATCH 18/24] Always assert modal is opened; Use :modal selector Uses Capybara accessible selectors `:modal` selector and accompanying `within_modal` helper where possible. This allows us to use `aria-labelledby` / `aria-label` as a locator. This also ensures that: * the dialog is actually open. * the dialog is `aria-modal` true. See https://github.com/citizensadvice/capybara_accessible_selectors?tab=readme-ov-file#within_modalname-find_options-block --- .../recurring_meeting_crud_spec.rb | 12 +++---- .../recurring_meeting_end_series_spec.rb | 18 +++++----- .../recurring_meeting_global_crud_spec.rb | 8 ++--- .../structured_meeting_crud_spec.rb | 9 +++-- .../structured_meeting_global_crud_spec.rb | 7 ++-- .../support/pages/recurring_meeting/show.rb | 34 +++++++++++-------- .../pages/structured_meeting/mobile/show.rb | 2 +- .../support/pages/structured_meeting/show.rb | 8 +++-- 8 files changed, 53 insertions(+), 45 deletions(-) diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index 90dac180904..e58449faafd 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -89,7 +89,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.visit! show_page.delete_meeting_series - show_page.in_delete_recurring_dialog do + show_page.within_modal "Delete meeting series" do page.check "I understand that this deletion cannot be reversed" click_on "Delete permanently" @@ -119,7 +119,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.visit! show_page.cancel_occurrence date: "12/31/2024 01:30 PM" - show_page.in_delete_dialog do + show_page.within_modal "Delete meeting occurrence" do check "I understand that this deletion cannot be reversed" click_on "Delete permanently" @@ -136,8 +136,8 @@ RSpec.describe "Recurring meetings CRUD", it "can cancel a scheduled occurrence from the show page" do show_page.visit! - show_page.cancel_occurrence date: "01/07/2025 01:30 PM" - show_page.in_delete_scheduled_dialog do + show_page.cancel_scheduled_occurrence date: "01/07/2025 01:30 PM" + show_page.within_modal "Cancel meeting occurrence" do check "I understand that this deletion cannot be reversed" click_on "Cancel occurrence" @@ -156,7 +156,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_subtitle text: "Every week on Tuesday at 01:30 PM, ends on 01/14/2025" show_page.edit_meeting_series - show_page.in_edit_dialog do + show_page.within_modal "Edit Meeting" do page.select("Daily", from: "Frequency") meetings_page.set_start_time "11:00" page.select("a number of occurrences", from: "Meeting series ends") @@ -179,7 +179,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.expect_scheduled_actions date: "01/07/2025 01:30 PM" show_page.cancel_occurrence date: "12/31/2024 01:30 PM" - show_page.in_delete_dialog do + show_page.within_modal "Delete meeting occurrence" do check "I understand that this deletion cannot be reversed" click_on "Delete permanently" diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb index 930b26b27c0..e4df247c93a 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb @@ -77,18 +77,18 @@ RSpec.describe "Recurring meetings end series", it "can end the meeting early" do show_page.visit! - page.find_test_selector("recurring-meeting-action-menu").click - click_on "End meeting series" - expect(page).to have_css("#recurring-meetings-end-series-dialog") - expect(page).to have_text("Ending the series will delete any future open or scheduled meeting occurrences") + show_page.end_meeting_series + show_page.within_modal "End meeting series" do + expect(page).to have_text("Ending the series will delete any future open or scheduled meeting occurrences") - retry_block do - check "I understand that this deletion cannot be reversed" - expect(page).to have_checked_field("I understand that this deletion cannot be reversed") + retry_block do + check "I understand that this deletion cannot be reversed" + expect(page).to have_checked_field("I understand that this deletion cannot be reversed") + end + + click_on "End series now" end - click_on "End series now" - expect(page).to have_current_path recurring_meeting_path(meeting) expect(page).to have_text("Nothing to display") end diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb index 407f401c018..0934bb86527 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb @@ -91,7 +91,7 @@ RSpec.describe "Recurring meetings global CRUD", show_page.visit! show_page.delete_meeting_series - show_page.in_delete_recurring_dialog do + show_page.within_modal "Delete meeting series" do page.check "I understand that this deletion cannot be reversed" click_on "Delete permanently" @@ -107,7 +107,7 @@ RSpec.describe "Recurring meetings global CRUD", show_page.visit! show_page.cancel_occurrence date: "12/31/2024 01:30 PM" - show_page.in_delete_dialog do + show_page.within_modal "Delete meeting occurrence" do check "I understand that this deletion cannot be reversed" click_on "Delete permanently" @@ -124,8 +124,8 @@ RSpec.describe "Recurring meetings global CRUD", it "can cancel a scheduled occurrence from the show page" do show_page.visit! - show_page.cancel_occurrence date: "01/07/2025 01:30 PM" - show_page.in_delete_scheduled_dialog do + show_page.cancel_scheduled_occurrence date: "01/07/2025 01:30 PM" + show_page.within_modal "Cancel meeting occurrence" do check "I understand that this deletion cannot be reversed" click_on "Cancel occurrence" diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb index 6ea8d36a52d..a66e6b51189 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb @@ -210,11 +210,10 @@ RSpec.describe "Structured meetings CRUD", end it "can delete a meeting and get back to the index page" do - click_on("op-meetings-header-action-trigger") + show_page.trigger_dropdown_menu_item "Delete meeting" + show_page.expect_modal "Delete meeting" - click_on "Delete meeting" - - within "#delete-meeting-dialog" do + show_page.within_modal "Delete meeting" do check "I understand that this deletion cannot be reversed" click_on "Delete permanently" @@ -322,7 +321,7 @@ RSpec.describe "Structured meetings CRUD", # check for copied participants with attended status reset page.find_test_selector("manage-participants-button").click - expect(page).to have_css("#edit-participants-dialog") + expect(page).to have_modal("Participants") expect(page).to have_field(id: "checkbox_invited_#{other_user.id}", checked: true) expect(page).to have_field(id: "checkbox_attended_#{other_user.id}", checked: false) diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb index 22ac4e822d5..59644d974c0 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb @@ -93,11 +93,10 @@ RSpec.describe "Structured meetings global CRUD", end it "can delete a meeting and get back to the index page" do - click_on("op-meetings-header-action-trigger") + show_page.trigger_dropdown_menu_item "Delete meeting" + show_page.expect_modal "Delete meeting" - click_on "Delete meeting" - - within "#delete-meeting-dialog" do + show_page.within_modal "Delete meeting" do check "I understand that this deletion cannot be reversed" click_on "Delete permanently" diff --git a/modules/meeting/spec/support/pages/recurring_meeting/show.rb b/modules/meeting/spec/support/pages/recurring_meeting/show.rb index 43524c8f67a..c49abb34b82 100644 --- a/modules/meeting/spec/support/pages/recurring_meeting/show.rb +++ b/modules/meeting/spec/support/pages/recurring_meeting/show.rb @@ -100,6 +100,17 @@ module Pages::RecurringMeeting click_on "more-button" click_on "Cancel this occurrence" end + + expect_modal("Delete meeting occurrence") + end + + def cancel_scheduled_occurrence(date:) + within("li", text: date) do + click_on "more-button" + click_on "Cancel this occurrence" + end + + expect_modal("Cancel meeting occurrence") end def expect_subtitle(text:) @@ -110,30 +121,25 @@ module Pages::RecurringMeeting page.find_test_selector("recurring-meeting-action-menu").click click_on "Edit meeting series" - expect(page).to have_css("#new-meeting-dialog") + expect_modal("Edit Meeting") end def delete_meeting_series page.find_test_selector("recurring-meeting-action-menu").click click_on "Delete meeting series" - expect(page).to have_css("#delete-recurring-meeting-dialog") + expect_modal("Delete meeting series") end - def in_edit_dialog(&) - page.within("#new-meeting-dialog", &) + def end_meeting_series + page.find_test_selector("recurring-meeting-action-menu").click + click_on "End meeting series" + + expect_modal("End meeting series") end - def in_delete_dialog(&) - page.within("#delete-meeting-dialog", &) - end - - def in_delete_recurring_dialog(&) - page.within("#delete-recurring-meeting-dialog", &) - end - - def in_delete_scheduled_dialog(&) - page.within("#delete-scheduled-recurring-meeting-dialog", &) + def expect_modal(...) + expect(page).to have_modal(...) end def expect_no_meeting(date:) diff --git a/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb b/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb index d879de81eaf..46a8f2c9212 100644 --- a/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb +++ b/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb @@ -41,7 +41,7 @@ module Pages::StructuredMeeting::Mobile within(meeting_details_container) do click_link_or_button "Show all" end - expect(page).to have_css("#edit-participants-dialog") + expect(page).to have_modal("Participants") end end end diff --git a/modules/meeting/spec/support/pages/structured_meeting/show.rb b/modules/meeting/spec/support/pages/structured_meeting/show.rb index 016047bde79..b11727f6197 100644 --- a/modules/meeting/spec/support/pages/structured_meeting/show.rb +++ b/modules/meeting/spec/support/pages/structured_meeting/show.rb @@ -63,6 +63,10 @@ module Pages::StructuredMeeting end end + def expect_modal(...) + expect(page).to have_modal(...) + end + def expect_no_add_form expect(page).not_to have_test_selector("#meeting-agenda-items-form-component") end @@ -204,11 +208,11 @@ module Pages::StructuredMeeting def open_participant_form page.find_test_selector("manage-participants-button").click - expect(page).to have_css("#edit-participants-dialog") + expect_modal("Participants") end def in_participant_form(&) - page.within("#edit-participants-dialog", &) + page.within_modal("Participants", &) end def expect_participant(participant, invited: false, attended: false, editable: true) From 8c12e1dd917d4e224e44b0c92ff5ed573bb006ce Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 09:22:22 -0300 Subject: [PATCH 19/24] Try to make Capy confirmation checking more robust Since `Primer::Alpha::Checkbox` draws a custom checkbox element with a `::before` pseudo-selector and disables the platform-native appearance. This *may* cause issues with checking the confirmation check box. --- .../recurring_meetings/recurring_meeting_crud_spec.rb | 8 ++++---- .../recurring_meeting_end_series_spec.rb | 2 +- .../recurring_meeting_global_crud_spec.rb | 6 +++--- .../structured_meetings/structured_meeting_crud_spec.rb | 2 +- .../structured_meeting_global_crud_spec.rb | 2 +- .../features/custom_fields/hierarchy_custom_field_spec.rb | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index e58449faafd..b823a84c37e 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -90,7 +90,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.delete_meeting_series show_page.within_modal "Delete meeting series" do - page.check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end @@ -120,7 +120,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.cancel_occurrence date: "12/31/2024 01:30 PM" show_page.within_modal "Delete meeting occurrence" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end @@ -138,7 +138,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.cancel_scheduled_occurrence date: "01/07/2025 01:30 PM" show_page.within_modal "Cancel meeting occurrence" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Cancel occurrence" end @@ -180,7 +180,7 @@ RSpec.describe "Recurring meetings CRUD", show_page.cancel_occurrence date: "12/31/2024 01:30 PM" show_page.within_modal "Delete meeting occurrence" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb index e4df247c93a..5923da9a1b2 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_end_series_spec.rb @@ -82,7 +82,7 @@ RSpec.describe "Recurring meetings end series", expect(page).to have_text("Ending the series will delete any future open or scheduled meeting occurrences") retry_block do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true expect(page).to have_checked_field("I understand that this deletion cannot be reversed") end diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb index 0934bb86527..ab18073109c 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb @@ -92,7 +92,7 @@ RSpec.describe "Recurring meetings global CRUD", show_page.delete_meeting_series show_page.within_modal "Delete meeting series" do - page.check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end @@ -108,7 +108,7 @@ RSpec.describe "Recurring meetings global CRUD", show_page.cancel_occurrence date: "12/31/2024 01:30 PM" show_page.within_modal "Delete meeting occurrence" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end @@ -126,7 +126,7 @@ RSpec.describe "Recurring meetings global CRUD", show_page.cancel_scheduled_occurrence date: "01/07/2025 01:30 PM" show_page.within_modal "Cancel meeting occurrence" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Cancel occurrence" end diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb index a66e6b51189..a1a7a18216e 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_crud_spec.rb @@ -214,7 +214,7 @@ RSpec.describe "Structured meetings CRUD", show_page.expect_modal "Delete meeting" show_page.within_modal "Delete meeting" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb index 59644d974c0..23f78d5e0d3 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb @@ -97,7 +97,7 @@ RSpec.describe "Structured meetings global CRUD", show_page.expect_modal "Delete meeting" show_page.within_modal "Delete meeting" do - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" end diff --git a/spec/features/custom_fields/hierarchy_custom_field_spec.rb b/spec/features/custom_fields/hierarchy_custom_field_spec.rb index f877e246228..918cae3f5f4 100644 --- a/spec/features/custom_fields/hierarchy_custom_field_spec.rb +++ b/spec/features/custom_fields/hierarchy_custom_field_spec.rb @@ -128,7 +128,7 @@ RSpec.describe "custom fields of type hierarchy", :js do hierarchy_page.open_action_menu_for("Phoenix Squad") click_on "Delete" expect(page).to have_test_selector("op-custom-fields--delete-item-dialog") - check "I understand that this deletion cannot be reversed" + check "I understand that this deletion cannot be reversed", allow_label_click: true click_on "Delete permanently" expect(page).not_to have_test_selector("op-custom-fields--delete-item-dialog") expect(page).to have_test_selector("op-custom-fields--hierarchy-item", count: 1) From 2a38e890d5de2fb7b3c05ca4931dac9cf83531df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 3 Feb 2025 15:56:10 +0100 Subject: [PATCH 20/24] Fix start_time of instantiated meeting --- .../requests/recurring_meetings/recurring_meetings_show_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/meeting/spec/requests/recurring_meetings/recurring_meetings_show_spec.rb b/modules/meeting/spec/requests/recurring_meetings/recurring_meetings_show_spec.rb index ca3bd9636c8..53f1a3cc652 100644 --- a/modules/meeting/spec/requests/recurring_meetings/recurring_meetings_show_spec.rb +++ b/modules/meeting/spec/requests/recurring_meetings/recurring_meetings_show_spec.rb @@ -90,7 +90,7 @@ RSpec.describe "Recurring meetings show", describe "upcoming tab" do let!(:upcoming_open_meeting) do - create(:structured_meeting, recurring_meeting:, start_time: Time.zone.today + 10.hours, state: :open) + create(:structured_meeting, recurring_meeting:, start_time: Time.zone.today + 1.day + 10.hours, state: :open) end let!(:open_meeting) do create :scheduled_meeting, From 7f7af8f4492f1f9639ecfb5351b02067189c0bec Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 12:27:59 -0300 Subject: [PATCH 21/24] =?UTF-8?q?=E2=99=BE=EF=B8=8F=20Fix=20handling=20mee?= =?UTF-8?q?ting=20series=20that=20end=5Fafter=20never?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates confirmation messages for end user depending on `count`. N.B. rails-i18n does not provide built-in pluralisation rules for `infinite` so for now we treat 0 and infinity the same. --- .../delete_dialog_component.rb | 2 +- modules/meeting/config/locales/en.yml | 13 ++++++++--- .../delete_dialog_component_spec.rb | 22 +++++++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb index 72368d123c1..6abf3e86201 100644 --- a/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb +++ b/modules/meeting/app/components/recurring_meetings/delete_dialog_component.rb @@ -49,7 +49,7 @@ module RecurringMeetings def confirmation_message t("recurring_meeting.delete_dialog.confirmation_message_html", title: @recurring_meeting.title, - count: @recurring_meeting.remaining_occurrences.count) + count: @recurring_meeting.remaining_occurrences&.count || 0) # actually ♾️, but we show same message as for 0 end end end diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index e8efab0c491..31fe68ead9f 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -290,10 +290,17 @@ en: delete_dialog: title: "Delete meeting series" heading: "Permanently delete this meeting series?" - confirmation_message_html: > - Deleting %{title} will delete all %{count} meetings in this series. + confirmation_message_html: + zero: > + Deleting %{title} will delete any meetings in this series. + This action is not reversible. Please proceed with caution. + one: > + Deleting %{title} will also delete one meeting in this series. + This action is not reversible. Please proceed with caution. + other: > + Deleting %{title} will delete all %{count} meetings in this series. + This action is not reversible. Please proceed with caution. - This action is not reversible. Please proceed with caution. scheduled_delete_dialog: title: "Cancel meeting occurrence" heading: "Cancel this meeting occurrence?" diff --git a/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb b/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb index 4996ce890b7..6e7e94feebe 100644 --- a/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb +++ b/modules/meeting/spec/components/recurring_meetings/delete_dialog_component_spec.rb @@ -69,7 +69,25 @@ RSpec.describe RecurringMeetings::DeleteDialogComponent, type: :component do expect(subject).to have_text "Permanently delete this meeting series?" end - it "shows a confirmation message detailing the remaining occurrences in the series" do - expect(subject).to have_text "will delete all 6 meetings in this series" + context "with a meeting series that ends - with several remaining meetings" do + it "shows a confirmation message with a count of the remaining meetings" do + expect(subject).to have_text "will delete all 6 meetings in this series" + end + end + + context "with a meeting series that ends - with one remaining meeting" do + let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 1) } + + it "shows a confirmation message mentioning one remaining meeting" do + expect(subject).to have_text "will also delete one meeting in this series" + end + end + + context "with a meeting series that never ends" do + let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :never) } + + it "shows a confirmation message without a count" do + expect(subject).to have_text "will delete any meetings in this series" + end end end From dd35d13aa328fa62a976f9294295776f87d78ba7 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 13:11:50 -0300 Subject: [PATCH 22/24] Update confirmation message for one-time delete per @psatyal's comment --- modules/meeting/config/locales/en.yml | 2 +- .../spec/components/meetings/delete_dialog_component_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 31fe68ead9f..b86bf456d0a 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -220,7 +220,7 @@ en: title: "Delete meeting" heading: "Delete this meeting?" confirmation_message_html: > - Do you want to continue? + This action is not reversible. Please proceed with caution. occurrence: title: "Delete meeting occurrence" heading: "Delete this meeting occurrence?" diff --git a/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb b/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb index 0f37640dd70..5bb67d6c755 100644 --- a/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb +++ b/modules/meeting/spec/components/meetings/delete_dialog_component_spec.rb @@ -73,7 +73,7 @@ RSpec.describe Meetings::DeleteDialogComponent, type: :component do end it "shows a simple confirmation message" do - expect(subject).to have_text "Do you want to continue?" + expect(subject).to have_text "This action is not reversible. Please proceed with caution." end end From e2395cfbb19ef3b2ce531a21c8d1b69edeb6fb99 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 13:13:09 -0300 Subject: [PATCH 23/24] Rename confirmation message interpolation var Renames variabble for consistency. --- .../meeting/app/components/meetings/delete_dialog_component.rb | 2 +- modules/meeting/config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/meeting/app/components/meetings/delete_dialog_component.rb b/modules/meeting/app/components/meetings/delete_dialog_component.rb index d78eb65da65..935a19c0680 100644 --- a/modules/meeting/app/components/meetings/delete_dialog_component.rb +++ b/modules/meeting/app/components/meetings/delete_dialog_component.rb @@ -64,7 +64,7 @@ module Meetings def confirmation_message if recurring_meeting.present? - t("meeting.delete_dialog.occurrence.confirmation_message_html", name: recurring_meeting.title) + t("meeting.delete_dialog.occurrence.confirmation_message_html", title: recurring_meeting.title) else t("meeting.delete_dialog.one_time.confirmation_message_html") end diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index b86bf456d0a..0add916e561 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -225,7 +225,7 @@ en: title: "Delete meeting occurrence" heading: "Delete this meeting occurrence?" confirmation_message_html: > - This meeting is part of a series called %{name}. + This meeting is part of a series called %{title}. This will only delete this particular occurrence and not the entire series. Do you want to continue? From bc55b0a3d57230e144242505beebe9c96821bbf5 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Mon, 3 Feb 2025 13:33:56 -0300 Subject: [PATCH 24/24] Remove additional feature flags for meetings --- .../recurring_meetings/recurring_meeting_global_crud_spec.rb | 4 +--- .../structured_meeting_global_crud_spec.rb | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb index ab18073109c..e61c5b29b11 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb @@ -35,9 +35,7 @@ require_relative "../../support/pages/structured_meeting/show" require_relative "../../support/pages/recurring_meeting/show" require_relative "../../support/pages/meetings/index" -RSpec.describe "Recurring meetings global CRUD", - :js, - with_flag: { recurring_meetings: true } do +RSpec.describe "Recurring meetings global CRUD", :js do include Components::Autocompleter::NgSelectAutocompleteHelpers before_all do diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb index 23f78d5e0d3..a24eeb59b53 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_global_crud_spec.rb @@ -34,9 +34,7 @@ require_relative "../../support/pages/meetings/new" require_relative "../../support/pages/structured_meeting/show" require_relative "../../support/pages/meetings/index" -RSpec.describe "Structured meetings global CRUD", - :js, - with_flag: { recurring_meetings: true } do +RSpec.describe "Structured meetings global CRUD", :js do include Components::Autocompleter::NgSelectAutocompleteHelpers shared_let(:project) { create(:project, enabled_module_names: %w[meetings work_package_tracking]) }