Skip to content

Commit

Permalink
Prepare basic recurring meeting setup
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverguenther committed May 21, 2024
1 parent b3afdf1 commit 4ed9b8b
Show file tree
Hide file tree
Showing 22 changed files with 526 additions and 24 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ gem "paper_trail", "~> 15.1.0"

gem "op-clamav-client", "~> 3.4", require: "clamav"

# Recurring meeting events definition
gem "ice_cube", github: "ice-cube-ruby/ice_cube", ref: "10ae8dc"

group :production do
# we use dalli as standard memcache client
# requires memcached 1.4+
Expand Down
9 changes: 8 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ GIT
capybara_accessible_selectors (0.11.0)
capybara (~> 3.36)

GIT
remote: https://github.com/ice-cube-ruby/ice_cube.git
revision: 10ae8dc1c64ea23c9461f2b046cf7ee4513050b9
ref: 10ae8dc
specs:
ice_cube (0.16.4)

GIT
remote: https://github.com/opf/md-to-pdf
revision: 8f14736a88ad0064d2a97be108fe7061ffbcee91
Expand Down Expand Up @@ -634,7 +641,6 @@ GEM
terminal-table (>= 1.5.1)
icalendar (2.10.1)
ice_cube (~> 0.16)
ice_cube (0.16.4)
ice_nine (0.11.2)
interception (0.5)
io-console (0.7.2)
Expand Down Expand Up @@ -1227,6 +1233,7 @@ DEPENDENCIES
httpx
i18n-js (~> 4.2.3)
i18n-tasks (~> 1.0.13)
ice_cube!
json_schemer (~> 2.2.0)
json_spec (~> 1.1.4)
ladle
Expand Down
46 changes: 27 additions & 19 deletions modules/meeting/app/components/meetings/header_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
component_wrapper do
render(Primer::OpenProject::PageHeader.new) do |header|
if show_state?
header.with_title { @meeting.title }
header.with_title do
if @meeting.template?
"TEMPLATE #{@meeting.title}"
else
@meeting.title
end
end
header.with_breadcrumbs(breadcrumb_items)
header.with_description { render(Meetings::HeaderInfolineComponent.new(@meeting)) }
header.with_action_menu(menu_arguments: {},
button_arguments: { icon: "kebab-horizontal",
"aria-label": t("label_meeting_actions"),
test_selector: 'op-meetings-header-action-trigger'}) do |menu|
test_selector: 'op-meetings-header-action-trigger' }) do |menu|
menu.with_item(label: t("label_meeting_edit_title"),
href: edit_meeting_path(@meeting),
content_arguments: {
Expand All @@ -17,25 +23,27 @@
item.with_leading_visual_icon(icon: :pencil)
end if @meeting.editable?

menu.with_item(label: t(:button_copy),
href: copy_meeting_path(@meeting),
content_arguments: {
data: { turbo: false }
}) do |item|
item.with_leading_visual_icon(icon: :copy)
end
unless @meeting.template?
menu.with_item(label: t(:button_copy),
href: copy_meeting_path(@meeting),
content_arguments: {
data: { turbo: false }
}) do |item|
item.with_leading_visual_icon(icon: :copy)
end

menu.with_item(label: t(:label_icalendar_download),
href: download_ics_meeting_path(@meeting)) do |item|
item.with_leading_visual_icon(icon: :download)
end
menu.with_item(label: t(:label_icalendar_download),
href: download_ics_meeting_path(@meeting)) do |item|
item.with_leading_visual_icon(icon: :download)
end

if User.current.allowed_in_project?(:send_meeting_agendas_notification, @meeting.project
)
menu.with_item(label: t('meeting.label_mail_all_participants'),
href: notify_meeting_path(@meeting),
form_arguments: { method: :post, data: { turbo: 'false' } }) do |item|
item.with_leading_visual_icon(icon: :mail)
if User.current.allowed_in_project?(:send_meeting_agendas_notification, @meeting.project
)
menu.with_item(label: t('meeting.label_mail_all_participants'),
href: notify_meeting_path(@meeting),
form_arguments: { method: :post, data: { turbo: 'false' } }) do |item|
item.with_leading_visual_icon(icon: :mail)
end
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
details.with_row do
render_meeting_attribute_row(:calendar) do
render(Primer::Beta::Text.new) do
format_date(@meeting.start_time)
if @meeting.template?
"Time set in next occurrence"
else
format_date(@meeting.start_time)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
component_wrapper do
render(Primer::OpenProject::BorderGrid.new(spacious: true)) do |border_grid|
border_grid.with_row { render(Meetings::Sidebar::DetailsComponent.new(meeting: @meeting)) }
border_grid.with_row { render(Meetings::Sidebar::StateComponent.new(meeting: @meeting)) }
unless @meeting.template?
border_grid.with_row { render(Meetings::Sidebar::StateComponent.new(meeting: @meeting)) }
end
border_grid.with_row(display: [:none, nil, :"table_cell"]) { render(Meetings::Sidebar::ParticipantsComponent.new(meeting: @meeting)) }
border_grid.with_row(display: [:none, nil, :"table_cell"]) { render(Meetings::Sidebar::AttachmentsComponent.new(meeting: @meeting)) }
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%= render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { page_title }
header.with_breadcrumbs(breadcrumb_items)
if render_create_button?
header.with_action_button(tag: :a,
href: dynamic_path,
scheme: :primary,
mobile_icon: :plus,
mobile_label: label_text,
aria: { label: accessibility_label_text },
title: accessibility_label_text,
id: id,
test_selector: "add-recurring-meeting-button") do |button|
button.with_leading_visual_icon(icon: :plus)
label_text
end
end
end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-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 RecurringMeetings
class IndexPageHeaderComponent < ApplicationComponent

Check notice on line 32 in modules/meeting/app/components/recurring_meetings/index_page_header_component.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] modules/meeting/app/components/recurring_meetings/index_page_header_component.rb#L32 <OpenProject/AddPreviewForViewComponent>

Missing Lookbook preview for /home/runner/work/openproject/openproject/modules/meeting/app/components/recurring_meetings/index_page_header_component.rb. Expected preview to exist at /home/runner/work/openproject/openproject/modules/meeting/lookbook/previews/recurring_meetings/index_page_header_component_preview.rb.
Raw output
modules/meeting/app/components/recurring_meetings/index_page_header_component.rb:32:9: C: OpenProject/AddPreviewForViewComponent: Missing Lookbook preview for /home/runner/work/openproject/openproject/modules/meeting/app/components/recurring_meetings/index_page_header_component.rb. Expected preview to exist at /home/runner/work/openproject/openproject/modules/meeting/lookbook/previews/recurring_meetings/index_page_header_component_preview.rb.
include OpPrimer::ComponentHelpers
include ApplicationHelper

def initialize(project: nil)
super
@project = project
end

def render_create_button?
if @project
User.current.allowed_in_project?(:create_meetings, @project)
else
User.current.allowed_in_any_project?(:create_meetings)
end
end

def dynamic_path
polymorphic_path([:new, @project, :recurring_meeting])
end

def id
"add-recurring-meeting-button"
end

def accessibility_label_text
I18n.t(:label_recurring_meeting_new)
end

def label_text
I18n.t(:label_recurring_meeting)
end

def page_title
I18n.t(:label_recurring_meeting_plural)
end

def breadcrumb_items
[parent_element,
page_title]
end

def parent_element
if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: I18n.t(:label_home) }
end
end
end
end
49 changes: 49 additions & 0 deletions modules/meeting/app/controllers/recurring_meetings_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class RecurringMeetingsController < ApplicationController
include Layout

before_action :find_meeting, only: %i[show]
before_action :find_optional_project, only: %i[index new create]
before_action :authorize_global, only: %i[index new create]

menu_item :meetings

def index
@recurring_meetings =
if @project
RecurringMeeting.visible.where(project_id: @project.id)
else
RecurringMeeting.visible
end
end

def new
@recurring_meeting = RecurringMeeting.new(project: @project)
end

def show; end

def create
@recurring_meeting = RecurringMeeting.new(recurring_meeting_params.merge(project: @project))

if @recurring_meeting.save
flash[:notice] = t(:notice_successful_create)
redirect_to action: :index
else
render action: :new
end
end

private

def find_meeting
@recurring_meeting = RecurringMeeting.visible.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end

def recurring_meeting_params
params
.require(:recurring_meeting)
.permit(:title)
end
end
102 changes: 102 additions & 0 deletions modules/meeting/app/forms/recurring_meeting/schedule_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#-- 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.
#++

class RecurringMeeting::ScheduleForm < ApplicationForm
include OpenProject::StaticRouting::UrlHelpers

form do |form|
form.select_list(
name: :recurrence,
input_width: :medium,
label: RecurringMeeting.human_attribute_name(:recurrence),
required: true,
autofocus: true
) do |list|
list.option(label: "Daily", value: "daily")
list.option(label: "Daily on workdays", value: "workdays")
list.option(label: "Weekly", value: "weekly")
list.option(label: "Monthly", value: "monthly")
end

form.check_box_group(label: "Days", layout: :horizontal, visually_hide_label: true) do |check_group|
check_group.check_box(
name: "monday",
label: "Monday"
)
check_group.check_box(
name: "tuesday",
label: "Tuesday"
)
check_group.check_box(
name: "wednesday",
label: "Wednesaday"
)
check_group.check_box(
name: "thursday",
label: "Thursday"
)
check_group.check_box(
name: "friday",
label: "Friday"
)
check_group.check_box(
name: "saturday",
label: "Saturday"
)
check_group.check_box(
name: "sunday",
label: "Sunday"
)
end

form.text_field(
name: :interval,
input_width: :medium,
label: RecurringMeeting.human_attribute_name(:interval),
type: :number,
step: 1
)

form.select_list(
name: :end,
input_width: :medium,
label: RecurringMeeting.human_attribute_name(:ends_on),
required: true,
autofocus: true
) do |list|
list.option(label: "Never", value: "Never")
list.option(label: "After a number of events", value: "after")
list.option(label: "On a pecific date", value: "date")
end
end

def initialize(meeting:)
super()
@meeting = meeting
end
end
Loading

0 comments on commit 4ed9b8b

Please sign in to comment.