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

Update meeting series template to allow finalizing the template #17365

Merged
merged 2 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?
[email protected]? && User.current.allowed_in_project?(:delete_meetings, @meeting.project)
[email protected]? && 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
Loading