Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OP#50985] use primer flash (banner) for file storage administration settings #14213

48 changes: 0 additions & 48 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,54 +115,6 @@ def due_date_distance_in_words(date)
end
end

# Renders flash messages
def render_flash_messages
messages = flash
.reject { |k, _| k.start_with? '_' }
.map do |k, v|
if k.to_sym == :modal
component = v[:type].constantize
component.new(**v[:parameters]).render_in(self)
else
render_flash_message(k, v)
end
end

safe_join messages, "\n"
end

def join_flash_messages(messages)
if messages.respond_to?(:join)
safe_join(messages, '<br />'.html_safe)
else
messages
end
end

def render_flash_message(type, message, html_options = {})
if type.to_s == 'notice'
type = 'success'
end
toast_css_classes = ["op-toast -#{type}", html_options.delete(:class)]
# Add autohide class to notice flashes if configured
if type.to_s == 'success' && User.current.pref.auto_hide_popups?
toast_css_classes << 'autohide-toaster'
end
html_options = { class: toast_css_classes.join(' '), role: 'alert' }.merge(html_options)
close_button = content_tag :a, '', class: 'op-toast--close icon-context icon-close',
title: I18n.t('js.close_popup_title'),
tabindex: '0'
toast = content_tag(:div, join_flash_messages(message), class: 'op-toast--content')
content_tag :div, '', class: 'op-toast--wrapper' do
content_tag :div, '', class: 'op-toast--casing' do
content_tag :div, html_options do
concat(close_button)
concat(toast)
end
end
end
end

# Yields the given block for each project with its level in the tree
#
# Wrapper for Project#project_tree
Expand Down
100 changes: 100 additions & 0 deletions app/helpers/flash_messages_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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 FlashMessagesHelper
extend ActiveSupport::Concern

included do
# For .safe_join in join_flash_messages
include ActionView::Helpers::OutputSafetyHelper
end

def render_primer_banner_message?
flash[:primer_banner].present?
end

def render_primer_banner_message
return unless render_primer_banner_message?

render(BannerMessageComponent.new(**flash[:primer_banner].to_hash))
end

# Renders flash messages
def render_flash_messages
return if render_primer_banner_message?

messages = flash
.reject { |k, _| k.start_with? '_' }
.map do |k, v|
if k.to_sym == :modal
component = v[:type].constantize
component.new(**v[:parameters]).render_in(self)
else
render_flash_message(k, v)
end
end

safe_join messages, "\n"
end

def join_flash_messages(messages)
if messages.respond_to?(:join)
safe_join(messages, '<br />'.html_safe)
else
messages
end
end

def render_flash_message(type, message, html_options = {}) # rubocop:disable Metrics/AbcSize
if type.to_s == 'notice'
type = 'success'
end

toast_css_classes = ["op-toast -#{type}", html_options.delete(:class)]

# Add autohide class to notice flashes if configured
if type.to_s == 'success' && User.current.pref.auto_hide_popups?
toast_css_classes << 'autohide-toaster'
end

html_options = { class: toast_css_classes.join(' '), role: 'alert' }.merge(html_options)
close_button = content_tag :a, '', class: 'op-toast--close icon-context icon-close',
title: I18n.t('js.close_popup_title'),
tabindex: '0'
toast = content_tag(:div, join_flash_messages(message), class: 'op-toast--content')
content_tag :div, '', class: 'op-toast--wrapper' do
content_tag :div, '', class: 'op-toast--casing' do
content_tag :div, html_options do
concat(close_button)
concat(toast)
end
end
end
end
end
1 change: 1 addition & 0 deletions app/views/layouts/base.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<div class="content-overlay"></div>
<main id="content-wrapper" class="<%= initial_classes %>">
<%= render_primer_banner_message %>
<% if show_decoration %>
<div id="breadcrumb" class="<%= initial_classes %><%= show_breadcrumb ? ' -show' : '' %>">
<%= you_are_here_info %>
Expand Down
32 changes: 32 additions & 0 deletions modules/meeting/app/components/banner_message_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) 2012-2023 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::Alpha::Banner.new(full:, full_when_narrow:, dismiss_scheme:, icon:, scheme:,test_selector:)) { message }
%>
44 changes: 44 additions & 0 deletions modules/meeting/app/components/banner_message_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class BannerMessageComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
def initialize(message: nil, full: true, full_when_narrow: false, dismiss_scheme: :hide, icon: false, scheme: :default,
test_selector: "primer-banner-message-component")
super

@message = message
@full = full
@full_when_narrow = full_when_narrow
@dismiss_scheme = dismiss_scheme
@icon = icon
@scheme = scheme
@test_selector = test_selector
end

attr_reader :message, :full, :full_when_narrow, :dismiss_scheme, :icon, :scheme, :test_selector
end
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ def create
service_result = call_update_service

if service_result.success?
flash[:notice] = I18n.t(:'storages.notice_successful_storage_connection')
flash[:primer_banner] = {
message: I18n.t(:'storages.notice_successful_storage_connection'),
scheme: :success
}
redirect_to admin_settings_storages_path
else
respond_to do |format|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def new
# Actually create a OAuthClient object.
# Use service pattern to create a new OAuthClient
# Called by: Global app/config/routes.rb to serve Web page
def create # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
def create # rubocop:disable Metrics/AbcSize
call_oauth_clients_create_service

service_result.on_failure do
Expand All @@ -77,7 +77,7 @@ def create # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
format.turbo_stream { render :create }
end
elsif @storage.provider_type_one_drive?
flash[:notice] = I18n.t(:'storages.notice_successful_storage_connection')
flash[:primer_banner] = { message: I18n.t(:'storages.notice_successful_storage_connection'), scheme: :success }
redirect_to admin_settings_storages_path
else
raise "Unsupported provider type: #{@storage.short_provider_type}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# Purpose: CRUD the global admin page of Storages (=Nextcloud servers)
class Storages::Admin::StoragesController < ApplicationController
using Storages::Peripherals::ServiceResultRefinements
include FlashMessagesHelper

# See https://guides.rubyonrails.org/layouts_and_rendering.html for reference on layout
layout 'admin'
Expand Down Expand Up @@ -133,19 +134,14 @@ def edit_host
# Update is similar to create above
# See also: create above
# Called by: Global app/config/routes.rb to serve Web page
def update # rubocop:disable Metrics/AbcSize
def update
service_result = ::Storages::Storages::UpdateService
.new(user: current_user, model: @storage)
.call(permitted_storage_params)
@storage = service_result.result

if service_result.success?
flash[:notice] = I18n.t(:notice_successful_update)

respond_to do |format|
format.html { redirect_to edit_admin_settings_storage_path(@storage) }
format.turbo_stream
end
respond_to { |format| format.turbo_stream }
else
respond_to do |format|
format.html { render :edit }
Expand All @@ -159,15 +155,19 @@ def confirm_destroy
end

def destroy
Storages::Storages::DeleteService
service_result = Storages::Storages::DeleteService
.new(user: User.current, model: @storage)
.call
.match(
# rubocop:disable Rails/ActionControllerFlashBeforeRender
on_success: ->(*) { flash[:notice] = I18n.t(:notice_successful_delete) },
on_failure: ->(error) { flash[:error] = error.full_messages }
# rubocop:enable Rails/ActionControllerFlashBeforeRender
)

# rubocop:disable Rails/ActionControllerFlashBeforeRender
service_result.on_failure do
flash[:primer_banner] = { message: join_flash_messages(service_result.errors.full_messages), scheme: :danger }
end

service_result.on_success do
flash[:primer_banner] = { message: I18n.t(:notice_successful_delete), scheme: :success }
end
# rubocop:enable Rails/ActionControllerFlashBeforeRender

redirect_to admin_settings_storages_path
end
Expand Down
15 changes: 11 additions & 4 deletions modules/storages/spec/features/admin_storages_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,11 @@
end

expect(page).to have_current_path(admin_settings_storages_path)
expect(page).to have_text("Storage connected successfully! Remember to activate the module and the specific " \
"storage in the project settings of each desired project to use it.")
expect(page).to have_test_selector(
"primer-banner-message-component",
text: "Storage connected successfully! Remember to activate the module and the specific " \
"storage in the project settings of each desired project to use it."
)
end
end
end
Expand Down Expand Up @@ -320,8 +323,11 @@
end

expect(page).to have_current_path(admin_settings_storages_path)
wait_for(page).to have_text("Storage connected successfully! Remember to activate the module and the specific " \
"storage in the project settings of each desired project to use it.")
wait_for(page).to have_test_selector(
"primer-banner-message-component",
text: "Storage connected successfully! Remember to activate the module and the specific " \
"storage in the project settings of each desired project to use it."
)
end
end
end
Expand Down Expand Up @@ -367,6 +373,7 @@
storage_delete_button.click

expect(page).not_to have_text("Foo Nextcloud")
expect(page).to have_text('Successful deletion.')
expect(page).to have_current_path(admin_settings_storages_path)
end

Expand Down
Loading