Skip to content

Commit

Permalink
Update meeting series template to allow finalizing the template (#17365
Browse files Browse the repository at this point in the history
)

* Change blankslate for recurring meeting template

* Add template_complete button
  • Loading branch information
oliverguenther authored Dec 6, 2024
1 parent 20a1120 commit 0cffa57
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<%=
render(Primer::Beta::Blankslate.new) do |component|
component.with_visual_icon(icon: :book)
component.with_heading(tag: :h2).with_content(title)
component.with_description do
flex_layout do |flex|
if template?
flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { t(:"recurring_meeting.template.description") }
end
end

flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { t(:text_meeting_empty_description_1) }
end
flex.with_row do
render(Primer::Beta::Text.new(color: :subtle)) { t(:text_meeting_empty_description_2) }
end
if can_finalize_template?
flex.with_row(mt: 2) do
render(Primer::Beta::Text.new(font_weight: :bold, color: :subtle)) do
t(:"recurring_meeting.template.blankslate_finalize",
button_title: t("recurring_meeting.template.button_finalize"))
end
end
end
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#-- 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 MeetingAgendaItems
class BlankSlateComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

attr_reader :meeting

def initialize(meeting:)
super

@meeting = meeting
end

delegate :template?, to: :meeting

def title
if template?
t(:"recurring_meeting.template.blank_title")
else
t(:text_meeting_empty_heading)
end
end

def can_finalize_template?
template? &&
User.current.allowed_in_project?(:create_meetings, @meeting.project) &&
@meeting.recurring_meeting.scheduled_meetings.none?
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,7 @@
border_box.with_body(
scheme: :default
) do
render(Primer::Beta::Blankslate.new) do |component|
component.with_visual_icon(icon: :book)
component.with_heading(tag: :h2).with_content(t("text_meeting_empty_heading"))
component.with_description do
flex_layout do |flex|
flex.with_row(mb: 2) do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_1") }
end
flex.with_row do
render(Primer::Beta::Text.new(color: :subtle)) { t("text_meeting_empty_description_2") }
end
end
end
end
render(MeetingAgendaItems::BlankSlateComponent.new(meeting: @meeting))
end
end
border_box.with_row(p: 0, border_top: 0) do
Expand Down
15 changes: 15 additions & 0 deletions modules/meeting/app/components/meetings/header_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@
end
header.with_breadcrumbs(breadcrumb_items)
header.with_description { render(Meetings::HeaderInfolineComponent.new(@meeting)) }
if finish_setup_enabled?
header.with_action_button(
tag: :a,
scheme: :primary,
mobile_label: I18n.t("recurring_meeting.template.button_finalize"),
mobile_icon: :check,
size: :medium,
data: { "turbo-method": :post },
href: template_completed_recurring_meeting_path(@meeting.recurring_meeting),
) do |button|
button.with_leading_visual_icon(icon: :check)
I18n.t("recurring_meeting.template.button_finalize")
end
end

header.with_action_menu(menu_arguments: {},
button_arguments: { icon: "kebab-horizontal",
"aria-label": t("label_meeting_actions"),
Expand Down
8 changes: 7 additions & 1 deletion modules/meeting/app/components/meetings/header_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ def check_for_updates_interval
private

def delete_enabled?
!@meeting.templated? && User.current.allowed_in_project?(:delete_meetings, @meeting.project)
!@meeting.template? && User.current.allowed_in_project?(:delete_meetings, @meeting.project)
end

def finish_setup_enabled?
@meeting.template? &&
User.current.allowed_in_project?(:create_meetings, @meeting.project) &&
@series.scheduled_meetings.none?
end

def breadcrumb_items
Expand Down
36 changes: 35 additions & 1 deletion modules/meeting/app/controllers/recurring_meetings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ class RecurringMeetingsController < ApplicationController
include OpTurbo::FlashStreamHelper
include OpTurbo::DialogStreamHelper

before_action :find_meeting, only: %i[show update details_dialog destroy edit init delete_scheduled]
before_action :find_meeting, only: %i[show update details_dialog destroy edit init delete_scheduled template_completed]
before_action :find_optional_project, only: %i[index show new create update details_dialog destroy edit delete_scheduled]
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 :convert_params, only: %i[create update]
before_action :check_template_completable, only: %i[template_completed]

menu_item :meetings

Expand Down Expand Up @@ -142,6 +143,20 @@ def destroy
end
end

def template_completed
call = ::RecurringMeetings::InitOccurrenceService
.new(user: current_user, recurring_meeting: @recurring_meeting)
.call(start_time: @first_occurrence.to_time)

if call.success?
flash[:success] = I18n.t("recurring_meeting.occurrence.first_created")
else
flash[:error] = call.message
end

redirect_to action: :show, id: @recurring_meeting, status: :see_other
end

def delete_scheduled
if @scheduled.update(cancelled: true)
flash[:notice] = I18n.t(:notice_successful_cancel)
Expand Down Expand Up @@ -222,4 +237,23 @@ def structured_meeting_params
.require(:structured_meeting)
end
end

def check_template_completable
@first_occurrence = @recurring_meeting.next_occurrence&.to_time
if @first_occurrence.nil?
render_400(message: I18n.t("recurring_meeting.occurrence.error_no_next"))
return
end

is_scheduled = @recurring_meeting
.scheduled_meetings
.where(start_time: @first_occurrence)
.where.not(meeting_id: nil)
.exists?

if is_scheduled
flash[:info] = I18n.t("recurring_meeting.occurrence.first_already_exists")
redirect_to action: :show, status: :see_other
end
end
end
13 changes: 13 additions & 0 deletions modules/meeting/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,20 @@ en:
Enter the number of days or weeks between each occurrence.
occurrence:
infoline: "This meeting is part of a recurring meeting series."
error_no_next: "There is no next occurrence for this meeting."
first_already_exists: "The first occurrence of this meeting series is already instantiated."
first_created: >
The first meeting has been successfuly created from template.
All future meetings will be created automatically at the time of the previous occurrence.
template:
button_finalize: "Finish template"
blank_title: "Your meeting series template is empty"
description: >
This template will be used whenever new meetings in the series get created.
You can add agenda items, participants, and attachments to this template.
blankslate_finalize: >
When you're done preparing this template,
click the '%{button_title}' button above to finish the setup and schedule the first meeting of the series.
label_view_template: "View template"
label_edit_template: "Edit template"
banner_html: >
Expand Down
1 change: 1 addition & 0 deletions modules/meeting/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
get :details_dialog
post :init
post :delete_scheduled
post :template_completed
end
end

Expand Down
2 changes: 1 addition & 1 deletion modules/meeting/lib/open_project/meeting/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Engine < ::Rails::Engine
permission :create_meetings,
{
meetings: %i[new create copy new_dialog],
recurring_meetings: %i[new create copy init],
recurring_meetings: %i[new create copy init template_completed],
"meetings/menus": %i[show]
},
permissible_on: :project,
Expand Down
7 changes: 6 additions & 1 deletion modules/meeting/spec/factories/recurring_meeting_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@
after(:create) do |recurring_meeting, evaluator|
project = evaluator.project
recurring_meeting.project = project
recurring_meeting.template = create(:structured_meeting_template, recurring_meeting:, project:)

# create template
template = create(:structured_meeting_template, recurring_meeting:, project:)

# create agenda item
create(:meeting_agenda_item, meeting: template, title: "My template item")
end
end
end
1 change: 1 addition & 0 deletions modules/meeting/spec/features/meetings_close_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@

# Go to minutes, expect uneditable
find(".op-tab-row--link", text: "MINUTES").click
wait_for_network_idle
expect(page).to have_css(".button", text: "Close the agenda to begin the Minutes")

# Close the meeting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,18 @@
wait_for_network_idle
expect_and_dismiss_flash(type: :success, message: "Successful creation.")

# Use is redirected to the template
expect(page).to have_current_path(project_meeting_path(project, meeting.template))
expect(page).to have_content(I18n.t("recurring_meeting.template.description"))
expect(page).to have_link("Finish template")

click_link_or_button "Finish template"

# Does not send invitation mails by default
perform_enqueued_jobs
expect(ActionMailer::Base.deliveries.size).to eq 0

show_page.visit!

expect(page).to have_css(".start_time", count: 3)

show_page.expect_open_meeting date: "12/31/2024 01:30 PM"
Expand Down
Loading

0 comments on commit 0cffa57

Please sign in to comment.