Remove references to StructuredMeeting, Agenda and Minutes

This commit is contained in:
Oliver Günther
2025-04-04 08:44:06 +02:00
parent c139f93e70
commit 216def9bdc
130 changed files with 652 additions and 4467 deletions
@@ -61,7 +61,6 @@ module Users
)
SQL
},
{ class: MeetingContent, column: :text },
{ class: Journal::MeetingContentJournal, column: :text },
{ class: Message, column: :content },
{ class: Journal::MessageJournal, column: :content },
@@ -78,7 +78,7 @@ class BigintPrimaryAndForeignKeys < ActiveRecord::Migration[7.0]
LdapGroups::SynchronizedGroup => %i[id group_id auth_source_id],
MaterialBudgetItem => %i[id budget_id cost_type_id],
Journal::MeetingContentJournal => %i[id meeting_id author_id],
MeetingContent => %i[id meeting_id author_id],
:meeting_contents => %i[id meeting_id author_id],
Journal::MeetingJournal => %i[id project_id author_id],
MeetingParticipant => %i[id user_id meeting_id],
Meeting => %i[id author_id project_id],
@@ -1,44 +0,0 @@
//-- 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.
//++
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { Attachable } from 'core-app/features/hal/resources/mixins/attachable-mixin';
export interface MeetingContentResourceLinks {
addAttachment(attachment:HalResource):Promise<any>;
}
class MeetingContentBaseResource extends HalResource {
public $links:MeetingContentResourceLinks;
private attachmentsBackend = false;
}
export const MeetingContentResource = Attachable(MeetingContentBaseResource);
export type MeetingContentResource = HalResource;
@@ -33,7 +33,6 @@ import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { CustomActionResource } from 'core-app/features/hal/resources/custom-action-resource';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { WikiPageResource } from 'core-app/features/hal/resources/wiki-page-resource';
import { MeetingContentResource } from 'core-app/features/hal/resources/meeting-content-resource';
import { PostResource } from 'core-app/features/hal/resources/post-resource';
import { StatusResource } from 'core-app/features/hal/resources/status-resource';
import { AttachmentCollectionResource } from 'core-app/features/hal/resources/attachment-collection-resource';
@@ -185,9 +184,6 @@ const halResourceDefaultConfig:{ [typeName:string]:HalResourceFactoryConfigInter
Meeting: {
cls: MeetingResource,
},
MeetingContent: {
cls: MeetingContentResource,
},
Post: {
cls: PostResource,
},
@@ -1,70 +0,0 @@
/*
* -- 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.
* ++
*/
import { Controller } from '@hotwired/stimulus';
export default class MeetingContentController extends Controller {
static targets = [
'renderedText',
'editor',
'attachments',
'editButton',
];
static values = {
editState: { type: Boolean, default: false },
};
declare editStateValue:boolean;
declare readonly renderedTextTarget:HTMLElement;
declare readonly editorTarget:HTMLElement;
declare readonly attachmentsTarget:HTMLElement;
declare readonly editButtonTarget:HTMLLinkElement;
enableEditState() {
this.editStateValue = true;
}
cancelEditState() {
this.editStateValue = false;
window.OpenProject.pageWasEdited = false;
}
editStateValueChanged() {
this.renderedTextTarget.hidden = this.editStateValue;
this.attachmentsTarget.hidden = this.editStateValue;
this.editorTarget.hidden = !this.editStateValue;
this.editButtonTarget.classList.toggle('-active', this.editStateValue);
}
}
@@ -36,10 +36,6 @@ export default class OpMeetingsFormController extends ApplicationController {
private turboRequests:TurboRequestsService;
private pathHelper:PathHelperService;
static values = { structured: Boolean };
declare structuredValue:boolean;
async connect() {
const context = await window.OpenProject.getPluginContext();
this.turboRequests = context.services.turboRequests;
@@ -52,11 +48,7 @@ export default class OpMeetingsFormController extends ApplicationController {
let key:string;
['start_date', 'start_time_hour'].forEach((name) => {
if (this.structuredValue === true) {
key = `structured_meeting[${name}]`;
} else {
key = `meeting[${name}]`;
}
key = `meeting[${name}]`;
urlSearchParams.append(key, data.get(key) as string);
});
@@ -41,7 +41,7 @@ RSpec.describe "Calendar Widget", :js, with_settings: { start_of_week: 1 } do
due_date: Time.zone.today.beginning_of_week.next_occurring(:thursday))
end
shared_let(:meeting) do
create(:structured_meeting, title: "Weekly", project:, start_time: Time.zone.tomorrow + 10.hours)
create(:meeting, title: "Weekly", project:, start_time: Time.zone.tomorrow + 10.hours)
end
let(:overview_page) do
@@ -31,7 +31,7 @@
menu.with_item(
label: I18n.t("meeting.types.one_time"),
tag: :a,
href: polymorphic_path([:new_dialog, @project, :meetings], type: :structured),
href: polymorphic_path([:new_dialog, @project, :meetings]),
content_arguments: { data: { controller: "async-dialog" } }
) do |item|
item.with_description.with_content(t("meeting.types.structured_text"))
@@ -45,14 +45,6 @@
) do |item|
item.with_description.with_content(t("meeting.types.recurring_text"))
end
menu.with_item(
label: I18n.t("meeting.types.classic"),
tag: :a,
href: dynamic_path
) do |item|
item.with_description.with_content(t("meeting.types.classic_text"))
end
end
end
end
@@ -122,7 +122,6 @@ module Meetings
href: copy_project_meeting_path(project, model),
content_arguments: {
data: {
turbo: model.is_a?(StructuredMeeting),
turbo_stream: true
}
}) do |item|
@@ -6,8 +6,7 @@
data: { turbo: true,
turbo_stream: true,
controller: "meetings--form",
"application-target": "dynamic",
"meetings--form-structured-value": true },
"application-target": "dynamic" },
url: update_details_project_meeting_path(@project, @meeting)
) do |f|
component_collection do |collection|
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -34,7 +35,7 @@ module MeetingAgendaItems
validate :user_allowed_to_add, :validate_meeting_existence
def self.assignable_meetings(user)
StructuredMeeting
Meeting
.open
.not_templated
.not_cancelled
@@ -1,54 +0,0 @@
# frozen_string_literal: true
#-- 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 MeetingContents
class BaseContract < ::ModelContract
include Attachments::ValidateReplacements
def self.model
MeetingContent
end
attribute :meeting
attribute :text
attribute :lock_version
attribute :locked
attribute :type
validate :type_in_allowed
private
def type_in_allowed
unless [MeetingAgenda.name, MeetingMinutes.name].include?(model.type)
errors.add(:type, :inclusion)
end
end
end
end
@@ -1,42 +0,0 @@
# frozen_string_literal: true
#-- 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 MeetingContents
class UpdateContract < BaseContract
validate :validate_editable
private
def validate_editable
unless model.editable?
errors.add :base, :error_readonly
end
end
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -40,7 +41,7 @@ module MeetingSections
:validate_meeting_existence
def self.assignable_meetings(user)
StructuredMeeting
Meeting
.open
.visible(user)
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,21 +30,13 @@
module Meetings
class CreateContract < BaseContract
attribute :type
attribute :recurring_meeting_id
validate :user_allowed_to_add
validate :type_in_allowed
validate :recurring_meeting_visible
private
def type_in_allowed
unless [StructuredMeeting.name, Meeting.name].include?(model.type)
errors.add(:type, :inclusion)
end
end
def user_allowed_to_add
return if model.project.nil?
@@ -1,50 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
class MeetingAgendasController < MeetingContentsController
menu_item :meetings
def close
@meeting.close_agenda_and_copy_to_minutes!
redirect_back_or_default({ controller: "/meetings", action: "show", id: @meeting })
end
def open
@content.unlock!
redirect_back_or_default({ controller: "/meetings", action: "show", id: @meeting })
end
private
def find_content
@content = @meeting.agenda || @meeting.build_agenda
@content_type = "meeting_agenda"
end
end
@@ -1,109 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
class MeetingContentsController < ApplicationController
include AttachableServiceCall
include PaginationHelper
menu_item :meetings
helper :watchers
helper :wiki
helper :meetings
helper :meeting_contents
helper :watchers
helper :meetings
before_action :load_and_authorize_in_optional_project
before_action :find_meeting, :find_content
def show # rubocop:disable Metrics/AbcSize
if params[:id].present? && @content.last_journal.version == params[:id].to_i
# Redirect links to the last version
redirect_to project_meeting_path(@project, @meeting, tab: @content_type.sub(/^meeting_/, ""))
return
end
# go to an old version if a version id is given
@journaled_version = true
@content = @content.at_version params[:id] if params[:id].present?
render "meeting_contents/show"
end
def update # rubocop:disable Metrics/AbcSize
call = attachable_update_call ::MeetingContents::UpdateService,
model: @content,
args: content_params
if call.success?
flash[:notice] = I18n.t(:notice_successful_update)
redirect_back_or_default project_meeting_path(@project, @meeting)
else
flash.now[:error] = call.message
params[:tab] ||= "minutes" if @meeting.agenda.present? && @meeting.agenda.locked?
render "meetings/show", status: :unprocessable_entity
end
end
def history
# don't load text
@content_versions = @content.journals.select("id, user_id, notes, created_at, version")
.order(Arel.sql("version DESC"))
.page(page_param)
.per_page(per_page_param)
render "meeting_contents/history", layout: !request.xhr?
end
def diff
@diff = @content.diff(params[:version_to], params[:version_from])
render "meeting_contents/diff"
rescue ActiveRecord::RecordNotFound
render_404
end
def default_breadcrumb
MeetingsController.new.send(:default_breadcrumb)
end
private
def find_meeting
@meeting = Meeting.includes(:project, :author, :participants, :agenda, :minutes)
.find(params[:meeting_id])
@project = @meeting.project
@author = User.current
rescue ActiveRecord::RecordNotFound
render_404
end
def content_params
params.require(@content_type).permit(:text, :lock_version, :journal_notes)
end
end
@@ -1,39 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
class MeetingMinutesController < MeetingContentsController
menu_item :meetings
private
def find_content
@content = @meeting.minutes || @meeting.build_minutes
@content_type = "meeting_minutes"
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -41,7 +42,6 @@ class MeetingsController < ApplicationController
before_action :prevent_template_destruction, only: :destroy
helper :watchers
helper :meeting_contents
include MeetingsHelper
include Layout
include WatchersHelper
@@ -67,18 +67,14 @@ class MeetingsController < ApplicationController
:meetings
end
def show # rubocop:disable Metrics/AbcSize
def show
respond_to do |format|
format.html do
html_title "#{t(:label_meeting)}: #{@meeting.title}"
if @meeting.is_a?(StructuredMeeting)
if @meeting.state == "cancelled"
render_404
else
render(Meetings::ShowComponent.new(meeting: @meeting), layout: true)
end
elsif @meeting.agenda.present? && @meeting.agenda.locked?
params[:tab] ||= "minutes"
if @meeting.state == "cancelled"
render_404
else
render(Meetings::ShowComponent.new(meeting: @meeting), layout: true)
end
end
end
@@ -256,7 +252,7 @@ class MeetingsController < ApplicationController
end
def update_title
@meeting.update(title: structured_meeting_params[:title])
@meeting.update(title: meeting_params[:title])
if @meeting.errors.any?
update_header_component_via_turbo_stream(state: :edit)
@@ -270,7 +266,7 @@ class MeetingsController < ApplicationController
def update_details
call = ::Meetings::UpdateService
.new(user: current_user, model: @meeting)
.call(structured_meeting_params)
.call(meeting_params)
if call.success?
update_header_component_via_turbo_stream
@@ -337,9 +333,7 @@ class MeetingsController < ApplicationController
@text = friendly_timezone_name(User.current.time_zone, period: meeting.start_time)
end
prefix = params[:structured_meeting] ? "structured_" : ""
add_caption_to_input_element_via_turbo_stream("input[name='#{prefix}meeting[start_time_hour]']",
add_caption_to_input_element_via_turbo_stream("input[name='meeting[start_time_hour]']",
caption: @text,
clean_other_captions: true)
@@ -420,7 +414,12 @@ class MeetingsController < ApplicationController
end
def build_meeting
meeting = meeting_class.new
meeting =
if params[:type] == "recurring"
RecurringMeeting.new
else
Meeting.new
end
service = meeting.is_a?(RecurringMeeting) ? ::RecurringMeetings::SetAttributesService : ::Meetings::SetAttributesService
call = service
@@ -430,17 +429,6 @@ class MeetingsController < ApplicationController
@meeting = call.result
end
def meeting_class
case params[:type]
when "recurring"
RecurringMeeting
when "structured"
StructuredMeeting
else
Meeting
end
end
def global_upcoming_meetings
projects = Project.allowed_in_project(User.current, :view_meetings)
@@ -449,7 +437,7 @@ class MeetingsController < ApplicationController
def find_meeting
@meeting = Meeting
.includes([:project, :author, { participants: :user }, :agenda, :minutes])
.includes([:project, :author, { participants: :user }, :sections, { agenda_items: :outcomes }])
.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
@@ -466,7 +454,7 @@ class MeetingsController < ApplicationController
# Handle participants separately for each meeting type
@converted_params[:participants_attributes] ||= {}
if copy_structured_meeting_participants?
if copy_meeting_participants?
create_participants
else
force_defaults
@@ -481,19 +469,11 @@ class MeetingsController < ApplicationController
params
.require(:meeting)
.permit(:title, :location, :start_time, :project_id,
:duration, :start_date, :start_time_hour, :type,
:duration, :start_date, :start_time_hour,
participants_attributes: %i[email name invited attended user user_id meeting id])
end
end
def structured_meeting_params
if params[:structured_meeting].present?
params
.require(:structured_meeting)
.permit(:title, :location, :start_time_hour, :duration, :start_date, :state, :lock_version)
end
end
def set_activity
@activity = Activities::Fetcher.new(User.current,
project: @project,
@@ -559,12 +539,6 @@ class MeetingsController < ApplicationController
end
def timezone_params
meeting_params = if params[:meeting]
params.require(:meeting)
else
params.require(:structured_meeting)
end
@timezone_params ||= meeting_params.permit(:start_date, :start_time_hour).compact_blank
@timezone_params ||= params.require(:meeting).permit(:start_date, :start_time_hour).compact_blank
end
end
@@ -346,9 +346,9 @@ class RecurringMeetingsController < ApplicationController
end
def structured_meeting_params
if params[:structured_meeting].present?
if params[:meeting].present?
params
.require(:structured_meeting)
.require(:meeting)
end
end
@@ -1,133 +0,0 @@
# frozen_string_literal: true
#-- 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 MeetingContentsHelper
def can_edit_meeting_content?(content, content_type)
authorize_for(content_type.pluralize, "update") && content.editable?
end
def saved_meeting_content_text_present?(content)
!content.new_record? && content.text.present? && !content.text.empty?
end
def show_meeting_content_editor?(content, content_type)
can_edit_meeting_content?(content, content_type) && (!saved_meeting_content_text_present?(content) || content.changed?)
end
def meeting_content_context_menu(content, content_type)
menu = []
menu << meeting_agenda_toggle_status_link(content, content_type)
menu << meeting_content_edit_link(content_type) if can_edit_meeting_content?(content, content_type)
menu << meeting_content_history_link(content_type, content.meeting)
menu.join(" ")
end
def meeting_agenda_toggle_status_link(content, content_type)
if content.meeting.agenda.present? && content.meeting.agenda.locked?
open_meeting_agenda_link(content_type, content.meeting)
else
close_meeting_agenda_link(content_type, content.meeting)
end
end
def close_meeting_agenda_link(content_type, meeting)
case content_type
when "meeting_agenda"
content_tag :li, "", class: "toolbar-item" do
link_to_if_authorized({ controller: "/meeting_agendas",
action: "close",
meeting_id: meeting },
method: :put,
data: { confirm: I18n.t(:text_meeting_closing_are_you_sure) },
class: "meetings--close-meeting-button button") do
text_with_icon(I18n.t(:label_meeting_close), "icon-locked")
end
end
when "meeting_minutes"
content_tag :li, "", class: "toolbar-item" do
link_to_if_authorized({ controller: "/meeting_agendas",
action: "close",
meeting_id: meeting },
method: :put,
class: "button") do
text_with_icon(I18n.t(:label_meeting_agenda_close), "icon-locked")
end
end
end
end
def open_meeting_agenda_link(content_type, meeting)
return unless content_type == "meeting_agenda"
content_tag :li, "", class: "toolbar-item" do
link_to_if_authorized({ controller: "/meeting_agendas",
action: "open",
meeting_id: meeting },
method: :put,
class: "button",
data: { confirm: I18n.t(:text_meeting_agenda_open_are_you_sure) }) do
text_with_icon(I18n.t(:label_meeting_open), "icon-unlocked")
end
end
end
def meeting_content_edit_link(_content_type)
content_tag :li, "", class: "toolbar-item" do
link_to "",
class: "button button--edit-agenda",
data: {
action: "meeting-content#enableEditState",
"meeting-content-target": "editButton"
},
accesskey: accesskey(:edit) do
text_with_icon(I18n.t(:label_edit), "icon-edit")
end
end
end
def meeting_content_history_link(content_type, meeting)
content_tag :li, "", class: "toolbar-item" do
link_to_if_authorized({ controller: "/" + content_type.pluralize,
action: "history",
meeting_id: meeting },
aria: { label: t(:label_history) },
title: t(:label_history),
class: "button") do
text_with_icon(I18n.t(:label_history), "icon-activity-history")
end
end
end
def text_with_icon(text, icon)
op_icon("button--icon #{icon}") +
" " +
content_tag("span", text, class: "button--text")
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -86,8 +87,8 @@ module MeetingsHelper
request.path == meetings_path && @project.nil?
end
def copy_structured_meeting_participants?
@copy_from.is_a?(StructuredMeeting) && params[:meeting][:copy_participants] == "1"
def copy_meeting_participants?
params[:meeting][:copy_participants] == "1"
end
def create_participants
@@ -29,20 +29,9 @@
class Activities::MeetingActivityProvider < Activities::BaseActivityProvider
activity_provider_for type: "meetings",
activities: %i[meeting meeting_content],
activities: %i[meeting],
permission: :view_meetings
def extend_event_query(query)
case activity
when :meeting_content
query.join(meetings_table).on(activity_journals_table[:meeting_id].eq(meetings_table[:id]))
join_cond = journals_table[:journable_type].eq("MeetingContent")
query.join(meeting_contents_table).on(journals_table[:journable_id].eq(meeting_contents_table[:id]).and(join_cond))
else
super
end
end
def event_query_projection
case activity
when :meeting
@@ -54,7 +43,6 @@ class Activities::MeetingActivityProvider < Activities::BaseActivityProvider
]
else
[
projection_statement(meeting_contents_table, :type, "meeting_content_type"),
projection_statement(meetings_table, :id, "meeting_id"),
projection_statement(meetings_table, :title, "meeting_title"),
projection_statement(meetings_table, :project_id, "project_id")
@@ -63,7 +51,7 @@ class Activities::MeetingActivityProvider < Activities::BaseActivityProvider
end
def activitied_type
activity == :meeting ? Meeting : MeetingContent
Meeting
end
def projects_reference_table
@@ -76,56 +64,30 @@ class Activities::MeetingActivityProvider < Activities::BaseActivityProvider
end
def activity_journals_table
@activity_journals_table ||= case activity
when :meeting
Meeting.journal_class.arel_table
else
MeetingContent.journal_class.arel_table
end
@activity_journals_table ||= Meeting.journal_class.arel_table
end
protected
def event_name(event)
case event["event_description"]
when "Agenda closed"
I18n.t("meeting_agenda_closed", scope: "events")
when "Agenda opened"
I18n.t("meeting_agenda_opened", scope: "events")
when "Minutes created"
I18n.t("meeting_minutes_created", scope: "events")
else
super
end
end
def event_title(event)
case activity
when :meeting
start_time = if event["meeting_start_time"].is_a?(String)
DateTime.parse(event["meeting_start_time"])
else
event["meeting_start_time"]
end
end_time = start_time + event["meeting_duration"].to_f.hours
start_time =
if event["meeting_start_time"].is_a?(String)
DateTime.parse(event["meeting_start_time"])
else
event["meeting_start_time"]
end
fstart_with = format_date start_time
fstart_without = format_time start_time, include_date: false
fend_without = format_time end_time, include_date: false
end_time = start_time + event["meeting_duration"].to_f.hours
"#{I18n.t(:label_meeting)}: #{event['meeting_title']} (#{fstart_with} #{fstart_without}-#{fend_without})"
else
"#{event['meeting_content_type'].constantize.model_name.human}: #{event['meeting_title']}"
end
fstart_with = format_date start_time
fstart_without = format_time start_time, include_date: false
fend_without = format_time end_time, include_date: false
"#{I18n.t(:label_meeting)}: #{event['meeting_title']} (#{fstart_with} #{fstart_without}-#{fend_without})"
end
def event_type(event)
case activity
when :meeting
"meeting"
else
event["meeting_content_type"].include?("Agenda") ? "meeting-agenda" : "meeting-minutes"
end
"meeting"
end
def event_path(event)
@@ -160,10 +122,6 @@ class Activities::MeetingActivityProvider < Activities::BaseActivityProvider
@meetings_table ||= Meeting.arel_table
end
def meeting_contents_table
@meeting_contents_table ||= MeetingContent.arel_table
end
def activity_id(event)
activity == :meeting ? event["journable_id"] : event["meeting_id"]
end
+58 -35
View File
@@ -41,17 +41,17 @@ class Meeting < ApplicationRecord
belongs_to :recurring_meeting, optional: true
has_one :scheduled_meeting, inverse_of: :meeting
has_one :agenda, dependent: :destroy, class_name: "MeetingAgenda"
has_one :minutes, dependent: :destroy, class_name: "MeetingMinutes"
has_many :contents, -> { readonly }, class_name: "MeetingContent"
has_many :participants,
dependent: :destroy,
class_name: "MeetingParticipant",
after_add: :send_participant_added_mail
has_many :sections, dependent: :destroy, class_name: "MeetingSection"
has_many :agenda_items, dependent: :destroy, class_name: "MeetingAgendaItem"
has_many :agenda_items,
class_name: "MeetingAgendaItem",
inverse_of: :meeting
accepts_nested_attributes_for :agenda_items
scope :templated, -> { where(template: true) }
scope :not_templated, -> { where(template: false) }
@@ -91,12 +91,12 @@ class Meeting < ApplicationRecord
acts_as_searchable columns: [
"#{table_name}.title",
"#{MeetingContent.table_name}.text",
"#{MeetingAgendaItem.table_name}.title",
"#{MeetingAgendaItem.table_name}.notes"
"#{MeetingAgendaItem.table_name}.notes",
"#{MeetingOutcome.table_name}.notes"
],
include: %i[contents project agenda_items],
references: %i[meeting_contents agenda_items],
include: [:project, { agenda_items: :outcomes }],
references: %i[agenda_items outcomes],
date_column: "#{table_name}.created_at"
include Meeting::Journalized
@@ -157,10 +157,6 @@ class Meeting < ApplicationRecord
title
end
def text
agenda.text if agenda.present?
end
def templated?
!!template
end
@@ -207,28 +203,6 @@ class Meeting < ApplicationRecord
by_start_year_month_date
end
def close_agenda_and_copy_to_minutes!
Meeting.transaction do
agenda.lock!
attachments = agenda.attachments.map { |a| [a, a.copy] }
original_text = String(agenda.text)
minutes = create_minutes(text: original_text,
journal_notes: I18n.t("events.meeting_minutes_created"),
attachments: attachments.map(&:last))
# substitute attachment references in text to use the respective copied attachments
updated_text = original_text.gsub(/(?<=\(\/api\/v3\/attachments\/)\d+(?=\/content\))/) do |id|
old_id = id.to_i
new_id = attachments.select { |a, _| a.id == old_id }.map { |_, a| a.id }.first
new_id || -1
end
minutes.update text: updated_text if updated_text != original_text
end
end
alias :original_participants_attributes= :participants_attributes=
def participants_attributes=(attrs)
@@ -249,6 +223,55 @@ class Meeting < ApplicationRecord
.where(user_id: available_members)
end
# triggered by MeetingAgendaItem#after_create/after_destroy/after_save
def calculate_agenda_item_time_slots
current_time = start_time
MeetingAgendaItem.transaction do
changed_items = agenda_items.includes(:meeting_section).order("meeting_sections.position", :position).map do |top|
start_time = current_time
current_time += top.duration_in_minutes&.minutes || 0.minutes
end_time = current_time
top.assign_attributes(start_time:, end_time:)
top
end
# Disable optimistic locking in order to avoid causing `StaleObjectError`.
MeetingAgendaItem.skip_optimistic_locking do
MeetingAgendaItem.import(
changed_items,
on_duplicate_key_update: {
conflict_target: [:id],
columns: %i[meeting_id
author_id
title
notes
position
duration_in_minutes
start_time
end_time
created_at
updated_at
work_package_id
item_type
lock_version]
}
)
end
end
end
def agenda_items_sum_duration_in_minutes
agenda_items.sum(:duration_in_minutes)
end
def duration_exceeded_by_agenda_items?
agenda_items_sum_duration_in_minutes > (duration * 60)
end
def duration_exceeded_by_agenda_items_in_minutes
agenda_items_sum_duration_in_minutes - (duration * 60)
end
private
def add_new_participants_as_watcher
@@ -37,7 +37,7 @@ class MeetingAgendaItem < ApplicationRecord
enum :item_type, ITEM_TYPES
belongs_to :meeting, class_name: "StructuredMeeting"
belongs_to :meeting
belongs_to :meeting_section, optional: false
belongs_to :work_package, class_name: "::WorkPackage"
has_one :project, through: :meeting
@@ -1,86 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
class StructuredMeeting < Meeting
has_many :agenda_items,
dependent: :destroy,
foreign_key: "meeting_id",
class_name: "MeetingAgendaItem",
inverse_of: :meeting
accepts_nested_attributes_for :agenda_items
# triggered by MeetingAgendaItem#after_create/after_destroy/after_save
def calculate_agenda_item_time_slots
current_time = start_time
MeetingAgendaItem.transaction do
changed_items = agenda_items.includes(:meeting_section).order("meeting_sections.position", :position).map do |top|
start_time = current_time
current_time += top.duration_in_minutes&.minutes || 0.minutes
end_time = current_time
top.assign_attributes(start_time:, end_time:)
top
end
# Disable optimistic locking in order to avoid causing `StaleObjectError`.
MeetingAgendaItem.skip_optimistic_locking do
MeetingAgendaItem.import(
changed_items,
on_duplicate_key_update: {
conflict_target: [:id],
columns: %i[meeting_id
author_id
title
notes
position
duration_in_minutes
start_time
end_time
created_at
updated_at
work_package_id
item_type
lock_version]
}
)
end
end
end
def agenda_items_sum_duration_in_minutes
agenda_items.sum(:duration_in_minutes)
end
def duration_exceeded_by_agenda_items?
agenda_items_sum_duration_in_minutes > (duration * 60)
end
def duration_exceeded_by_agenda_items_in_minutes
agenda_items_sum_duration_in_minutes - (duration * 60)
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -51,7 +52,7 @@ module Meetings
end
def create_meeting_template!(series, model_data)
template = StructuredMeeting.new(template_attributes(model_data))
template = Meeting.new(template_attributes(model_data))
template.template = true
template.recurring_meeting = series
@@ -1,40 +0,0 @@
# frozen_string_literal: true
#-- 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 MeetingContents
class SetAttributesService < ::BaseServices::SetAttributes
include Attachments::SetReplacements
def set_default_attributes(_params)
model.change_by_system do
model.author = user if model.author.nil?
end
end
end
end
@@ -1,46 +0,0 @@
# frozen_string_literal: true
#-- 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 MeetingContents
class UpdateService < ::BaseServices::Update
include Attachments::ReplaceAttachments
def persist(call)
content = call.result
if content.lock_version_changed?
call.errors.add(:base, I18n.t(:notice_locking_conflict))
call.success = false
return call
end
super
end
end
end
@@ -114,31 +114,14 @@ module Meetings
end
def copy_meeting_agenda(copy)
if meeting.is_a?(StructuredMeeting)
meeting.sections.each do |section|
copy.sections << section.dup
copied_section = copy.reload.sections.last
section.agenda_items.each do |agenda_item|
copied_agenda_item = agenda_item.dup
copied_agenda_item.meeting_id = copy.id
copied_section.agenda_items << copied_agenda_item
end
meeting.sections.each do |section|
copy.sections << section.dup
copied_section = copy.reload.sections.last
section.agenda_items.each do |agenda_item|
copied_agenda_item = agenda_item.dup
copied_agenda_item.meeting_id = copy.id
copied_section.agenda_items << copied_agenda_item
end
else
MeetingAgenda.create!(
meeting: copy,
author: user,
text: meeting.agenda&.text,
journal_notes: I18n.t("meeting.copied", id: meeting.id)
)
end
end
def copy_structured_meeting_participants(copy)
meeting.participants.each do |participant|
copied_participant = participant.dup
copied_participant.meeting_id = copy.id
copy.participants << copied_participant
end
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -31,17 +32,6 @@ module Meetings
class CreateService < ::BaseServices::Create
protected
def instance(params)
# Setting the #type as attributes will not work
# as the STI instance is not changed without using e.g., +becomes!+
case params.delete(:type)
when "StructuredMeeting"
StructuredMeeting.new
else
Meeting.new
end
end
def after_perform(call)
if call.success? && Journal::NotificationConfiguration.active?
meeting = call.result
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -44,7 +45,6 @@ module RecurringMeetings
def create_meeting_template(recurring_meeting)
params = @template_params.merge(
type: "StructuredMeeting",
template: true,
recurring_meeting:,
project: recurring_meeting.project
@@ -1,59 +0,0 @@
<%#-- 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.
++#%>
<%= labelled_tabular_form_for content, :url => {:controller => '/' + content_type.pluralize, :action => 'update', :meeting_id => content.meeting}, :html => {:id => "#{content_type}_form", :method => :put} do |f| %>
<%= error_messages_for content_type %>
<% resource = ::API::V3::MeetingContents::MeetingContentRepresenter.new(content, current_user: current_user, embed_links: true) %>
<div class="form--field -no-label -visible-overflow">
<%=
f.text_area(
:text,
class: 'wiki-edit wiki-toolbar',
no_label: true,
resource: resource,
label_options: { class: 'hidden-for-sighted' },
with_text_formatting: true
)
%>
</div>
<%= f.hidden_field :lock_version %>
<p><%= f.text_field :journal_notes, label: :comments %></p>
<%= styled_button_tag t(:button_save),
class: '-primary -with-icon icon-checkmark button--save-agenda',
data: { disable_with: I18n.t(:label_loading) } %>
<%= link_to t(:button_cancel),
"#",
data: {
action: 'meeting-content#cancelEditState'
},
class: 'button -with-icon icon-cancel button--cancel-agenda' %>
<% end %>
@@ -1,76 +0,0 @@
<%#-- 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.
++#%>
<%
tab ||= locals[:tab_contents] if defined? locals
if tab.present?
content = tab[:content]
content_type = tab[:content_type]
end
-%>
<div data-controller="meeting-content"
data-application-target="dynamic"
data-meeting-content-edit-state-value="<%= show_meeting_content_editor?(content, content_type) %>"
data-test-selector="op-meeting--<%= content_type %>">
<div>
<%= toolbar title: t(:"label_#{content_type}") do %>
<%= raw meeting_content_context_menu content, content_type %>
<% end %>
</div>
<% if can_edit_meeting_content?(content, content_type) -%>
<div data-meeting-content-target="editor"
hidden>
<%= render(partial: "meeting_contents/form", locals: { content: content, content_type: content_type }) %>
</div>
<% end -%>
<div data-meeting-content-target="renderedText"
hidden>
<% if saved_meeting_content_text_present?(content) -%>
<div class="wiki op-uc-container">
<%= format_text(content.text, object: @meeting) %>
</div>
<% else -%>
<%= no_results_box %>
<% end -%>
</div>
<%# We cannot render attachments for journaled content %>
<% unless local_assigns[:journaled_version] %>
<% resource = ::API::V3::MeetingContents::MeetingContentRepresenter.new(content, current_user: current_user, embed_links: true) %>
<%= list_attachments(
resource,
data: {
"meeting-content-target": "attachments"
}
) %>
<% end %>
</div>
@@ -1,55 +0,0 @@
<%#-- 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.
++#%>
<% html_title "#{t(:label_meeting_diff)}: #{@meeting.title}" %>
<%= toolbar title: t(:"label_#{@content_type}"),
link_to: link_to(@meeting, @meeting) do %>
<% if authorize_for(@content_type.pluralize, :history) %>
<li class="toolbar-item">
<%= link_to({ controller: "/#{@content_type.pluralize}", action: "history", meeting_id: @meeting }, class: "button") do %>
<%= op_icon("button--icon icon-wiki") %>
<span class="button--text"><%= t(:label_history) %></span>
<% end %>
</li>
<% end %>
<% end %>
<p>
<%= t(:label_version) %> <%= link_to @diff.content_from.version, send(:"#{@content_type}_version_path", @meeting, @diff.content_from.version) %>
<em>(<%= link_to_user(@diff.content_from.user) %>, <%= format_time(@diff.content_from.created_at) %>)</em>
&#8594;
<%= t(:label_version) %> <%= link_to @diff.content_to.version, send(:"#{@content_type}_version_path", @meeting, @diff.content_to.version) %>/<%= @content.last_journal&.version || 0 %>
<em>(<%= link_to_user(@diff.content_to.user) %>, <%= format_time(@diff.content_to.created_at) %>)</em>
</p>
<hr>
<div class="text-diff">
<%= simple_format_without_paragraph @diff.to_html %>
</div>
@@ -1,148 +0,0 @@
<%#-- 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.
++#%>
<% html_title "#{t(:label_history)}: #{@meeting.title}" %>
<%= toolbar title: t(:"label_#{@content_type}"),
link_to: link_to(@meeting, @meeting) %>
<h3><%= t(:label_history) %></h3>
<%= form_tag({ action: "diff" }, method: :get) do %>
<div class="generic-table--container"
data-application-target="dynamic"
data-controller="journal-history">
<div class="generic-table--results-container">
<table class="generic-table" data-controller="table-highlighting">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th class="-short">
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span id="history-version">#</span>
<label class="hidden-for-sighted" for="history-version"><%= t(:label_version) %></label>
</div>
</div>
</th>
<th class="-short">
<div class="generic-table--empty-header"></div>
</th>
<th class="-short">
<div class="generic-table--empty-header"></div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Meeting.human_attribute_name(:updated_at) %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Meeting.human_attribute_name(:author) %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= Meeting.human_attribute_name(:comments) %>
</span>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<% show_diff = @content_versions.size > 1 %>
<% @content_versions.each_with_index do |content_version,index| %>
<tr>
<td class="id -short">
<%= if content_version.version == @content.last_journal.version
link_to(content_version.version, tab_meeting_path(@meeting, tab: @content_type.sub(/^meeting_/, "")), id: "version-#{content_version.version}")
else
link_to(content_version.version, send(:"#{@content_type}_version_path", @meeting, content_version.version), id: "version-#{content_version.version}")
end %>
</td>
<td class="checkbox -short">
<% if show_diff && (index < @content_versions.size-1) %>
<%= radio_button_tag(
"version_from",
content_version.version,
(index == 0),
class: "meetings--checkbox-version-to",
data: {
"journal-history-target": "fromVersion",
action: "journal-history#selectToVersion"
}
) %>
<label class="hidden-for-sighted" for="checkbox-from-<%= index %>"><%= t(:description_compare_from) %> <%= index %></label>
<% end %>
</td>
<td class="checkbox -short">
<% if show_diff && (index > 0) %>
<%= radio_button_tag(
"version_to",
content_version.version,
(index == 1),
data: {
"journal-history-target": "toVersion",
action: "journal-history#selectFromVersion"
}
) %>
<label class="hidden-for-sighted" for="checkbox-to-<%= index %>"><%= t(:description_compare_to) %> <%= index %></label>
<% end %>
</td>
<td id="test"><%= format_time(content_version.created_at) %></td>
<td><em><%= User.find content_version.user_id %></em></td>
<td><%= h content_version.notes %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<%= styled_button_tag t(:label_view_diff), class: "-small -primary" if show_diff %>
<%= pagination_links_full @content_versions %>
<% end %>
@@ -1,36 +0,0 @@
<%#-- 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.
++#%>
<%= render partial: "meeting_contents/show",
locals: {
content: @content,
journaled_version: @journaled_version,
content_type: @content_type,
title: "#{t(:"label_#{@content_type}")}: #{link_to @meeting, @meeting}"
} %>
@@ -1,156 +0,0 @@
<%#-- 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.
++#%>
<%= error_messages_for "meeting" %>
<section class="form--section">
<div class="form--field -required">
<%= f.text_field :title, required: true, size: 60, container_class: "-wide" %>
</div>
<% copy_from = local_assigns[:copy_from] %>
<% if copy_from.present? %>
<%= f.hidden_field :type, value: copy_from.type %>
<%= hidden_field_tag "copied_from_meeting_id", copy_from.id %>
<div class="form--field">
<%= styled_label_tag "copy_agenda", t("meeting.copy.agenda") %>
<div class="form--field-container">
<%= styled_check_box_tag "copy_agenda",
1,
true,
no_label: true,
class: "radio-button" %>
</div>
<div class="form--field-instructions">
<%= t("meeting.copy.agenda_text") %>
</div>
</div>
<div class="form--field">
<%= styled_label_tag "copy_attachments", t("meeting.copy.attachments") %>
<div class="form--field-container">
<%= styled_check_box_tag "copy_attachments",
1,
false,
no_label: true,
class: "radio-button" %>
</div>
<div class="form--field-instructions">
<%= t("meeting.copy.attachments_text") %>
</div>
</div>
<% elsif @meeting.new_record? %>
<%= f.hidden_field :type, value: "Meeting" %>
<% end %>
<% if global_meeting_create_context? %>
<div class="form--field -required">
<label class="form--label" for="project_id"><%= Meeting.human_attribute_name(:project) %>:</label>
<div class="form--field-container">
<%= angular_component_tag "opce-project-autocompleter",
inputs: {
filters: [{ name: "user_action", operator: "=", values: ["meetings/create"] }],
inputName: "project_id",
inputValue: @project&.id,
appendTo: "body",
hiddenFieldAction: "change->refresh-on-form-changes#triggerTurboStream"
},
id: "project_id",
class: "form--select-container -wide remote-field--input",
data: {
"test-selector": "project_id"
} %>
</div>
</div>
<% end %>
<div class="form--field">
<%= f.text_field :location, size: 60, container_class: "-wide" %>
</div>
<div class="form--field -required">
<label for="meeting_start_date" class="form--label"><%= Meeting.human_attribute_name(:start_date) %>
<span class="hidden-for-sighted">
<%= t(:label_start_date) %><%= t(:text_hint_date_format) %>
</span>
</label>
<div class="form--field-container -visible-overflow">
<%= f.date_picker :start_date,
id: "meeting-form-start-date",
required: true,
no_label: true %>
<label for="meeting-form-start-time" class="hidden-for-sighted">
<%= Meeting.human_attribute_name(:start_time) %>
<label lang="en">
<%= t(:label_time_zone) %>
<%= formatted_time_zone_offset %>
</label>
</label>
<%= f.text_field :start_time_hour,
id: "meeting-form-start-time",
required: true,
type: "time",
no_label: true,
step: 5.minutes,
suffix: formatted_time_zone_offset,
container_class: "-xslim" %>
</div>
</div>
<div class="form--field -required">
<label for="meeting-form-duration" class="form--label"><%= Meeting.human_attribute_name(:duration) %></label>
<div class="form--field-container">
<%= f.number_field :duration,
id: "meeting-form-duration",
required: true,
size: 5,
no_label: true,
min: 0.00,
step: 0.25,
max: 168,
suffix: t(:text_in_hours),
container_class: "-xslim" %>
</div>
</div>
<%= render partial: "meetings/participants_section" %>
<div class="form--field">
<%= styled_label_tag "send_notfications", t(:"meeting.email.send_emails") %>
<div class="form--field-container">
<%= styled_check_box_tag "send_notifications",
1,
false %>
</div>
<div class="form--field-instructions">
<%= t(:"meeting.email.send_invitation_emails") %>
</div>
</div>
</section>
@@ -1,38 +0,0 @@
<%#-- 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.
++#%>
<% html_title "#{t(:label_meeting_edit)}: #{@meeting.title}" %>
<%= toolbar title: "#{t(:label_meeting)} ##{@meeting.id}" %>
<%= labelled_tabular_form_for @meeting, url: { controller: "/meetings", action: "update" }, html: { id: "meeting-form", method: :put } do |f| -%>
<%= render partial: "form", locals: { f: f } %>
<%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %>
<%= link_to t(:button_cancel), { action: "show", id: @meeting },
class: "button -with-icon icon-cancel" %>
<% end if @project %>
@@ -1,73 +0,0 @@
<%#-- 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.
++#%>
<% copy_from = local_assigns[:copy_from] %>
<% if copy_from
page_title = t("meeting.copy.title", title: copy_from.title)
breadcrumb_element = t("meeting.copy.title", title: "<b>#{copy_from.title}</b>")
else
page_title = t(:label_meeting_new)
breadcrumb_element = t(:label_meeting_new)
end %>
<% html_title page_title %>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { page_title }
header.with_breadcrumbs(
[if @project.present?
{ href: project_overview_path(@project.id), text: @project.name }
else
{ href: home_path, text: organization_name }
end,
{ href: @project.present? ? project_meetings_path(@project.id) : meetings_path,
text: I18n.t(:label_meeting_plural) },
breadcrumb_element.html_safe]
)
end
%>
<%= labelled_tabular_form_for @meeting,
as: :meeting,
url: { controller: "/meetings", action: "create", project_id: @project },
html: {
id: "meeting-form",
target: "_top",
data: {
turbo: false,
controller: "refresh-on-form-changes",
"refresh-on-form-changes-target": "form",
"refresh-on-form-changes-turbo-stream-url-value": new_meeting_url
}
} do |f| -%>
<%= render partial: "form", locals: { f:, copy_from: } %>
<%= styled_button_tag t(:button_create), class: "-primary" %>
<%= link_to t(:button_cancel), { action: "index", project_id: @project },
class: "button" %>
<% end %>
@@ -1,154 +0,0 @@
<%#-- 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.
++#%>
<%= toolbar title: t(:label_meeting),
link_to: link_to(@meeting),
html: { class: "meeting--main-toolbar -with-dropdown" } do %>
<% unless User.current.anonymous? %>
<li class="toolbar-item hidden-for-tablet">
<div class="button">
<%= watcher_link @meeting, User.current %>
</div>
</li>
<% end %>
<% if authorize_for(:meetings, :edit) %>
<li class="toolbar-item">
<%= link_to(edit_project_meeting_path(@project, @meeting), class: "button", accesskey: accesskey(:edit)) do %>
<%= op_icon("button--icon icon-edit") %>
<span class="button--text"><%= t(:button_edit) %></span>
<% end %>
</li>
<% end %>
<li class="toolbar-item drop-down">
<a
href="#"
aria-haspopup="true"
title="<%= t(:label_more) %>"
class="button"
data-test-selector="meetings-more-dropdown-menu">
<%= op_icon("button--icon icon-show-more") %>
</a>
<ul style="display:none;" class="menu-drop-down-container">
<% if authorize_for(:meetings, :notify) %>
<li>
<%= link_to t("meeting.label_mail_all_participants"),
notify_project_meeting_path(@project, @meeting),
class: "icon-context icon-mail1",
method: :post %>
</li>
<% end %>
<% if authorize_for(:meetings, :icalendar) %>
<li>
<%= link_to t(:label_icalendar_download),
download_ics_project_meeting_path(@project, @meeting),
class: "icon-context icon-calendar2" %>
</li>
<% end %>
<% if authorize_for(:meetings, :copy) %>
<li>
<%= link_to(
t(:button_copy),
copy_project_meeting_path(@project, @meeting),
class: "icon-context icon-copy"
) %>
</li>
<% end %>
<% if authorize_for(:meetings, :destroy) %>
<li>
<%= link_to(
t(:button_delete),
project_meeting_path(@project, @meeting),
class: "icon-context icon-delete",
method: :delete,
data: { confirm: t(:text_are_you_sure) }
) %>
</li>
<% end %>
</ul>
</li>
<% end %>
<div class="meeting details box">
<div class="grid-block wrap">
<div class="grid-content small-12 block--author">
<%= avatar(@meeting.author) %>
<p class="author"><%= authoring @meeting.created_at, @meeting.author %></p>
</div>
<div class="grid-content small-6">
<p>
<strong><%= Meeting.human_attribute_name(:start_time) %></strong>: <%= format_date @meeting.start_time %> <%= format_time @meeting.start_time, include_date: false %>
- <%= format_time @meeting.end_time, include_date: false %> <%= formatted_time_zone_offset %></p>
</div>
<div class="grid-content small-6">
<p>
<strong><%= Meeting.human_attribute_name(:location) %></strong>: <%= auto_link(h(@meeting.location), link: :all, html: { target: "_blank" }) %>
</p>
</div>
<div class="grid-content small-12">
<p>
<strong><%= Meeting.human_attribute_name(:participants_invited) %></strong>: <%= format_participant_list @meeting.participants.invited %>
</p>
</div>
<div class="grid-content small-12">
<p>
<strong><%= Meeting.human_attribute_name(:participants_attended) %></strong>: <%= format_participant_list @meeting.participants.attended %>
</p>
</div>
</div>
</div>
<%= render_tabs(
[
{ name: "agenda",
action: :create_meeting_agendas,
partial: "meeting_contents/show",
path: meeting_agenda_path(@meeting),
label: :label_meeting_agenda,
content: @meeting.agenda || @meeting.build_agenda,
content_type: "meeting_agenda" },
{
name: "minutes",
action: :create_meeting_minutes,
partial: "meeting_contents/show",
path: meeting_minutes_path(@meeting),
label: :label_meeting_minutes, content: @meeting.minutes || @meeting.build_minutes,
content_type: "meeting_minutes"
}
]
) %>
<% if @meeting.journals.changing.present? %>
<div id="history">
<h3><%= t(:label_history) %></h3>
<% @meeting.journals.each do |journal| %>
<%= render_meeting_journal @meeting, journal %>
<% end %>
</div>
<% end %>
+1 -6
View File
@@ -82,11 +82,9 @@ en:
invalid_time_format: "is not a valid time. Required format: HH:MM"
models:
recurring_meeting: "Recurring meeting"
structured_meeting: "One-time meeting"
meeting: "Meeting"
meeting: "One-time meeting"
meeting_agenda_item: "Agenda item"
meeting_agenda: "Agenda"
meeting_minutes: "Minutes"
meeting_section: "Section"
activity:
@@ -234,9 +232,6 @@ en:
new_date_time: "New date/time"
label_mail_all_participants: "Send email to all participants"
types:
classic: "Classic (unsupported)"
classic_text: "Note: Classic meetings will be removed in the next version of OpenProject."
structured: "Structured"
one_time: "One-time"
recurring: "Recurring"
recurring_text: "Create meeting series with dynamic template for each occurrence."
@@ -1,4 +1,5 @@
# frozen_string_literal: true
class CreateMeetingSections < ActiveRecord::Migration[7.1]
def up
create_table :meeting_sections do |t|
@@ -24,7 +25,7 @@ class CreateMeetingSections < ActiveRecord::Migration[7.1]
private
def create_and_assign_default_section
StructuredMeeting.includes(:agenda_items).find_each do |meeting|
Meeting.includes(:agenda_items).find_each do |meeting|
section = MeetingSection.create!(
meeting:,
title: "Untitled"
@@ -1,37 +0,0 @@
# frozen_string_literal: true
#-- 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 API
module V3
module MeetingAgendas
class MeetingAgendaRepresenter < API::V3::MeetingContents::MeetingContentRepresenter
end
end
end
end
@@ -1,58 +0,0 @@
# frozen_string_literal: true
#-- 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 API
module V3
module MeetingContents
class MeetingContentRepresenter < ::API::Decorators::Single
include API::Decorators::LinkedResource
include API::Caching::CachedRepresenter
include ::API::V3::Attachments::AttachableRepresenterMixin
self_link title_getter: ->(*) {}
property :id
associated_resource :project,
link: ->(*) do
next unless represented.project.present?
{
href: api_v3_paths.project(represented.project.id),
title: represented.project.name
}
end
def _type
"MeetingContent"
end
end
end
end
end
@@ -1,37 +0,0 @@
# frozen_string_literal: true
#-- 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 API
module V3
module MeetingMinutes
class MeetingMinutesRepresenter < API::V3::MeetingContents::MeetingContentRepresenter
end
end
end
end
@@ -48,10 +48,6 @@ module API
lock_version.to_i
}
property :type,
as: :meeting_type,
getter: ->(*) { type }
date_time_property :start_time
date_time_property :end_time
@@ -69,7 +69,7 @@ RSpec.describe Meetings::DeleteDialogComponent, type: :component do
context "with an associated recurring/templated meeting" do
let(:series) { build_stubbed(:recurring_meeting) }
let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series) }
let(:meeting) { build_stubbed(:meeting_template, recurring_meeting: series) }
it "shows a heading" do
expect(subject).to have_text "Cancel this meeting occurrence?"
@@ -61,7 +61,7 @@ RSpec.describe Meetings::RowComponent, type: :component do
context "with an associated recurring/templated meeting" do
let(:series) { build_stubbed(:recurring_meeting, project:) }
let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series, project:) }
let(:meeting) { build_stubbed(:meeting_template, recurring_meeting: series, project:) }
it "shows default menu items" do
expect(subject).to have_link "View meeting series"
@@ -99,7 +99,7 @@ RSpec.describe Meetings::RowComponent, type: :component do
context "with an associated recurring/templated meeting" do
let(:series) { build_stubbed(:recurring_meeting, project:) }
let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting: series, project:) }
let(:meeting) { build_stubbed(:meeting_template, recurring_meeting: series, project:) }
context "without a current project" do
it "shows delete menu item with a back url" do
@@ -49,7 +49,7 @@ RSpec.describe Meetings::SidePanel::DetailsComponent, type: :component do
frequency: "working_days")
end
let(:meeting) do
build_stubbed(:structured_meeting_template,
build_stubbed(:meeting_template,
recurring_meeting: series)
end
@@ -65,7 +65,7 @@ RSpec.describe Meetings::SidePanel::DetailsComponent, type: :component do
frequency: "weekly")
end
let(:meeting) do
build_stubbed(:structured_meeting_template,
build_stubbed(:meeting_template,
recurring_meeting: series)
end
@@ -35,7 +35,7 @@ RSpec.describe RecurringMeetings::DeleteDialogComponent, type: :component do
let(:project) { build_stubbed(:project) }
let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 6) }
let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting:) }
let(:meeting) { build_stubbed(:meeting_template, recurring_meeting:) }
let(:user) { build_stubbed(:user) }
subject do
@@ -37,7 +37,7 @@ RSpec.describe RecurringMeetings::DeleteScheduledDialogComponent, type: :compone
let(:recurring_meeting) { build_stubbed(:recurring_meeting, project:, end_after: :iterations, iterations: 6) }
let(:start_time) { 1.day.from_now }
let(:scheduled_meeting) { recurring_meeting.scheduled_meetings.find_or_initialize_by(start_time: start_time) }
let(:meeting) { build_stubbed(:structured_meeting_template, recurring_meeting:) }
let(:meeting) { build_stubbed(:meeting_template, recurring_meeting:) }
let(:user) { build_stubbed(:user) }
subject do
@@ -35,7 +35,7 @@ RSpec.describe MeetingAgendaItems::CreateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
let(:meeting) { create(:structured_meeting, project:) }
let(:meeting) { create(:meeting, project:) }
let(:item) { build(:meeting_agenda_item, meeting:) }
let(:contract) { described_class.new(item, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingAgendaItems::DeleteContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
shared_let(:item) { create(:meeting_agenda_item, meeting:) }
let(:contract) { described_class.new(item, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingAgendaItems::UpdateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
shared_let(:item) { create(:meeting_agenda_item, meeting:) }
let(:contract) { described_class.new(item, user) }
@@ -1,56 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
require "contracts/shared/model_contract_shared_context"
RSpec.describe MeetingContents::UpdateContract do
include_context "ModelContract shared context"
let(:agenda) { build_stubbed(:meeting_agenda) }
let(:current_user) { build_stubbed(:user) }
let(:contract) { described_class.new(agenda, current_user) }
context "when not editable" do
before do
allow(agenda).to receive(:editable?).and_return false
end
it_behaves_like "contract is invalid", base: :error_readonly
end
context "when editable" do
before do
allow(agenda).to receive(:editable?).and_return true
end
it_behaves_like "contract is valid"
end
include_examples "contract reuses the model errors"
end
@@ -35,7 +35,7 @@ RSpec.describe MeetingOutcomes::CreateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
let(:meeting) { create(:structured_meeting, project:) }
let(:meeting) { create(:meeting, project:) }
let(:meeting_agenda_item) { create(:meeting_agenda_item, meeting:) }
let(:outcome) { build(:meeting_outcome, meeting_agenda_item:) }
let(:contract) { described_class.new(outcome, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingOutcomes::DeleteContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
let(:meeting_agenda_item) { create(:meeting_agenda_item, meeting:) }
let(:outcome) { build_stubbed(:meeting_outcome, meeting_agenda_item:) }
let(:contract) { described_class.new(outcome, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingOutcomes::UpdateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
let(:meeting) { create(:structured_meeting, project:) }
let(:meeting) { create(:meeting, project:) }
let(:meeting_agenda_item) { create(:meeting_agenda_item, meeting:) }
let(:outcome) { build(:meeting_outcome, meeting_agenda_item:) }
let(:contract) { described_class.new(outcome, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingSections::CreateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
let(:meeting) { create(:structured_meeting, project:) }
let(:meeting) { create(:meeting, project:) }
let(:section) { build(:meeting_section, meeting:) }
let(:contract) { described_class.new(section, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingSections::DeleteContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
let(:section) { create(:meeting_section, meeting:) }
let(:contract) { described_class.new(section, user) }
@@ -35,7 +35,7 @@ RSpec.describe MeetingSections::UpdateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
shared_let(:section) { create(:meeting_section, meeting:) }
let(:contract) { described_class.new(section, user) }
@@ -35,7 +35,7 @@ RSpec.describe Meetings::CreateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
let(:meeting) { build(:structured_meeting, project:) }
let(:meeting) { build(:meeting, project:) }
let(:contract) { described_class.new(meeting, user) }
context "with permission" do
@@ -35,7 +35,7 @@ RSpec.describe Meetings::UpdateContract do
include_context "ModelContract shared context"
shared_let(:project) { create(:project) }
shared_let(:meeting) { create(:structured_meeting, project:) }
shared_let(:meeting) { create(:meeting, project:) }
let(:contract) { described_class.new(meeting, user) }
context "with permission" do
@@ -1,221 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "#{File.dirname(__FILE__)}/../spec_helper"
RSpec.describe MeetingsController do
shared_let(:user) { create(:admin) }
shared_let(:project) { create(:project) }
shared_let(:other_project) { create(:project) }
current_user { user }
describe "GET" do
describe "index" do
let(:meetings) do
[
create(:meeting, project:),
create(:meeting, author: user, project:),
create(:meeting, author: user, project: other_project)
]
end
end
describe "show" do
let(:meeting) { create(:meeting, project:, agenda: nil) }
describe "html" do
before do
get "show", params: { id: meeting.id }
end
it { expect(response).to redirect_to(project_meeting_path(project, meeting)) }
it { expect(assigns(:meeting)).to eql meeting }
end
end
describe "new" do
let(:meeting) { Meeting.new(project:) }
before do
allow(Project).to receive(:find).and_return(project)
allow(Meeting).to receive(:new).and_return(meeting)
end
shared_examples_for "new action" do |response_type:|
describe response_type do
context "when requesting the page without a project id" do
before do
get "new"
end
it { expect(response).to be_successful }
it { expect(assigns(:meeting)).to eql meeting }
it { expect(assigns(:project)).to be_nil }
end
context "when requesting the page with a project id" do
before do
get "new", params: { project_id: project.id }
end
it { expect(response).to be_successful }
it { expect(assigns(:meeting)).to eql meeting }
it { expect(assigns(:project)).to eql project }
end
end
end
it_behaves_like "new action", response_type: "html"
it_behaves_like "new action", response_type: "turbo_stream"
end
describe "edit" do
let(:meeting) { create(:meeting, project:) }
describe "html" do
before do
get "edit", params: { project_id: meeting.project_id, id: meeting.id }
end
it { expect(response).to be_successful }
it { expect(assigns(:meeting)).to eql meeting }
end
end
end
describe "POST" do
describe "create" do
render_views
let(:base_params) do
{
project_id: project&.id,
meeting: meeting_params
}
end
let(:base_meeting_params) do
{
title: "Foobar",
duration: "1.0",
start_date: "2015-06-01",
start_time_hour: "10:00"
}
end
let(:params) { base_params }
let(:meeting_params) { base_meeting_params }
before do
post :create,
params:
end
context "with a project_id" do
context "and an invalid start_date with start_time_hour" do
let(:meeting_params) do
base_meeting_params.merge(start_date: "-")
end
it "renders an error" do
expect(response).to have_http_status :unprocessable_entity
expect(response).to render_template :new
expect(response.body)
.to have_text("Date #{I18n.t('activerecord.errors.messages.not_an_iso_date')}")
end
end
context "and an invalid start_time_hour with start_date" do
let(:meeting_params) do
base_meeting_params.merge(start_time_hour: "-")
end
it "renders an error" do
expect(response).to have_http_status :unprocessable_entity
expect(response).to render_template :new
expect(response.body)
.to have_text("Start time #{I18n.t('activerecord.errors.messages.invalid_time_format')}")
end
end
end
context "with a nil project_id" do
let(:project) { nil }
it "renders an error" do
expect(response).to have_http_status :unprocessable_entity
expect(response).to render_template :new
expect(response.body)
.to have_text("Project #{I18n.t('activerecord.errors.messages.blank')}")
end
end
context "without a project_id" do
let(:params) { base_params.except(:project_id) }
let(:project) { nil }
it "renders an error" do
expect(response).to have_http_status :unprocessable_entity
expect(response).to render_template :new
expect(response.body)
.to have_text("Project #{I18n.t('activerecord.errors.messages.blank')}")
end
end
end
end
describe "notify" do
let!(:meeting) { create(:meeting) }
let!(:participant) { create(:meeting_participant, meeting:, attended: true) }
it "produces a background job for notification" do
post :notify, params: { project_id: meeting.project_id, id: meeting.id }
perform_enqueued_jobs
expect(ActionMailer::Base.deliveries.count).to eq(1)
end
context "with an error during deliver" do
before do
allow(MeetingMailer).to receive(:invited).and_raise(Net::SMTPError)
end
it "produces a flash message containing the mail addresses raising the error" do
expect { post :notify, params: { project_id: meeting.project_id, id: meeting.id } }.not_to raise_error
meeting.participants.each do |participant|
expect(flash[:error]).to include(participant.name)
end
perform_enqueued_jobs
expect(ActionMailer::Base.deliveries.count).to eq(0)
end
end
end
end
@@ -1,34 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
FactoryBot.define do
factory :meeting_agenda do |_a|
meeting
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,7 +30,7 @@
FactoryBot.define do
factory :meeting_agenda_item do |m|
meeting factory: :structured_meeting
meeting factory: :meeting
work_package { nil }
author factory: :user
duration_in_minutes { 10 }
@@ -1,33 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
FactoryBot.define do
factory :journal_meeting_content_journal, class: "Journal::MeetingContentJournal" do
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -47,12 +48,8 @@ FactoryBot.define do
meeting.project = evaluator.project if evaluator.project
end
factory :structured_meeting, class: "StructuredMeeting" do |structured_meeting|
structured_meeting.sequence(:title) { |n| "Structured meeting #{n}" }
end
factory :structured_meeting_template, class: "StructuredMeeting" do |structured_meeting|
structured_meeting.sequence(:title) { |n| "Structured meeting template #{n}" }
factory :meeting_template do |meeting|
meeting.sequence(:title) { |n| "Meeting template #{n}" }
template { true }
recurring_meeting
@@ -1,34 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
FactoryBot.define do
factory :meeting_minutes do |_m|
meeting
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,7 +30,7 @@
FactoryBot.define do
factory :meeting_section do |m|
meeting factory: :structured_meeting
meeting factory: :meeting
m.sequence(:title) { |n| "Section #{n}" }
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -48,7 +49,7 @@ FactoryBot.define do
recurring_meeting.project = project
# create template
template = create(:structured_meeting_template,
template = create(:meeting_template,
:author_participates,
author: recurring_meeting.author,
recurring_meeting:,
@@ -34,8 +34,6 @@ RSpec.describe "Meetings", :js do
let(:user) { create(:admin) }
let!(:meeting) { create(:meeting, project:, title: "Awesome meeting!") }
let!(:agenda) { create(:meeting_agenda, meeting:, text: "foo") }
let!(:minutes) { create(:meeting_minutes, meeting:, text: "minutes") }
before do
login_as(user)
@@ -47,9 +45,6 @@ RSpec.describe "Meetings", :js do
check "Meetings"
click_on "Apply"
expect(page).to have_css(".op-activity-list--item-title", text: "Minutes: Awesome meeting!")
expect(page).to have_css(".op-activity-list--item-title", text: "Agenda: Awesome meeting!")
expect(page).to have_css(".op-activity-list--item-title", text: "Meeting: Awesome meeting!")
end
end
@@ -1,79 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
require "features/page_objects/notification"
RSpec.describe "Add an attachment to a meeting (agenda)", :js, :selenium do
let(:role) do
create(:project_role, permissions: %i[view_meetings edit_meetings create_meeting_agendas])
end
let(:dev) do
create(:user, member_with_roles: { project => role })
end
let(:project) { create(:project) }
let(:meeting) do
create(
:meeting,
project:,
title: "Versammlung",
agenda: create(:meeting_agenda, text: "Versammlung")
)
end
let(:attachments) { Components::Attachments.new }
let(:image_fixture) { UploadedFile.load_from("spec/fixtures/files/image.png") }
let(:editor) { Components::WysiwygEditor.new }
let(:attachments_list) { Components::AttachmentsList.new }
before do
login_as(dev)
visit "/meetings/#{meeting.id}"
within "#tab-content-agenda .toolbar" do
click_on "Edit"
end
end
describe "wysiwyg editor" do
context "if on an existing page" do
it "can upload an image via drag & drop" do
find(".ck-content")
editor.expect_button "Upload image from computer"
editor.drag_attachment image_fixture.path, "Some image caption"
click_on "Save"
content = find_test_selector("op-meeting--meeting_agenda")
expect(content).to have_css("img")
expect(content).to have_content("Some image caption")
end
end
end
describe "attachment dropzone" do
it "can upload an image via attaching and drag & drop" do
editor.wait_until_loaded
attachments_list.wait_until_visible
##
# Attach file manually
editor.attachments_list.expect_empty
attachments.attach_file_on_input(image_fixture.path)
editor.wait_until_upload_progress_toaster_cleared
editor.attachments_list.expect_attached("image.png")
##
# and via drag & drop
editor.attachments_list.drag_enter
editor.attachments_list.drop(image_fixture)
editor.wait_until_upload_progress_toaster_cleared
editor.attachments_list.expect_attached("image.png", count: 2)
end
end
end
@@ -1,97 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
RSpec.describe "Meetings close" do
let(:project) { create(:project, enabled_module_names: %w[meetings]) }
let(:user) do
create(:user,
member_with_permissions: { project => permissions })
end
let(:other_user) do
create(:user,
member_with_permissions: { project => permissions })
end
let!(:meeting) { create(:meeting, :author_participates, project:, title: "Own awesome meeting!", author: user) }
let!(:meeting_agenda) { create(:meeting_agenda, meeting:, text: "asdf") }
before do
login_as(user)
end
context "with permission to close meetings",
:js do
let(:permissions) { %i[view_meetings close_meeting_agendas] }
it "can delete own and other`s meetings" do
visit project_meetings_path(project)
click_on meeting.title
# 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
find(".op-tab-row--link", text: "AGENDA").click
wait_for_network_idle
accept_confirm do
find(".button", text: "Close").click
end
# Expect to be on minutes
expect(page).to have_css(".op-tab-row--link_selected", text: "MINUTES")
# Copies the text
expect(page).to have_css("#tab-content-minutes", text: "asdf")
# Go back to agenda, expect we can open it again
find(".op-tab-row--link", text: "AGENDA").click
accept_confirm do
find(".button", text: "Open").click
end
expect(page).to have_css(".button", text: "Close")
end
end
context "without permission to close meetings" do
let(:permissions) { %i[view_meetings] }
it "cannot delete own and other`s meetings" do
visit project_meetings_path(project)
expect(page)
.to have_no_link "Close"
end
end
end
@@ -1,133 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
RSpec.describe "Meetings copy", :js do
shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) }
shared_let(:permissions) { %i[view_meetings create_meetings] }
shared_let(:user) do
create(:user,
member_with_permissions: { project => permissions }).tap do |u|
u.pref[:time_zone] = "Etc/UTC"
u.save!
end
end
shared_let(:other_user) do
create(:user,
member_with_permissions: { project => permissions })
end
shared_let(:start_time) { Time.current.next_day.at_noon }
shared_let(:duration) { 1.5 }
shared_let(:agenda_text) { "We will talk" }
shared_let(:meeting) do
create(:meeting,
:author_participates,
author: user,
project:,
title: "Awesome meeting!",
location: "Meeting room",
duration:,
start_time:).tap do |m|
create(:meeting_agenda, meeting: m, text: agenda_text)
create(:meeting_participant, :attendee, meeting: m, user: other_user)
end
end
shared_let(:twelve_hour_format) { "%I:%M %p" }
shared_let(:copied_meeting_time_heading) do
date = (start_time + 1.day).strftime("%m/%d/%Y")
start_of_meeting = start_time.strftime(twelve_hour_format)
end_of_meeting = (start_time + meeting.duration.hours).strftime(twelve_hour_format)
"Start time: #{date} #{start_of_meeting} - #{end_of_meeting} UTC+00:00"
end
before do
login_as user
end
it "copying a meeting" do
visit project_meetings_path(project)
click_on meeting.title
find_test_selector("meetings-more-dropdown-menu").click
page.within(".menu-drop-down-container") do
click_on "Copy"
end
expect(page)
.to have_field "Title", with: meeting.title
expect(page)
.to have_field "Location", with: meeting.location
expect(page)
.to have_field "Duration", with: meeting.duration
expect(page)
.to have_field "Start date", with: (start_time + 1.day).strftime("%Y-%m-%d")
expect(page)
.to have_field "Time", with: start_time.strftime("%H:%M")
click_on "Create"
# Be on the new meeting's page with copied over attributes
expect(page).to have_no_current_path meeting_path(meeting.id)
expect(page)
.to have_content("Added by #{user.name}")
expect(page)
.to have_content("Meeting: #{meeting.title}")
expect(page)
.to have_content(copied_meeting_time_heading)
expect(page)
.to have_content("Location: #{meeting.location}")
# Copies the invitees
expect(page)
.to have_content "Invitees: #{other_user.name}"
# Does not copy the attendees
expect(page)
.to have_no_content "Attendees: #{other_user.name}"
expect(page)
.to have_content "Attendees:"
# Copies the agenda
click_on "Agenda"
expect(page)
.to have_content agenda_text
# Adds an entry to the history
click_on "History"
expect(page)
.to have_content("Copied from Meeting ##{meeting.id}")
end
end
@@ -1,101 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
RSpec.describe "Meetings deletion" do
let(:project) { create(:project, enabled_module_names: %w[meetings]) }
let(:user) do
create(:user,
member_with_permissions: { project => permissions })
end
let(:other_user) do
create(:user,
member_with_permissions: { project => permissions })
end
let!(:meeting) { create(:meeting, project:, title: "Own awesome meeting!", author: user) }
let!(:other_meeting) { create(:meeting, project:, title: "Other awesome meeting!", author: other_user) }
let(:index_path) { project_meetings_path(project) }
before do
create(:meeting_participant, :invitee, user:, meeting:)
create(:meeting_participant, :invitee, user:, meeting: other_meeting)
login_as(user)
end
context "with permission to delete meetings", :js do
let(:permissions) { %i[view_meetings delete_meetings] }
it "can delete own and other's meetings" do
visit index_path
click_on meeting.title
accept_confirm do
find_test_selector("meetings-more-dropdown-menu").click
click_on "Delete"
end
expect(page)
.to have_current_path index_path
click_on other_meeting.title
accept_confirm do
find_test_selector("meetings-more-dropdown-menu").click
click_on "Delete"
end
expect(page)
.to have_content(I18n.t("meeting.blankslate.title"))
expect(page)
.to have_current_path index_path
end
end
context "without permission to delete meetings" do
let(:permissions) { %i[view_meetings] }
it "cannot delete own and other's meetings" do
visit index_path
click_on meeting.title
expect(page)
.to have_no_link "Delete"
visit index_path
click_on other_meeting.title
expect(page)
.to have_no_link "Delete"
end
end
end
@@ -1,66 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
RSpec.describe "Meetings locking", :js do
let(:project) { create(:project, enabled_module_names: %w[meetings]) }
let(:user) { create(:admin) }
let!(:meeting) { create(:meeting, project:) }
let!(:agenda) { create(:meeting_agenda, meeting:) }
let(:agenda_field) do
TextEditorField.new(page,
"",
selector: test_selector("op-meeting--meeting_agenda"))
end
before do
login_as(user)
end
it "shows an error when trying to update a meeting update while editing" do
visit meeting_path(meeting)
# Edit agenda
within "#tab-content-agenda" do
find(".button--edit-agenda").click
agenda_field.set_value("Some new text")
agenda.text = "blabla"
agenda.save!
click_on "Save"
end
expect(page).to have_text "Information has been updated by at least one other user in the meantime."
agenda_field.expect_value("Some new text")
end
end
@@ -1,349 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
require_relative "../support/pages/meetings/index"
RSpec.describe "Meetings new", :js do
shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) }
shared_let(:admin) { create(:admin) }
let(:time_zone) { "Etc/UTC" }
let(:user) do
create(:user,
lastname: "First",
member_with_permissions: { project => permissions }).tap do |u|
u.pref[:time_zone] = time_zone
u.save!
end
end
let(:other_user) do
create(:user,
lastname: "Second",
member_with_permissions: { project => permissions })
end
let(:permissions) { %i[view_meetings create_meetings] }
let(:current_user) { user }
before do
login_as current_user
end
context "when creating a meeting from the global page" do
before do
other_user
project
end
let(:index_page) { Pages::Meetings::Index.new(project: nil) }
let(:new_page) { Pages::Meetings::New.new(nil) }
context "with permission to create meetings" do
it "does not render menus" do
new_page.visit!
new_page.expect_no_main_menu
end
describe "clicking on the create new meeting button" do
it "navigates to the global create form" do
index_page.visit!
index_page.click_create_new
expect(page).to have_current_path(new_page.path)
end
end
["CET", "UTC", "", "Pacific Time (US & Canada)"].each do |zone|
let(:time_zone) { zone }
it "allows creating a project and handles errors in time zone #{zone}" do
new_page.visit!
expect_angular_frontend_initialized # Wait for project dropdown to be ready
new_page.set_title "Some title"
new_page.set_project project
new_page.set_start_date "2013-03-28"
new_page.set_start_time "13:30"
new_page.set_duration "1.5"
new_page.invite(other_user)
show_page = new_page.click_create
expect_flash(message: "Successful creation.")
show_page.expect_invited(user, other_user)
show_page.expect_date_time "03/28/2013 01:30 PM - 03:00 PM"
end
end
context "without a title set" do
before do
new_page.visit!
# Wait for project dropdown to be initialized
expect_angular_frontend_initialized
new_page.set_project project
new_page.set_start_date "2013-03-28"
new_page.set_start_time "13:30"
new_page.set_duration "1.5"
new_page.invite(other_user)
end
it "renders a validation error" do
expect do
new_page.click_create
end.not_to change(Query, :count)
# HTML required attribute validation error
expect(page).to have_current_path(new_page.path)
end
end
context "without a project set" do
before do
new_page.visit!
new_page.set_title "Some title"
new_page.set_start_date "2013-03-28"
new_page.set_start_time "13:30"
new_page.set_duration "1.5"
end
it "renders a validation error" do
new_page.click_create
expect_flash(type: :error,
message: "#{Project.model_name.human} #{I18n.t('activerecord.errors.messages.blank')}")
new_page.expect_project_dropdown
end
end
end
context "without permission to create meetings" do
let(:permissions) { %i[view_meetings] }
it "shows no edit link" do
index_page.visit!
index_page.expect_no_create_new_button
end
end
context "as an admin" do
let(:current_user) { admin }
it "allows creating meeting in a project without members" do
new_page.visit!
expect_angular_frontend_initialized # Wait for project dropdown to be ready
new_page.set_title "Some title"
new_page.set_project project
wait_for_network_idle # Wait for participant section to be fetched
show_page = new_page.click_create
expect_flash(message: "Successful creation.")
# Not sure if that is then intended behaviour but that is what is currently programmed
show_page.expect_invited(admin)
end
context "without a project set" do
before do
new_page.visit!
new_page.set_title "Some title"
end
it "renders a validation error" do
new_page.click_create
expect(page).to have_text "#{Project.model_name.human} #{I18n.t('activerecord.errors.messages.blank')}"
new_page.expect_project_dropdown
end
end
context "without a title set" do
before do
new_page.visit!
# Wait for project dropdown to be initialized
expect_angular_frontend_initialized
new_page.set_project project
end
it "renders a validation error" do
expect do
new_page.click_create
end.not_to change(Query, :count)
# HTML required attribute validation error
expect(page).to have_current_path(new_page.path)
end
end
end
end
context "when creating a meeting from the project-specific page" do
let(:index_page) { Pages::Meetings::Index.new(project:) }
let(:new_page) { Pages::Meetings::New.new(project) }
context "with permission to create meetings" do
before do
other_user
end
describe "clicking on the create new meeting button" do
it "navigates to the project-specific create form" do
index_page.visit!
index_page.click_create_new
expect(page).to have_current_path(new_page.path)
end
end
["CET", "UTC", "", "Pacific Time (US & Canada)"].each do |zone|
let(:time_zone) { zone }
it "allows creating a project and handles errors in time zone #{zone}" do
new_page.visit!
new_page.set_title "Some title"
new_page.set_start_date "2013-03-28"
new_page.set_start_time "13:30"
new_page.set_duration "1.5"
new_page.invite(other_user)
show_page = new_page.click_create
expect_flash(message: "Successful creation.")
show_page.expect_invited(user, other_user)
show_page.expect_date_time "03/28/2013 01:30 PM - 03:00 PM"
end
end
context "without a title set" do
before do
new_page.visit!
new_page.set_start_date "2013-03-28"
new_page.set_start_time "13:30"
new_page.set_duration "1.5"
new_page.invite(other_user)
end
it "renders a validation error" do
expect do
new_page.click_create
end.not_to change(Query, :count)
# HTML required attribute validation error
expect(page).to have_current_path(new_page.path)
end
end
end
context "without permission to create meetings" do
let(:permissions) { %i[view_meetings] }
it "shows no edit link" do
index_page.visit!
index_page.expect_no_create_new_button
end
end
context "as an admin" do
let(:current_user) { admin }
let(:field) do
TextEditorField.new(page,
"",
selector: test_selector("op-meeting--meeting_agenda"))
end
it "allows creating meeting in a project without members" do
new_page.visit!
new_page.set_title "Some title"
show_page = new_page.click_create
expect_flash(message: "Successful creation.")
# Not sure if that is then intended behaviour but that is what is currently programmed
show_page.expect_invited(admin)
end
context "without a title set" do
before do
new_page.visit!
end
it "renders a validation error" do
expect do
new_page.click_create
end.not_to change(Query, :count)
# HTML required attribute validation error
expect(page).to have_current_path(new_page.path)
end
end
it "can save the meeting agenda via cmd+Enter" do
new_page.visit!
new_page.set_title "Some title"
new_page.click_create
expect_flash(message: "Successful creation.")
meeting = Meeting.last
field.set_value("My new meeting text")
field.submit_by_enter
expect_flash(message: "Successful update")
meeting.reload
expect(meeting.agenda.text).to eq "My new meeting text"
end
end
end
end
@@ -1,97 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
require_relative "../support/pages/meetings/edit"
RSpec.describe "Meetings participants" do
let(:project) { create(:project, enabled_module_names: %w[meetings]) }
let!(:user) do
create(:user,
firstname: "Current",
member_with_permissions: { project => %i[view_meetings edit_meetings] })
end
let!(:viewer_user) do
create(:user,
firstname: "Viewer",
member_with_permissions: { project => %i[view_meetings] })
end
let!(:non_viewer_user) do
create(:user,
firstname: "Nonviewer",
member_with_permissions: { project => %i[] })
end
let(:edit_page) { Pages::Meetings::Edit.new(meeting) }
let!(:meeting) { create(:meeting, project:, title: "Awesome meeting!") }
before do
login_as(user)
end
it "allows setting members to participants which are allowed to view the meeting" do
edit_page.visit!
edit_page.expect_available_participant(user)
edit_page.expect_available_participant(viewer_user)
edit_page.expect_not_available_participant(non_viewer_user)
edit_page.invite(viewer_user)
show_page = edit_page.click_save
expect_flash(message: "Successful update")
show_page.expect_invited(viewer_user)
show_page.click_edit
edit_page.uninvite(viewer_user)
show_page = edit_page.click_save
expect_flash(message: "Successful update")
show_page.expect_uninvited(viewer_user)
end
context "with an invalid user reference" do
let(:show_page) { Pages::Meetings::Show.new(meeting) }
let(:meeting_participant) { create(:meeting_participant, user: viewer_user, meeting:) }
before do
meeting_participant.update_column(:user_id, 12341234)
end
it "still allows to view the meeting" do
show_page.visit!
show_page.expect_invited meeting.author
show_page.expect_uninvited viewer_user
end
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -35,7 +36,7 @@ RSpec.describe "Meeting search", :js do
let(:role) { create(:project_role, permissions: %i(view_meetings view_work_packages)) }
let(:user) { create(:user, member_with_roles: { project => role }) }
let!(:meeting) { create(:structured_meeting, project:) }
let!(:meeting) { create(:meeting, project:) }
let!(:agenda_item) { create(:meeting_agenda_item, meeting:) }
before do
@@ -1,183 +0,0 @@
# frozen_string_literal: true
#-- 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.
#++
require "spec_helper"
require_relative "../support/pages/meetings/show"
RSpec.describe "Meetings", :js do
let(:project) { create(:project, enabled_module_names: %w[meetings]) }
let(:role) { create(:project_role, permissions:) }
let(:user) do
create(:user,
member_with_roles: { project => role })
end
let!(:meeting) { create(:meeting, project:, title: "Awesome meeting!") }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
current_user { user }
describe "navigate to meeting page" do
before do
create(:meeting_participant, :invitee, user:, meeting:)
end
let(:permissions) { %i[view_meetings] }
it "can visit the meeting" do
visit meetings_path(project)
find("div.title a", text: "Awesome meeting!", wait: 10).click
expect(page).to have_css("h2", text: "Meeting: Awesome meeting!")
expect(page).to have_test_selector("op-meeting--meeting_agenda",
text: "There is currently nothing to display")
end
context "with a location" do
context "as a valid url" do
it "renders a link to the meeting location" do
show_page.visit!
show_page.expect_link_to_location(meeting.location)
end
end
context "as an invalid url" do
before do
meeting.update!(location: "badurl")
end
it "renders the meeting location as plaintext" do
show_page.visit!
show_page.expect_plaintext_location(meeting.location)
end
end
end
context "with an open agenda" do
let!(:agenda) { create(:meeting_agenda, meeting:, text: "foo") }
let(:agenda_update) { create(:meeting_agenda, meeting:, text: "bla") }
it "shows the agenda" do
visit meeting_path(meeting)
expect(page).to have_test_selector("op-meeting--meeting_agenda",
text: "foo")
# May not edit
expect(page).to have_no_css(".button--edit-agenda")
expect(page).not_to have_test_selector("op-meeting--meeting_agenda",
text: "Edit")
end
it "can view history" do
agenda_update
visit meeting_path(meeting)
click_on "History"
find_by_id("version-1").click
expect(page).to have_test_selector("op-meeting--meeting_agenda", text: "foo")
end
context "and edit permissions" do
let(:permissions) { %i[view_meetings create_meeting_agendas] }
let(:field) do
TextEditorField.new(page,
"",
selector: test_selector("op-meeting--meeting_agenda"))
end
it "can edit the agenda" do
visit meeting_path(meeting)
find(".toolbar-item", text: "Edit").click
field.expect_value("foo")
field.set_value("My new meeting text")
field.submit_by_enter
expect_and_dismiss_flash(message: "Successful update")
meeting.reload
expect(meeting.agenda.text).to eq "My new meeting text"
end
end
context "and edit minutes permissions" do
let(:permissions) { %i[view_meetings create_meeting_minutes] }
it "can not edit the minutes" do
visit meeting_path(meeting)
click_on "Minutes"
expect(page).not_to have_test_selector("op-meeting--meeting_minutes", text: "Edit")
expect(page).to have_test_selector("op-meeting--meeting_minutes",
text: "There is currently nothing to display")
end
end
end
context "with a locked agenda" do
let!(:agenda) { create(:meeting_agenda, meeting:, text: "foo", locked: true) }
it "shows the minutes when visiting" do
visit meeting_path(meeting)
expect(page).to have_no_css("h2", text: "Agenda")
expect(page).to have_no_css("#meeting_minutes_text")
expect(page).to have_css("h2", text: "Minutes")
end
context "and edit permissions" do
let(:permissions) { %i[view_meetings create_meeting_minutes] }
let(:field) do
TextEditorField.new(page,
"",
selector: test_selector("op-meeting--meeting_minutes"))
end
it "can edit the minutes" do
visit meeting_path(meeting)
field.set_value("This is what we talked about")
click_on "Save"
expect(page)
.to have_css(".op-uc-container",
text: "This is what we talked about")
end
end
end
end
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,8 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -61,7 +61,7 @@ RSpec.describe "Recurring meetings creation",
let(:current_user) { user }
let(:meeting) { RecurringMeeting.last }
let(:show_page) { Pages::RecurringMeeting::Show.new(meeting) }
let(:template_page) { Pages::StructuredMeeting::Show.new(meeting.template) }
let(:template_page) { Pages::Meetings::Show.new(meeting.template) }
let(:meetings_page) { Pages::Meetings::Index.new(project:) }
before do
@@ -30,8 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -30,8 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -29,8 +29,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -30,8 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -30,8 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/recurring_meeting/show"
require_relative "../../support/pages/meetings/index"
@@ -60,8 +59,8 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
end_after: "never",
author: user_with_manage_permissions
end
shared_let(:structured_meeting) do
create :structured_meeting,
shared_let(:meeting) do
create :meeting,
project:,
start_time: DateTime.parse("2025-01-28T10:30:00Z"),
duration: 1,
@@ -75,10 +74,8 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
series.meetings.not_templated.first
end
let!(:agenda_item) { create(:meeting_agenda_item, meeting: structured_meeting, title: "Test notes") }
let!(:series_agenda_item) { create(:meeting_agenda_item, meeting: recurring_meeting, title: "Test notes") }
let(:meeting_page) { Pages::StructuredMeeting::Show.new(meeting) }
let!(:agenda_item) { create(:meeting_agenda_item, meeting:, title: "Test notes") }
let(:meeting_page) { Pages::Meetings::Show.new(meeting) }
before do
login_as current_user
@@ -94,10 +91,9 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
it "shows the move to next meeting option" do
meeting_page.expect_agenda_item(title: "Test notes")
meeting_page.expect_agenda_action_menu(series_agenda_item)
accept_confirm do
meeting_page.select_action(series_agenda_item, "Move to next meeting")
meeting_page.select_action(agenda_item, "Move to next meeting")
end
meeting_page.expect_no_agenda_item(title: "Test notes")
@@ -115,10 +111,9 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
it "shows the move to next meeting option" do
meeting_page.expect_agenda_item(title: "Test notes")
meeting_page.expect_agenda_action_menu(series_agenda_item)
accept_confirm do
meeting_page.select_action(series_agenda_item, "Move to next meeting")
meeting_page.select_action(agenda_item, "Move to next meeting")
end
expect(page).to have_text "Unable to move to the next meeting since it has been cancelled."
@@ -136,8 +131,7 @@ RSpec.describe "Recurring meetings move to next meeting", :js do
end
end
context "when viewing a structured meeting" do
let(:meeting) { structured_meeting }
context "when viewing a one-time meeting" do
let(:current_user) { user_with_manage_permissions }
it "does not show the move to next meeting option" do
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,7 +30,7 @@
require "spec_helper"
require "features/page_objects/notification"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
RSpec.describe "Upload attachment to meetings", :js do
let(:user) do
@@ -43,8 +44,8 @@ RSpec.describe "Upload attachment to meetings", :js do
let(:wiki_page_content) { project.wiki.pages.first.text }
let(:attachment_list) { Components::AttachmentsList.new("#content") }
let(:meeting) { create(:structured_meeting, project:) }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:meeting) { create(:meeting, project:) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
before do
login_as(user)
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,9 +30,8 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/structured_meeting/history"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/meetings/history"
RSpec.describe "history",
:js do
@@ -72,7 +72,7 @@ RSpec.describe "history",
end
shared_let(:meeting) do
User.execute_as(user) do
create(:structured_meeting,
create(:meeting,
project:,
start_time: DateTime.parse("2024-03-28T13:30:00Z"),
title: "Some title",
@@ -82,8 +82,8 @@ RSpec.describe "history",
end
end
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:history_page) { Pages::StructuredMeeting::History.new(meeting) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
let(:history_page) { Pages::Meeting::History.new(meeting) }
let(:editor) { Components::WysiwygEditor.new "#content", "opce-ckeditor-augmented-textarea" }
it "allows browsing the history", with_settings: { journal_aggregation_time_minutes: 0 } do
@@ -30,7 +30,7 @@
require "spec_helper"
require_relative "../../../support/pages/structured_meeting/show"
require_relative "../../../support/pages/meetings/show"
RSpec.describe "Meeting Outcomes CRUD", :js do
shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) }
@@ -46,7 +46,7 @@ RSpec.describe "Meeting Outcomes CRUD", :js do
member_with_permissions: { project => %i[view_meetings manage_agendas close_meeting_agendas] }
end
shared_let(:meeting) do
create :structured_meeting,
create :meeting,
project:,
start_time: "2024-12-31T13:30:00Z",
duration: 1.5,
@@ -59,7 +59,7 @@ RSpec.describe "Meeting Outcomes CRUD", :js do
let(:current_user) { user }
let(:state) { :in_progress }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
let(:field) do
TextEditorField.new(page, "Outcome", selector: test_selector("meeting-outcome-input"))
end
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,9 +30,9 @@
require "spec_helper"
require_relative "../../support/pages/structured_meeting//mobile/show"
require_relative "../../support/pages/meetings/mobile/show"
RSpec.describe "Structured meetings CRUD",
RSpec.describe "Meetings CRUD",
:js do
include Components::Autocompleter::NgSelectAutocompleteHelpers
@@ -57,14 +58,14 @@ RSpec.describe "Structured meetings CRUD",
end
shared_let(:meeting) do
create(:structured_meeting,
create(:meeting,
:author_participates,
project:,
author: user)
end
let(:current_user) { user }
let(:show_page) { Pages::StructuredMeeting::Mobile::Show.new(StructuredMeeting.last) }
let(:show_page) { Pages::Meetings::Mobile::Show.new(Meeting.last) }
include_context "with mobile screen size"
@@ -73,7 +74,7 @@ RSpec.describe "Structured meetings CRUD",
show_page.visit!
end
it "can edit participants of a structured meeting" do
it "can edit participants of a meeting" do
expect(page).to have_current_path(show_page.path)
show_page.expect_participants(count: 1)
@@ -30,11 +30,10 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/meetings/index"
RSpec.describe "Structured meetings CRUD",
RSpec.describe "Meetings CRUD",
:js do
include Components::Autocompleter::NgSelectAutocompleteHelpers
@@ -63,9 +62,8 @@ RSpec.describe "Structured meetings CRUD",
end
let(:current_user) { user }
let(:new_page) { Pages::Meetings::New.new(project) }
let(:meeting) { StructuredMeeting.last }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:meeting) { Meeting.last }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
let(:meetings_page) { Pages::Meetings::Index.new(project:) }
before do |test|
@@ -88,7 +86,7 @@ RSpec.describe "Structured meetings CRUD",
meetings_page.click_create
end
it "can create a structured meeting and add agenda items" do
it "can create a meeting and add agenda items" do
expect_and_dismiss_flash(type: :success, message: "Successful creation")
# Does not send invitation mails by default
@@ -312,7 +310,7 @@ RSpec.describe "Structured meetings CRUD",
fill_in "Title", with: "Some title"
click_on "Create meeting"
new_meeting = StructuredMeeting.last
new_meeting = Meeting.last
expect(page).to have_current_path "/projects/#{project.identifier}/meetings/#{new_meeting.id}"
# check for copied agenda items
@@ -330,13 +328,13 @@ RSpec.describe "Structured meetings CRUD",
end
context "with a work package reference to another" do
let!(:meeting) { create(:structured_meeting, project:, author: current_user) }
let!(:meeting) { create(:meeting, project:, author: current_user) }
let!(:other_project) { create(:project) }
let!(:other_wp) { create(:work_package, project: other_project, author: current_user, subject: "Private task") }
let!(:role) { create(:project_role, permissions: %w[view_work_packages]) }
let!(:membership) { create(:member, principal: user, project: other_project, roles: [role]) }
let!(:agenda_item) { create(:wp_meeting_agenda_item, meeting:, author: current_user, work_package: other_wp) }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
it "shows correctly for author, but returns an unresolved reference for the second user" do
show_page.visit!
@@ -352,8 +350,8 @@ RSpec.describe "Structured meetings CRUD",
end
context "with sections" do
let!(:meeting) { create(:structured_meeting, project:, author: current_user) }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let!(:meeting) { create(:meeting, project:, author: current_user) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
context "when starting with empty sections" do
it "can add, edit and delete sections" do
@@ -30,11 +30,10 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/meetings/index"
RSpec.describe "Structured meetings deletion",
RSpec.describe "Meetings deletion",
:js do
include Components::Autocompleter::NgSelectAutocompleteHelpers
@@ -30,11 +30,10 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
require_relative "../../support/pages/meetings/index"
RSpec.describe "Structured meetings global CRUD", :js do
RSpec.describe "Meetings global CRUD", :js do
include Components::Autocompleter::NgSelectAutocompleteHelpers
shared_let(:project) { create(:project, enabled_module_names: %w[meetings work_package_tracking]) }
@@ -62,9 +61,8 @@ RSpec.describe "Structured meetings global CRUD", :js do
end
let(:current_user) { user }
let(:new_page) { Pages::Meetings::New.new(project) }
let(:meeting) { StructuredMeeting.last }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:meeting) { Meeting.last }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
let(:meetings_page) { Pages::Meetings::Index.new(project: nil) }
before do
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,9 +30,9 @@
require "spec_helper"
require_relative "../../support/pages/structured_meeting//mobile/show"
require_relative "../../support/pages/meetings/mobile/show"
RSpec.describe "Structured meetings participants",
RSpec.describe "Meetings participants",
:js do
include Components::Autocompleter::NgSelectAutocompleteHelpers
@@ -57,21 +58,21 @@ RSpec.describe "Structured meetings participants",
end
shared_let(:meeting) do
create(:structured_meeting,
create(:meeting,
:author_participates,
project:,
author: user)
end
let(:current_user) { user }
let(:show_page) { Pages::StructuredMeeting::Show.new(StructuredMeeting.last) }
let(:show_page) { Pages::Meetings::Show.new(Meeting.last) }
before do
login_as current_user
show_page.visit!
end
it "can edit participants of a structured meeting" do
it "can edit participants of a meeting" do
expect(page).to have_current_path(show_page.path)
show_page.open_participant_form
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,10 +30,9 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
RSpec.describe "Structured meetings CRUD",
RSpec.describe "Meetings CRUD",
:js,
:selenium do
include Components::Autocompleter::NgSelectAutocompleteHelpers
@@ -41,9 +41,8 @@ RSpec.describe "Structured meetings CRUD",
shared_let(:user) { create(:admin) }
current_user { user }
let(:new_page) { Pages::Meetings::New.new(project) }
let(:meeting) { create(:structured_meeting, project:, author: current_user) }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) }
let(:meeting) { create(:meeting, project:, author: current_user) }
let(:show_page) { Pages::Meetings::Show.new(meeting) }
describe "meeting update flash" do
before do
@@ -134,7 +133,7 @@ RSpec.describe "Structured meetings CRUD",
## Edit meeting details
within_window(first_window) do
find_test_selector("edit-meeting-details-button").click
fill_in "structured_meeting_duration", with: "2.5"
fill_in "meeting_duration", with: "2.5"
click_link_or_button "Save"
# Expect updated duration
@@ -1,4 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -29,10 +30,9 @@
require "spec_helper"
require_relative "../../support/pages/meetings/new"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
RSpec.describe "Structured meetings links caught by turbo",
RSpec.describe "Meetings links caught by turbo",
:js do
include Rails.application.routes.url_helpers
@@ -47,8 +47,8 @@ RSpec.describe "Structured meetings links caught by turbo",
u.save!
end
end
shared_let(:meeting1) { create(:structured_meeting, title: "First meeting", project:) }
shared_let(:meeting2) { create(:structured_meeting, title: "Other meeting", project:) }
shared_let(:meeting1) { create(:meeting, title: "First meeting", project:) }
shared_let(:meeting2) { create(:meeting, title: "Other meeting", project:) }
let(:notes) do
<<~NOTES
@@ -56,7 +56,7 @@ RSpec.describe "Structured meetings links caught by turbo",
NOTES
end
let!(:agenda_item) { create(:meeting_agenda_item, meeting: meeting1, notes:) }
let(:show_page) { Pages::StructuredMeeting::Show.new(meeting1) }
let(:show_page) { Pages::Meetings::Show.new(meeting1) }
before do
login_as user
@@ -30,7 +30,7 @@
require "spec_helper"
require_relative "../../support/pages/work_package_meetings_tab"
require_relative "../../support/pages/structured_meeting/show"
require_relative "../../support/pages/meetings/show"
RSpec.describe "Open the Meetings tab",
:js do
@@ -98,8 +98,8 @@ RSpec.describe "Open the Meetings tab",
context "when the user has the permission to see the tab, but the work package is linked in two projects" do
let(:other_project) { create(:project, enabled_module_names: %w[meetings]) }
let!(:visible_meeting) { create(:structured_meeting, project:) }
let!(:invisible_meeting) { create(:structured_meeting, project: other_project) }
let!(:visible_meeting) { create(:meeting, project:) }
let!(:invisible_meeting) { create(:meeting, project: other_project) }
let!(:meeting_agenda_item_of_visible_meeting) do
create(:meeting_agenda_item, meeting: visible_meeting, work_package:, notes: "Public note!")
@@ -131,7 +131,7 @@ RSpec.describe "Open the Meetings tab",
end
context "with another past meeting" do
let!(:past_meeting) { create(:structured_meeting, project:, start_time: 1.week.ago) }
let!(:past_meeting) { create(:meeting, project:, start_time: 1.week.ago) }
let!(:past_agenda_item) do
create(:meeting_agenda_item, meeting: past_meeting, work_package:, notes: "Public note!")
@@ -225,8 +225,8 @@ RSpec.describe "Open the Meetings tab",
end
context "when the work_package is already referenced in upcoming meetings" do
let!(:first_meeting) { create(:structured_meeting, project:) }
let!(:second_meeting) { create(:structured_meeting, project:) }
let!(:first_meeting) { create(:meeting, project:) }
let!(:second_meeting) { create(:meeting, project:) }
let!(:first_meeting_agenda_item_of_first_meeting) do
create(:meeting_agenda_item, meeting: first_meeting, work_package:, notes: "A very important note in first meeting!")
@@ -267,7 +267,7 @@ RSpec.describe "Open the Meetings tab",
end
context "when the work_package is referenced and has an outcome" do
let!(:meeting) { create(:structured_meeting, project:) }
let!(:meeting) { create(:meeting, project:) }
let!(:meeting_agenda_item) do
create(:meeting_agenda_item, meeting:, work_package:, notes: "A very important note in first meeting!")
@@ -291,8 +291,8 @@ RSpec.describe "Open the Meetings tab",
end
context "when the work_package was already referenced in past meetings" do
let!(:first_past_meeting) { create(:structured_meeting, project:, start_time: Date.yesterday - 11.hours) }
let!(:second_past_meeting) { create(:structured_meeting, project:, start_time: Date.yesterday - 10.hours) }
let!(:first_past_meeting) { create(:meeting, project:, start_time: Date.yesterday - 11.hours) }
let!(:second_past_meeting) { create(:meeting, project:, start_time: Date.yesterday - 10.hours) }
let!(:first_meeting_agenda_item_of_first_past_meeting) do
create(:meeting_agenda_item, meeting: first_past_meeting, work_package:, notes: "A very important note in first meeting!")
@@ -347,15 +347,15 @@ RSpec.describe "Open the Meetings tab",
end
context "when open, upcoming meetings are visible for the user" do
shared_let(:past_meeting) { create(:structured_meeting, project:, start_time: Date.yesterday - 10.hours) }
shared_let(:first_upcoming_meeting) { create(:structured_meeting, project:) }
shared_let(:second_upcoming_meeting) { create(:structured_meeting, project:) }
shared_let(:closed_upcoming_meeting) { create(:structured_meeting, project:, state: :closed) }
shared_let(:past_meeting) { create(:meeting, project:, start_time: Date.yesterday - 10.hours) }
shared_let(:first_upcoming_meeting) { create(:meeting, project:) }
shared_let(:second_upcoming_meeting) { create(:meeting, project:) }
shared_let(:closed_upcoming_meeting) { create(:meeting, project:, state: :closed) }
shared_let(:ongoing_meeting) do
create(:structured_meeting, title: "Ongoing", project:, start_time: 1.hour.ago, duration: 4.0)
create(:meeting, title: "Ongoing", project:, start_time: 1.hour.ago, duration: 4.0)
end
let(:meeting_page) { Pages::StructuredMeeting::Show.new(first_upcoming_meeting) }
let(:meeting_page) { Pages::Meetings::Show.new(first_upcoming_meeting) }
it "enables the user to add the work package to multiple open, upcoming meetings" do
work_package_page.visit!
@@ -45,9 +45,6 @@ RSpec.describe MeetingMailer do
author:,
project:)
end
let(:meeting_agenda) do
create(:meeting_agenda, meeting:)
end
let(:tokyo_offset) { "UTC#{ActiveSupport::TimeZone['Asia/Tokyo'].now.formatted_offset}" }
let(:berlin_offset) { "UTC#{ActiveSupport::TimeZone['Europe/Berlin'].now.formatted_offset}" }

Some files were not shown because too many files have changed in this diff Show More