From aad38c3684e014b8c954ff26c42374581f7ef418 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 12:38:00 +0300 Subject: [PATCH 1/6] [#56496] Document Primer Flash Modals Pattern https://community.openproject.org/work_packages/56496 --- lookbook/docs/patterns/09-flash-modal.md.erb | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lookbook/docs/patterns/09-flash-modal.md.erb diff --git a/lookbook/docs/patterns/09-flash-modal.md.erb b/lookbook/docs/patterns/09-flash-modal.md.erb new file mode 100644 index 000000000000..1829e02d808a --- /dev/null +++ b/lookbook/docs/patterns/09-flash-modal.md.erb @@ -0,0 +1,45 @@ +This pattern defines use cases where a modal view component is rendered on the next request via a [flash](https://api.rubyonrails.org/classes/ActionDispatch/Flash.html +). This is useful when you want to display a modal after a user action, such as a form submission or on a callback request from an external service. + +> *_[Flash](https://api.rubyonrails.org/classes/ActionDispatch/Flash.html) provides a way to pass temporary primitive types between controller actions. Anything you place in the flash is stored in the session and will be exposed to the very next action and then cleared out._ + +## Overview + +To flash a modal, set a `flash[:op_modal]` hash in your controller action. The hash should contain the following keys: + * `type:` - the modal component to render + * `parameters:` - the parameters to pass to the modal component + +Ex. + +```ruby +flash[:op_modal] = { + type: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } +} +``` + +Internally, the `FlashMessagesHelper#render_flash_messages` will select flash messages with the key `:op_modal` and render the modal component with the given parameters. + +### Flash a modal on callback from external service + +When you want to display a modal after a callback request from an external service, set the `:op_modal` properties directly in the `session` in the controller action that performs the open redirect and then extract them in the controller action that handles the callback. + +Ex. + +```ruby +# Controller action that performs the open redirect +def redirect_to_external_service + session[:op_modal] = { + type: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } + } + redirect_to external_service_url +end + +# Controller action that handles the callback +def callback_from_external_service + flash[:op_modal] = session.delete(:op_modal) + # Handle the callback +end +``` + From 4dbd60a3677104c823c9b51d532721ea807da966 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 15:18:59 +0300 Subject: [PATCH 2/6] chore[Op#56496]: Introduce flash modal contention via controller concern `OpTurbo::Flashable` - `#flash_op_modal` - accepts a component and parameters. The component should be a ViewComponent and respond to `:name`, otherwise an error is raised - `#store_callback_op_modal_flash` - "Long term storage" - store directly in the session, useful when callback is needed on longer term basis E.g. in open redirect with callback - `#retrieve_callback_op_modal_flash` - safely read the values from `#store_callback_op_modal_flash` --- app/controllers/application_controller.rb | 1 + .../concerns/op_modal/flashable.rb | 49 +++++++++++++++++++ app/controllers/oauth_clients_controller.rb | 3 +- app/helpers/flash_messages_helper.rb | 11 +++-- lookbook/docs/patterns/09-flash-modal.md.erb | 16 +++--- .../storages/oauth_access_grantable.rb | 18 +++---- .../admin/project_storages_controller.rb | 2 +- .../storages/project_storages_controller.rb | 14 +++--- .../requests/project_storages_open_spec.rb | 8 +-- 9 files changed, 85 insertions(+), 37 deletions(-) create mode 100644 app/controllers/concerns/op_modal/flashable.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 276ef7d8cd0c..3ed423a1317b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -52,6 +52,7 @@ class ApplicationController < ActionController::Base include AdditionalUrlHelpers include OpenProjectErrorHelper include Security::DefaultUrlOptions + include OpModal::Flashable layout "base" diff --git a/app/controllers/concerns/op_modal/flashable.rb b/app/controllers/concerns/op_modal/flashable.rb new file mode 100644 index 000000000000..0b6d8a853ed4 --- /dev/null +++ b/app/controllers/concerns/op_modal/flashable.rb @@ -0,0 +1,49 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module OpModal + module Flashable + extend ActiveSupport::Concern + + included do + add_flash_types :op_modal + end + + def flash_op_modal(component:, parameters: {}) + flash[:op_modal] = { component:, parameters: } + end + + def store_callback_op_modal_flash(component:, parameters: {}) + session[:callback_op_modal] = { component:, parameters: } + end + + def retrieve_callback_op_modal_flash + session.delete(:callback_op_modal) if session[:callback_op_modal].present? + end + end +end diff --git a/app/controllers/oauth_clients_controller.rb b/app/controllers/oauth_clients_controller.rb index 4329c8c0ebe4..365fefee1711 100644 --- a/app/controllers/oauth_clients_controller.rb +++ b/app/controllers/oauth_clients_controller.rb @@ -56,10 +56,9 @@ def callback service_result = @connection_manager.code_to_token(@code) if service_result.success? - flash[:modal] = session.delete(:oauth_callback_flash_modal) if session[:oauth_callback_flash_modal].present? # Redirect the user to the page that initially wanted to access the OAuth2 resource. # "state" is a nonce that identifies a cookie which holds that page's URL. - redirect_to @redirect_uri + redirect_to @redirect_uri, op_modal: retrieve_callback_op_modal_flash else # We got a list of errors from ::OAuthClients::ConnectionManager set_oauth_errors(service_result) diff --git a/app/helpers/flash_messages_helper.rb b/app/helpers/flash_messages_helper.rb index 0e8b668c9352..421508d044d8 100644 --- a/app/helpers/flash_messages_helper.rb +++ b/app/helpers/flash_messages_helper.rb @@ -40,9 +40,8 @@ def render_flash_messages .reject { |k, _| k.to_s == "op_primer_flash" } .reject { |k, _| k.start_with? "_" } .map do |k, v| - if k.to_sym == :modal - component = v[:type].constantize - component.new(**v.fetch(:parameters, {})).render_in(self) + if k.to_sym == :op_modal + render_op_modal_flash_component(component: v[:component], parameters: v.fetch(:parameters, {})) else render_flash_message(k, v) end @@ -51,6 +50,12 @@ def render_flash_messages safe_join messages, "\n" end + def render_op_modal_flash_component(component:, parameters: {}) + component.constantize.new(**parameters).render_in(self) + rescue NameError => e + Rails.logger.error { "Could not render flash component #{component}: #{e.message}" } + end + def render_flash_message(type, message, html_options = {}) # rubocop:disable Metrics/AbcSize if type.to_s == "notice" type = "success" diff --git a/lookbook/docs/patterns/09-flash-modal.md.erb b/lookbook/docs/patterns/09-flash-modal.md.erb index 1829e02d808a..83f6c2f41856 100644 --- a/lookbook/docs/patterns/09-flash-modal.md.erb +++ b/lookbook/docs/patterns/09-flash-modal.md.erb @@ -12,34 +12,30 @@ To flash a modal, set a `flash[:op_modal]` hash in your controller action. The h Ex. ```ruby -flash[:op_modal] = { - type: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, - parameters: { storage: storage.id } -} +flash_op_modal component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } ``` Internally, the `FlashMessagesHelper#render_flash_messages` will select flash messages with the key `:op_modal` and render the modal component with the given parameters. ### Flash a modal on callback from external service -When you want to display a modal after a callback request from an external service, set the `:op_modal` properties directly in the `session` in the controller action that performs the open redirect and then extract them in the controller action that handles the callback. +When you want to display a modal after a callback request from an external service, store the op modal component directly in the `session` in the controller action that performs by calling `OpModal::Flashable#store_callback_op_modal_flash` bofore the open redirect and then extract them in the controller action that handles the callback via `OpTurbo::Flashable#retrieve_callback_op_modal_flash`. Ex. ```ruby # Controller action that performs the open redirect def redirect_to_external_service - session[:op_modal] = { - type: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, - parameters: { storage: storage.id } - } + store_callback_op_modal_flash component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + parameters: { storage: storage.id } redirect_to external_service_url end # Controller action that handles the callback def callback_from_external_service - flash[:op_modal] = session.delete(:op_modal) # Handle the callback + redirect_to some_path, op_modal: retrieve_callback_op_modal_flash end ``` diff --git a/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb index 64e6830124c9..418304f75b8e 100644 --- a/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb +++ b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb @@ -38,12 +38,12 @@ def open_redirect_to_storage_authorization_with(callback_url:, storage:, callbac expires: 1.hour } - session[:oauth_callback_flash_modal] = case callback_modal_for - when :storage - storage_oauth_access_granted_modal(storage:) - when :project_storage - project_storage_oauth_access_granted_modal(storage:) - end + store_callback_op_modal_flash(**(case callback_modal_for + when :storage + storage_oauth_access_granted_modal(storage:) + when :project_storage + project_storage_oauth_access_granted_modal(storage:) + end)) redirect_to(storage.oauth_configuration.authorization_uri(state: nonce), allow_other_host: true) end @@ -54,21 +54,21 @@ def storage_oauth_access_granted?(storage:) def project_storage_oauth_access_grant_nudge_modal(project_storage:) { - type: ::Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, + component: ::Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, parameters: { project_storage: project_storage.id } } end def project_storage_oauth_access_granted_modal(storage:) { - type: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, parameters: { storage: storage.id } } end def storage_oauth_access_granted_modal(storage:) { - type: ::Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, + component: ::Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, parameters: { storage: storage.id } } end diff --git a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb index 1dee10947751..d5eece1e70ae 100644 --- a/modules/storages/app/controllers/storages/admin/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/admin/project_storages_controller.rb @@ -165,7 +165,7 @@ def redirect_to_project_storages_path_with_oauth_access_grant_confirmation def redirect_to_project_storages_path_with_nudge_modal redirect_to( external_file_storages_project_settings_project_storages_path, - flash: { modal: project_storage_oauth_access_grant_nudge_modal(project_storage: @project_storage) } + op_modal: project_storage_oauth_access_grant_nudge_modal(project_storage: @project_storage) ) end end diff --git a/modules/storages/app/controllers/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/project_storages_controller.rb index cb3b738e4d37..ec93ed41a429 100644 --- a/modules/storages/app/controllers/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/project_storages_controller.rb @@ -118,14 +118,12 @@ def user_can_not_read_project_folder def redirect_to_project_overview_with_modal redirect_to( project_overview_path(project_id: @project.identifier), - flash: { - modal: { - type: "Storages::OpenProjectStorageModalComponent", - parameters: { - project_storage_open_url: request.path, - redirect_url: api_v3_project_storage_open, - state: :waiting - } + op_modal: { + component: "Storages::OpenProjectStorageModalComponent", + parameters: { + project_storage_open_url: request.path, + redirect_url: api_v3_project_storage_open, + state: :waiting } } ) diff --git a/modules/storages/spec/requests/project_storages_open_spec.rb b/modules/storages/spec/requests/project_storages_open_spec.rb index 4baeb7b0f06c..c4e5099e7e32 100644 --- a/modules/storages/spec/requests/project_storages_open_spec.rb +++ b/modules/storages/spec/requests/project_storages_open_spec.rb @@ -113,8 +113,8 @@ expect(last_response.headers["Location"]).to eq("http://#{Setting.host_name}/projects/#{project.identifier}") expect(last_request.session["flash"]["flashes"]) .to eq({ - "modal" => { - type: "Storages::OpenProjectStorageModalComponent", + "op_modal" => { + component: "Storages::OpenProjectStorageModalComponent", parameters: { project_storage_open_url: "/projects/#{project.identifier}/project_storages/#{project_storage.id}/open", redirect_url: expected_redirect_path, state: :waiting } @@ -144,8 +144,8 @@ expect(last_response.headers["Location"]).to eq("http://#{Setting.host_name}/projects/#{project.identifier}") expect(last_request.session["flash"]["flashes"]) .to eq({ - "modal" => { - type: "Storages::OpenProjectStorageModalComponent", + "op_modal" => { + component: "Storages::OpenProjectStorageModalComponent", parameters: { project_storage_open_url: "/projects/#{project.identifier}/project_storages/#{project_storage.id}/open", redirect_url: expected_redirect_path, state: :waiting } From fc92b6dad3a966111f3c40802240e0f51a3b0eef Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 15:32:39 +0300 Subject: [PATCH 3/6] fix[Op#56496]: let op modal flash component be passed as a constant This way, mitigate NameError from typos, only seen later- although there is safe handling during render --- app/controllers/concerns/op_modal/flashable.rb | 4 ++-- app/helpers/flash_messages_helper.rb | 4 +++- lookbook/docs/patterns/09-flash-modal.md.erb | 4 ++-- .../controllers/concerns/storages/oauth_access_grantable.rb | 6 +++--- .../app/controllers/storages/project_storages_controller.rb | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/concerns/op_modal/flashable.rb b/app/controllers/concerns/op_modal/flashable.rb index 0b6d8a853ed4..ea747b0a6337 100644 --- a/app/controllers/concerns/op_modal/flashable.rb +++ b/app/controllers/concerns/op_modal/flashable.rb @@ -35,11 +35,11 @@ module Flashable end def flash_op_modal(component:, parameters: {}) - flash[:op_modal] = { component:, parameters: } + flash[:op_modal] = { component: component.name, parameters: } end def store_callback_op_modal_flash(component:, parameters: {}) - session[:callback_op_modal] = { component:, parameters: } + session[:callback_op_modal] = { component: component.name, parameters: } end def retrieve_callback_op_modal_flash diff --git a/app/helpers/flash_messages_helper.rb b/app/helpers/flash_messages_helper.rb index 421508d044d8..bc5e30841044 100644 --- a/app/helpers/flash_messages_helper.rb +++ b/app/helpers/flash_messages_helper.rb @@ -51,7 +51,9 @@ def render_flash_messages end def render_op_modal_flash_component(component:, parameters: {}) - component.constantize.new(**parameters).render_in(self) + component = component.constantize if component.is_a?(String) + + component.new(**parameters).render_in(self) rescue NameError => e Rails.logger.error { "Could not render flash component #{component}: #{e.message}" } end diff --git a/lookbook/docs/patterns/09-flash-modal.md.erb b/lookbook/docs/patterns/09-flash-modal.md.erb index 83f6c2f41856..daeb087c42c5 100644 --- a/lookbook/docs/patterns/09-flash-modal.md.erb +++ b/lookbook/docs/patterns/09-flash-modal.md.erb @@ -12,7 +12,7 @@ To flash a modal, set a `flash[:op_modal]` hash in your controller action. The h Ex. ```ruby -flash_op_modal component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, +flash_op_modal component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent, parameters: { storage: storage.id } ``` @@ -27,7 +27,7 @@ Ex. ```ruby # Controller action that performs the open redirect def redirect_to_external_service - store_callback_op_modal_flash component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + store_callback_op_modal_flash component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent, parameters: { storage: storage.id } redirect_to external_service_url end diff --git a/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb index 418304f75b8e..cbee6317cde6 100644 --- a/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb +++ b/modules/storages/app/controllers/concerns/storages/oauth_access_grantable.rb @@ -54,21 +54,21 @@ def storage_oauth_access_granted?(storage:) def project_storage_oauth_access_grant_nudge_modal(project_storage:) { - component: ::Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent.name, + component: ::Storages::ProjectStorages::OAuthAccessGrantNudgeModalComponent, parameters: { project_storage: project_storage.id } } end def project_storage_oauth_access_granted_modal(storage:) { - component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent.name, + component: ::Storages::ProjectStorages::OAuthAccessGrantedModalComponent, parameters: { storage: storage.id } } end def storage_oauth_access_granted_modal(storage:) { - component: ::Storages::Admin::Storages::OAuthAccessGrantedModalComponent.name, + component: ::Storages::Admin::Storages::OAuthAccessGrantedModalComponent, parameters: { storage: storage.id } } end diff --git a/modules/storages/app/controllers/storages/project_storages_controller.rb b/modules/storages/app/controllers/storages/project_storages_controller.rb index ec93ed41a429..70e53c650c39 100644 --- a/modules/storages/app/controllers/storages/project_storages_controller.rb +++ b/modules/storages/app/controllers/storages/project_storages_controller.rb @@ -119,7 +119,7 @@ def redirect_to_project_overview_with_modal redirect_to( project_overview_path(project_id: @project.identifier), op_modal: { - component: "Storages::OpenProjectStorageModalComponent", + component: Storages::OpenProjectStorageModalComponent.name, parameters: { project_storage_open_url: request.path, redirect_url: api_v3_project_storage_open, From 39040c696ceae1ea755bcb5b62faac08270a827f Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 17:45:10 +0300 Subject: [PATCH 4/6] chore[Op#56496]: Collapase mixin into a single module "OpModal::" is not really a shared namespace, so no need to nesting here --- app/controllers/application_controller.rb | 2 +- .../flashable.rb => op_modal_flashable.rb} | 28 +++++++++---------- lookbook/docs/patterns/09-flash-modal.md.erb | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) rename app/controllers/concerns/{op_modal/flashable.rb => op_modal_flashable.rb} (69%) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3ed423a1317b..609198f726f0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base include AdditionalUrlHelpers include OpenProjectErrorHelper include Security::DefaultUrlOptions - include OpModal::Flashable + include OpModalFlashable layout "base" diff --git a/app/controllers/concerns/op_modal/flashable.rb b/app/controllers/concerns/op_modal_flashable.rb similarity index 69% rename from app/controllers/concerns/op_modal/flashable.rb rename to app/controllers/concerns/op_modal_flashable.rb index ea747b0a6337..9d8d36ff88d7 100644 --- a/app/controllers/concerns/op_modal/flashable.rb +++ b/app/controllers/concerns/op_modal_flashable.rb @@ -26,24 +26,22 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module OpModal - module Flashable - extend ActiveSupport::Concern +module OpModalFlashable + extend ActiveSupport::Concern - included do - add_flash_types :op_modal - end + included do + add_flash_types :op_modal + end - def flash_op_modal(component:, parameters: {}) - flash[:op_modal] = { component: component.name, parameters: } - end + def flash_op_modal(component:, parameters: {}) + flash[:op_modal] = { component: component.name, parameters: } + end - def store_callback_op_modal_flash(component:, parameters: {}) - session[:callback_op_modal] = { component: component.name, parameters: } - end + def store_callback_op_modal_flash(component:, parameters: {}) + session[:callback_op_modal] = { component: component.name, parameters: } + end - def retrieve_callback_op_modal_flash - session.delete(:callback_op_modal) if session[:callback_op_modal].present? - end + def retrieve_callback_op_modal_flash + session.delete(:callback_op_modal) if session[:callback_op_modal].present? end end diff --git a/lookbook/docs/patterns/09-flash-modal.md.erb b/lookbook/docs/patterns/09-flash-modal.md.erb index daeb087c42c5..da831cb5595a 100644 --- a/lookbook/docs/patterns/09-flash-modal.md.erb +++ b/lookbook/docs/patterns/09-flash-modal.md.erb @@ -20,7 +20,7 @@ Internally, the `FlashMessagesHelper#render_flash_messages` will select flash me ### Flash a modal on callback from external service -When you want to display a modal after a callback request from an external service, store the op modal component directly in the `session` in the controller action that performs by calling `OpModal::Flashable#store_callback_op_modal_flash` bofore the open redirect and then extract them in the controller action that handles the callback via `OpTurbo::Flashable#retrieve_callback_op_modal_flash`. +When you want to display a modal after a callback request from an external service, store the op modal component directly in the `session` in the controller action that performs by calling `OpModalFlashable#store_callback_op_modal_flash` bofore the open redirect and then extract them in the controller action that handles the callback via `OpTurbo::Flashable#retrieve_callback_op_modal_flash`. Ex. From 69e91cb54230313550589ad0fb3534007b606ae8 Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 17:56:01 +0300 Subject: [PATCH 5/6] chore[Op#56496]: remove exception handling that could cause silent failure --- app/helpers/flash_messages_helper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/helpers/flash_messages_helper.rb b/app/helpers/flash_messages_helper.rb index bc5e30841044..4f39df25fe91 100644 --- a/app/helpers/flash_messages_helper.rb +++ b/app/helpers/flash_messages_helper.rb @@ -54,8 +54,6 @@ def render_op_modal_flash_component(component:, parameters: {}) component = component.constantize if component.is_a?(String) component.new(**parameters).render_in(self) - rescue NameError => e - Rails.logger.error { "Could not render flash component #{component}: #{e.message}" } end def render_flash_message(type, message, html_options = {}) # rubocop:disable Metrics/AbcSize From cab737f5328dcfd4100d90ceded033e1d4bb76ab Mon Sep 17 00:00:00 2001 From: Kabiru Mwenja Date: Mon, 30 Sep 2024 18:18:46 +0300 Subject: [PATCH 6/6] docs[Op#56496]: touch up documentation --- lookbook/docs/patterns/09-flash-modal.md.erb | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lookbook/docs/patterns/09-flash-modal.md.erb b/lookbook/docs/patterns/09-flash-modal.md.erb index da831cb5595a..5cd1aa59180b 100644 --- a/lookbook/docs/patterns/09-flash-modal.md.erb +++ b/lookbook/docs/patterns/09-flash-modal.md.erb @@ -6,9 +6,11 @@ This pattern defines use cases where a modal view component is rendered on the n ## Overview To flash a modal, set a `flash[:op_modal]` hash in your controller action. The hash should contain the following keys: - * `type:` - the modal component to render + * `component:` - the modal component to render * `parameters:` - the parameters to pass to the modal component +You can use the helper method `flash_op_modal` to set the flash modal properties. + Ex. ```ruby @@ -18,6 +20,23 @@ flash_op_modal component: ::Storages::ProjectStorages::OAuthAccessGrantedModalCo Internally, the `FlashMessagesHelper#render_flash_messages` will select flash messages with the key `:op_modal` and render the modal component with the given parameters. +P.S. When the `component` is a `Primer::Alpha::Dialog` the `auto-show-dialog` stimulus controller should be declared in order to automatically show the dialog when the page is loaded. + +Ex. + +```ruby +render( + Primer::Alpha::Dialog.new( + id: dialog_id, + title:, + data: { + controller: "auto-show-dialog", + } +) do + # .... +end +``` + ### Flash a modal on callback from external service When you want to display a modal after a callback request from an external service, store the op modal component directly in the `session` in the controller action that performs by calling `OpModalFlashable#store_callback_op_modal_flash` bofore the open redirect and then extract them in the controller action that handles the callback via `OpTurbo::Flashable#retrieve_callback_op_modal_flash`.