From acbaf7db689a66871388f09bfc3d82dbfd4d0bff Mon Sep 17 00:00:00 2001 From: Jonas Jabari Date: Tue, 1 Aug 2023 10:21:39 +0200 Subject: [PATCH] introduced standalone work_package issues on wp tab, proceeding with optional meeting_agenda_item relation of issues --- app/models/work_package.rb | 3 +- .../base/op_primer/component_helpers.rb | 12 +- .../work_package_tab/issues/form_component.rb | 87 +++++++ .../work_package_tab/issues/item_component.rb | 238 ++++++++++++++++++ .../work_package_tab/issues/list_component.rb | 145 +++++++++++ .../issues/resolve_component.rb | 80 ++++++ .../app/controllers/issues_controller.rb | 130 ++++++++++ .../new/details.rb => issue/description.rb} | 14 +- modules/meeting/app/forms/issue/resolution.rb | 37 +++ modules/meeting/app/forms/issue/submit.rb | 35 +++ modules/meeting/app/forms/issue/type.rb | 44 ++++ modules/meeting/app/models/issue.rb | 62 +++++ .../meeting/app/views/issues/closed.html.erb | 45 ++++ .../meeting/app/views/issues/edit.html.erb | 42 ++++ .../app/views/issues/edit_resolution.html.erb | 42 ++++ modules/meeting/app/views/issues/new.html.erb | 42 ++++ .../meeting/app/views/issues/open.html.erb | 45 ++++ modules/meeting/config/routes.rb | 14 ++ .../migrate/20230728115607_create_issues.rb | 15 ++ .../module/issues-tab/issues-tab.component.ts | 65 +++++ .../issues-tab/issues-tab.template.html | 1 + modules/meeting/frontend/module/main.ts | 9 + 22 files changed, 1191 insertions(+), 16 deletions(-) create mode 100644 modules/meeting/app/components/work_package_tab/issues/form_component.rb create mode 100644 modules/meeting/app/components/work_package_tab/issues/item_component.rb create mode 100644 modules/meeting/app/components/work_package_tab/issues/list_component.rb create mode 100644 modules/meeting/app/components/work_package_tab/issues/resolve_component.rb create mode 100644 modules/meeting/app/controllers/issues_controller.rb rename modules/meeting/app/forms/{meeting_agenda_item/new/details.rb => issue/description.rb} (86%) create mode 100644 modules/meeting/app/forms/issue/resolution.rb create mode 100644 modules/meeting/app/forms/issue/submit.rb create mode 100644 modules/meeting/app/forms/issue/type.rb create mode 100644 modules/meeting/app/models/issue.rb create mode 100644 modules/meeting/app/views/issues/closed.html.erb create mode 100644 modules/meeting/app/views/issues/edit.html.erb create mode 100644 modules/meeting/app/views/issues/edit_resolution.html.erb create mode 100644 modules/meeting/app/views/issues/new.html.erb create mode 100644 modules/meeting/app/views/issues/open.html.erb create mode 100644 modules/meeting/db/migrate/20230728115607_create_issues.rb create mode 100644 modules/meeting/frontend/module/issues-tab/issues-tab.component.ts create mode 100644 modules/meeting/frontend/module/issues-tab/issues-tab.template.html diff --git a/app/models/work_package.rb b/app/models/work_package.rb index 092e11ad0cf4..85a8f245e67b 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -61,7 +61,8 @@ class WorkPackage < ApplicationRecord has_many :storages, through: :project - has_many :meeting_agenda_items + has_many :meeting_agenda_items # should become through issue relation + has_many :issues, dependent: :destroy has_and_belongs_to_many :changesets, -> { # rubocop:disable Rails/HasAndBelongsToMany order("#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC") diff --git a/modules/meeting/app/components/base/op_primer/component_helpers.rb b/modules/meeting/app/components/base/op_primer/component_helpers.rb index cd4e9ef73a82..91b533d49401 100644 --- a/modules/meeting/app/components/base/op_primer/component_helpers.rb +++ b/modules/meeting/app/components/base/op_primer/component_helpers.rb @@ -28,16 +28,12 @@ module Base module OpPrimer::ComponentHelpers - def flex_layout(**system_arguments, &block) - render(OpPrimer::FlexLayoutComponent.new(**system_arguments)) do |component| - yield component - end + def flex_layout(**, &) + render(OpPrimer::FlexLayoutComponent.new(**), &) end - def box_collection(**system_arguments, &block) - render(OpPrimer::BoxCollectionComponent.new(**system_arguments)) do |component| - yield component - end + def box_collection(**, &) + render(OpPrimer::BoxCollectionComponent.new(**), &) end end end diff --git a/modules/meeting/app/components/work_package_tab/issues/form_component.rb b/modules/meeting/app/components/work_package_tab/issues/form_component.rb new file mode 100644 index 000000000000..41739a90b270 --- /dev/null +++ b/modules/meeting/app/components/work_package_tab/issues/form_component.rb @@ -0,0 +1,87 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTab + class Issues::FormComponent < Base::Component + include OpTurbo::Streamable + + def initialize(issue:) + super + + @issue = issue + end + + def call + component_wrapper do + primer_form_with( + model: @issue, + url: submit_path + ) do |form| + flex_layout do |flex| + flex.with_row do + render(Issue::Type.new(form)) + end + flex.with_row(mt: 2) do + render(Issue::Description.new(form)) + end + flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| + flex.with_column(mr: 2) do + back_link_partial + end + flex.with_column do + render(Issue::Submit.new(form)) + end + end + end + end + end + end + + private + + def submit_path + if @issue.persisted? + work_package_issue_path(@issue.work_package, @issue) + else + work_package_issues_path(@issue.work_package) + end + end + + def back_link_partial + link_to(open_work_package_issues_path(@issue.work_package)) do + render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |_component| + "Cancel" + end + end + end + end +end diff --git a/modules/meeting/app/components/work_package_tab/issues/item_component.rb b/modules/meeting/app/components/work_package_tab/issues/item_component.rb new file mode 100644 index 000000000000..89eb48432fc3 --- /dev/null +++ b/modules/meeting/app/components/work_package_tab/issues/item_component.rb @@ -0,0 +1,238 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTab + class Issues::ItemComponent < Base::Component + def initialize(issue:) + super + + @issue = issue + end + + def call + flex_layout(justify_content: :space_between, align_items: :flex_start) do |flex| + flex.with_column do + content_partial + end + flex.with_column do + actions_partial + end + end + end + + private + + def content_partial + flex_layout do |flex| + flex.with_column(mr: 2) do + issue_symbol_partial + end + flex.with_column do + issue_description_partial + end + end + end + + def issue_symbol_partial + if @issue.open? + render(Primer::Beta::Octicon.new(icon: "issue-opened", 'aria-label': "open", color: :muted)) + else + render(Primer::Beta::Octicon.new(icon: "issue-closed", 'aria-label': "closed", color: :muted)) + end + end + + def issue_description_partial + flex_layout do |flex| + flex.with_row(flex_layout: true) do |flex| + flex.with_column(mr: 1) do + if @issue.open? + open_issue_type_label_partial + else + closed_issue_type_label_partial + end + end + flex.with_column do + issue_meta_info_partial + end + end + flex.with_row(mt: 2, pl: 0) do + description_partial + end + end + end + + def issue_meta_info_partial + flex_layout do |flex| + flex.with_column(mr: 1) do + render(Primer::Beta::Text.new(font_size: :small, color: :muted)) do + if @issue.open? + "by #{@issue.author.name}" + else + "by #{@issue.resolved_by&.name}" + end + end + end + flex.with_column do + render(Primer::Beta::RelativeTime.new(datetime: @issue.updated_at, font_size: :small, color: :muted)) + end + end + end + + def open_issue_type_label_partial + case @issue.issue_type + when "input_need" + render(Primer::Beta::Label.new(scheme: :accent)) { "Input need" } + when "clarification_need" + render(Primer::Beta::Label.new(scheme: :attention)) { "Clarification need" } + when "decision_need" + render(Primer::Beta::Label.new(scheme: :severe)) { "Decision need" } + end + end + + def closed_issue_type_label_partial + case @issue.issue_type + when "input_need" + render(Primer::Beta::Label.new(scheme: :accent)) { "Input" } + when "clarification_need" + render(Primer::Beta::Label.new(scheme: :attention)) { "Clarification" } + when "decision_need" + render(Primer::Beta::Label.new(scheme: :severe)) { "Decision" } + end + end + + def actions_partial + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button(icon: "kebab-horizontal", 'aria-label': "Issue actions") + edit_action_item(menu) + delete_action_item(menu) + resolve_action_item(menu) + reopen_action_item(menu) + add_to_meeting_action_item(menu) + end + end + + def edit_action_item(menu) + return unless @issue.open? + + menu.with_item(label: "Edit", href: edit_work_package_issue_path(work_package_id: @issue.work_package.id, id: @issue.id)) + end + + def delete_action_item(menu) + return unless @issue.open? + + menu.with_item(label: "Delete", + href: work_package_issue_path(work_package_id: @issue.work_package.id, id: @issue.id), + form_arguments: { + method: :delete, data: { confirm: "Are you sure?", 'turbo-stream': true } + }) + end + + def resolve_action_item(menu) + return unless @issue.open? + + menu.with_item(label: "Resolve", + href: edit_resolution_work_package_issue_path( + work_package_id: @issue.work_package.id, id: @issue.id + )) + end + + def reopen_action_item(menu) + return unless @issue.closed? + + menu.with_item(label: "Reopen", + href: reopen_work_package_issue_path( + work_package_id: @issue.work_package.id, id: @issue.id + ), + form_arguments: { + method: :patch, data: { confirm: "Are you sure?", 'turbo-stream': true } + }) + end + + def add_to_meeting_action_item(menu) + return unless @issue.open? + + menu.with_item(label: "Add to meeting", href: "/") + end + + def count_active_work_package_references_in_meeting + @meeting.agenda_items.where(work_package_id: @active_work_package.id).count if @active_work_package.present? + end + + def description_partial + if @issue.open? + open_description_partial + else + flex_layout do |flex| + flex.with_row do + resolution_partial + end + flex.with_row(ml: 2, mt: 1, border: :left) do + historic_description_partial + end + end + end + end + + def open_description_partial(color = :default) + render(Primer::Box.new(font_size: :small, color:)) do + simple_format(@issue.description, {}, wrapper_tag: "span") + end + end + + def resolution_partial + render(Primer::Box.new(font_size: :small)) do + simple_format(@issue.resolution, {}, wrapper_tag: "span") + end + end + + def historic_description_partial + flex_layout(pl: 2) do |flex| + flex.with_row do + original_author_partial + end + flex.with_row do + open_description_partial(:muted) + end + end + end + + def original_author_partial + flex_layout do |flex| + flex.with_column(mr: 1) do + render(Primer::Beta::Text.new(font_size: :small, font_style: :italic, color: :muted)) do + "#{@issue.author.name} created" + end + end + flex.with_column do + render(Primer::Beta::RelativeTime.new(datetime: @issue.created_at, font_size: :small, font_style: :italic, + color: :muted)) + end + end + end + end +end diff --git a/modules/meeting/app/components/work_package_tab/issues/list_component.rb b/modules/meeting/app/components/work_package_tab/issues/list_component.rb new file mode 100644 index 000000000000..fac0b63bf7d2 --- /dev/null +++ b/modules/meeting/app/components/work_package_tab/issues/list_component.rb @@ -0,0 +1,145 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTab + class Issues::ListComponent < Base::Component + include OpTurbo::Streamable + + def initialize(work_package:, resolved: false) + super + + @work_package = work_package + @resolved = resolved + @open_issues_count = @work_package.issues.open.count + @closed_issues_count = @work_package.issues.closed.count + @issues = if resolved + @work_package.issues.closed + else + @work_package.issues.open + end + end + + def call + component_wrapper do + render(Primer::Beta::BorderBox.new) do |component| + component.with_header do |header| + header.with_title(tag: :h2) do + header_partial + end + end + content_partial(component) + end + end + end + + private + + def show_open_issues? + !@resolved + end + + def show_closed_issues? + @resolved + end + + def header_partial + flex_layout(align_items: :center) do |flex| + flex.with_column(mr: 3) do + open_link_partial + end + flex.with_column do + closed_link_partial + end + end + end + + def open_link_partial + render(Primer::Beta::Link.new(href: open_work_package_issues_path(@work_package), scheme: :primary, + muted: show_closed_issues?, underline: false, font_weight: open_link_font_weight)) do + flex_layout do |flex| + flex.with_column(mr: 1) do + render(Primer::Beta::Octicon.new(icon: "issue-opened", 'aria-label': "open issues")) + end + flex.with_column do + "#{@open_issues_count} Open" + end + end + end + end + + def open_link_font_weight + show_open_issues? ? :bold : :normal + end + + def closed_link_partial + render(Primer::Beta::Link.new(href: closed_work_package_issues_path(@work_package), scheme: :primary, + muted: show_open_issues?, underline: false, font_weight: closed_link_font_weight)) do + flex_layout do |flex| + flex.with_column(mr: 1) do + render(Primer::Beta::Octicon.new(icon: "issue-closed", 'aria-label': "closed issues")) + end + flex.with_column do + "#{@closed_issues_count} Closed" + end + end + end + end + + def closed_link_font_weight + show_closed_issues? ? :bold : :normal + end + + def content_partial(component) + if @issues.empty? + component.with_body do + empty_state_partial + end + else + @issues.each do |issue| + component.with_row do + render(WorkPackageTab::Issues::ItemComponent.new(issue:)) + end + end + end + end + + def empty_state_partial + render Primer::Beta::Blankslate.new do |component| + if show_open_issues? + component.with_visual_icon(icon: "issue-opened") + component.with_heading(tag: :h2).with_content("No open issues found for this work package") + end + if show_closed_issues? + component.with_visual_icon(icon: "issue-closed") + component.with_heading(tag: :h2).with_content("No closed issues found for this work package") + end + component.with_description { "Issues help tracking questions, clarifications and descisions." } + end + end + end +end diff --git a/modules/meeting/app/components/work_package_tab/issues/resolve_component.rb b/modules/meeting/app/components/work_package_tab/issues/resolve_component.rb new file mode 100644 index 000000000000..d3798bea6b90 --- /dev/null +++ b/modules/meeting/app/components/work_package_tab/issues/resolve_component.rb @@ -0,0 +1,80 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module WorkPackageTab + class Issues::ResolveComponent < Base::Component + include OpTurbo::Streamable + + def initialize(issue:) + super + + @issue = issue + end + + def call + component_wrapper do + primer_form_with( + model: @issue, + url: submit_path + ) do |form| + flex_layout do |flex| + flex.with_row(mt: 2) do + render(Issue::Resolution.new(form)) + end + flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| + flex.with_column(mr: 2) do + back_link_partial + end + flex.with_column do + render(Issue::Submit.new(form)) + end + end + end + end + end + end + + private + + def submit_path + resolve_work_package_issue_path(@issue.work_package, @issue) + end + + def back_link_partial + link_to(open_work_package_issues_path(@issue.work_package)) do + render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |_component| + "Cancel" + end + end + end + end +end diff --git a/modules/meeting/app/controllers/issues_controller.rb b/modules/meeting/app/controllers/issues_controller.rb new file mode 100644 index 000000000000..3da27f577942 --- /dev/null +++ b/modules/meeting/app/controllers/issues_controller.rb @@ -0,0 +1,130 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class IssuesController < ApplicationController + include OpTurbo::ComponentStream + + before_action :set_issue, only: %i[edit update destroy edit_resolution resolve reopen] + before_action :set_work_package + + def open + render layout: false + end + + def closed + render layout: false + end + + def new + @issue = Issue.new(work_package: @work_package) + + render layout: false + end + + def create + @issue = Issue.new(issue_params.merge(work_package: @work_package, author: User.current)) + + if @issue.save + redirect_to open_work_package_issues_path(@work_package) + else + # simply rendering the new view again as a turbo-frame messes up the src attribute of the frame + # using turbo-stream instead as a quick fix + update_via_turbo_stream( + component: WorkPackageTab::Issues::FormComponent.new(issue: @issue) + ) + + respond_with_turbo_streams + end + end + + def edit + render layout: false + end + + def update + if @issue.update(issue_params) + redirect_to open_work_package_issues_path(@work_package) + else + # simply rendering the new view again as a turbo-frame messes up the src attribute of the frame + # using turbo-stream instead as a quick fix + update_via_turbo_stream( + component: WorkPackageTab::Issues::FormComponent.new(issue: @issue) + ) + + respond_with_turbo_streams + end + end + + def destroy + if @issue.destroy + update_via_turbo_stream( + component: WorkPackageTab::Issues::ListComponent.new(work_package: @work_package) + ) + + respond_with_turbo_streams + end + end + + def edit_resolution + render layout: false + end + + def resolve + if @issue.resolve(User.current, issue_params[:resolution]) + redirect_to open_work_package_issues_path(@work_package) + else + # simply rendering the new view again as a turbo-frame messes up the src attribute of the frame + # using turbo-stream instead as a quick fix + update_via_turbo_stream( + component: WorkPackageTab::Issues::ResolveComponent.new(issue: @issue) + ) + + respond_with_turbo_streams + end + end + + def reopen + if @issue.reopen # keep the former resolution in place + redirect_to open_work_package_issues_path(@work_package) + end + end + + private + + def set_work_package + @work_package = WorkPackage.find(params[:work_package_id]) + end + + def set_issue + @issue = Issue.find(params[:id]) + end + + def issue_params + params.require(:issue).permit(:description, :issue_type, :resolution) + end +end diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/details.rb b/modules/meeting/app/forms/issue/description.rb similarity index 86% rename from modules/meeting/app/forms/meeting_agenda_item/new/details.rb rename to modules/meeting/app/forms/issue/description.rb index d5a306f4a1cc..9b8ccf15191f 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/details.rb +++ b/modules/meeting/app/forms/issue/description.rb @@ -26,12 +26,12 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Details < ApplicationForm - form do |agenda_item_form| - agenda_item_form.text_area( - name: :details, - label: "Details", - autofocus: true, +class Issue::Description < ApplicationForm + form do |issue_form| + issue_form.text_area( + name: :description, + label: "Description", + autofocus: true ) end -end \ No newline at end of file +end diff --git a/modules/meeting/app/forms/issue/resolution.rb b/modules/meeting/app/forms/issue/resolution.rb new file mode 100644 index 000000000000..adeb7dc474b3 --- /dev/null +++ b/modules/meeting/app/forms/issue/resolution.rb @@ -0,0 +1,37 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Issue::Resolution < ApplicationForm + form do |issue_form| + issue_form.text_area( + name: :resolution, + label: "Resolution", + autofocus: true + ) + end +end diff --git a/modules/meeting/app/forms/issue/submit.rb b/modules/meeting/app/forms/issue/submit.rb new file mode 100644 index 000000000000..3c40b92c2971 --- /dev/null +++ b/modules/meeting/app/forms/issue/submit.rb @@ -0,0 +1,35 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Issue::Submit < ApplicationForm + form do |issue_form| + issue_form.group(layout: :horizontal) do |button_group| + button_group.submit(name: :submit, label: "Submit", scheme: :primary) + end + end +end diff --git a/modules/meeting/app/forms/issue/type.rb b/modules/meeting/app/forms/issue/type.rb new file mode 100644 index 000000000000..776cf10db000 --- /dev/null +++ b/modules/meeting/app/forms/issue/type.rb @@ -0,0 +1,44 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Issue::Type < ApplicationForm + form do |issue_form| + issue_form.select_list( + name: :issue_type, + label: "Type", + include_blank: false + ) do |type_select_list| + Issue.issue_types.to_a.each do |k, _v| + type_select_list.option( + label: k.humanize.capitalize, + value: k + ) + end + end + end +end diff --git a/modules/meeting/app/models/issue.rb b/modules/meeting/app/models/issue.rb new file mode 100644 index 000000000000..1d34839b47ec --- /dev/null +++ b/modules/meeting/app/models/issue.rb @@ -0,0 +1,62 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Issue < ApplicationRecord + self.table_name = 'issues' + + belongs_to :work_package + belongs_to :author, class_name: 'User' + belongs_to :resolved_by, class_name: 'User', optional: true + + # has_many :meeting_agenda_items, dependent: :destroy, class_name: 'MeetingAgendaItem' + + enum issue_type: %i[input_need clarification_need decision_need] + + default_scope { order(updated_at: :desc) } + + scope :open, -> { where(resolved_at: nil) } + scope :closed, -> { where.not(resolved_at: nil) } + + validates :description, presence: true + + def open? + resolved_at.nil? + end + + def closed? + !open? + end + + def resolve(user, resolution) + update(resolved_at: Time.zone.now, resolved_by: user, resolution:) + end + + def reopen + update(resolved_at: nil, resolved_by: nil) # leave resolution in place + end +end diff --git a/modules/meeting/app/views/issues/closed.html.erb b/modules/meeting/app/views/issues/closed.html.erb new file mode 100644 index 000000000000..34c92f97b9ea --- /dev/null +++ b/modules/meeting/app/views/issues/closed.html.erb @@ -0,0 +1,45 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2023 the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + + + <%= render(::Base::OpPrimer::FlexLayoutComponent.new(justify_content: :flex_end, mb: 3)) do |flex| %> + <%= flex.with_column do %> + <%= link_to(new_work_package_issue_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :primary, + block: false, + mb: 3 + )) do |component| %> + Create issue + <% end %> + <% end %> + <% end %> + <% end %> + <%= render(WorkPackageTab::Issues::ListComponent.new(work_package: @work_package, resolved: true)) %> + diff --git a/modules/meeting/app/views/issues/edit.html.erb b/modules/meeting/app/views/issues/edit.html.erb new file mode 100644 index 000000000000..1db6b5cb46bb --- /dev/null +++ b/modules/meeting/app/views/issues/edit.html.erb @@ -0,0 +1,42 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2023 the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + + + <%= link_to(open_work_package_issues_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |component| %> + <% component.with_leading_visual_icon(icon: "arrow-left") %> + Back to issues + <% end %> + <% end %> + <%= render(WorkPackageTab::Issues::FormComponent.new(issue: @issue)) %> + diff --git a/modules/meeting/app/views/issues/edit_resolution.html.erb b/modules/meeting/app/views/issues/edit_resolution.html.erb new file mode 100644 index 000000000000..8482d47c7acd --- /dev/null +++ b/modules/meeting/app/views/issues/edit_resolution.html.erb @@ -0,0 +1,42 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2023 the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + + + <%= link_to(open_work_package_issues_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |component| %> + <% component.with_leading_visual_icon(icon: "arrow-left") %> + Back to issues + <% end %> + <% end %> + <%= render(WorkPackageTab::Issues::ResolveComponent.new(issue: @issue)) %> + diff --git a/modules/meeting/app/views/issues/new.html.erb b/modules/meeting/app/views/issues/new.html.erb new file mode 100644 index 000000000000..1db6b5cb46bb --- /dev/null +++ b/modules/meeting/app/views/issues/new.html.erb @@ -0,0 +1,42 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2023 the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + + + <%= link_to(open_work_package_issues_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |component| %> + <% component.with_leading_visual_icon(icon: "arrow-left") %> + Back to issues + <% end %> + <% end %> + <%= render(WorkPackageTab::Issues::FormComponent.new(issue: @issue)) %> + diff --git a/modules/meeting/app/views/issues/open.html.erb b/modules/meeting/app/views/issues/open.html.erb new file mode 100644 index 000000000000..eaa729ee7c53 --- /dev/null +++ b/modules/meeting/app/views/issues/open.html.erb @@ -0,0 +1,45 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-2023 the OpenProject GmbH + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 3. + +OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +Copyright (C) 2006-2013 Jean-Philippe Lang +Copyright (C) 2010-2013 the ChiliProject Team + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +See COPYRIGHT and LICENSE files for more details. + +++#%> + + + <%= render(::Base::OpPrimer::FlexLayoutComponent.new(justify_content: :flex_end, mb: 3)) do |flex| %> + <%= flex.with_column do %> + <%= link_to(new_work_package_issue_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :primary, + block: false, + mb: 3 + )) do |component| %> + Create issue + <% end %> + <% end %> + <% end %> + <% end %> + <%= render(WorkPackageTab::Issues::ListComponent.new(work_package: @work_package, resolved: false)) %> + diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index d37c166a9dce..ed8c2cf5ea63 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -35,6 +35,20 @@ end end + resources :work_packages, only: %i[] do + resources :issues, only: %i[new create edit update destroy] do + collection do + get :open + get :closed + end + member do + get :edit_resolution + patch :resolve + patch :reopen + end + end + end + resources :meetings do member do get 'show_in_wp_tab/:work_package_id', action: :show_in_wp_tab, as: :show_in_wp_tab diff --git a/modules/meeting/db/migrate/20230728115607_create_issues.rb b/modules/meeting/db/migrate/20230728115607_create_issues.rb new file mode 100644 index 000000000000..2a6065881f4b --- /dev/null +++ b/modules/meeting/db/migrate/20230728115607_create_issues.rb @@ -0,0 +1,15 @@ +class CreateIssues < ActiveRecord::Migration[5.1] + def change + create_table :issues do |t| + t.references :work_package + t.references :author, foreign_key: { to_table: :users } + t.references :resolved_by, foreign_key: { to_table: :users }, null: true + t.integer :issue_type, default: 0, null: false + t.text :description + t.text :resolution + t.datetime :resolved_at + + t.timestamps + end + end +end diff --git a/modules/meeting/frontend/module/issues-tab/issues-tab.component.ts b/modules/meeting/frontend/module/issues-tab/issues-tab.component.ts new file mode 100644 index 000000000000..a28d0943f7aa --- /dev/null +++ b/modules/meeting/frontend/module/issues-tab/issues-tab.component.ts @@ -0,0 +1,65 @@ +//-- copyright +// OpenProject is an open source project management software. +// Copyright (C) 2012-2023 the OpenProject GmbH +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See COPYRIGHT and LICENSE files for more details. +//++ + +import { Component, Input, OnInit, AfterViewInit, ElementRef } from '@angular/core'; +import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource"; +import { TabComponent } from "core-app/features/work-packages/components/wp-tabs/components/wp-tab-wrapper/tab"; +import { I18nService } from "core-app/core/i18n/i18n.service"; +import { PathHelperService } from "core-app/core/path-helper/path-helper.service"; + +@Component({ + selector: 'issues-tab', + templateUrl: './issues-tab.template.html' +}) +export class IssuesTabComponent implements OnInit, TabComponent { + @Input() public workPackage: WorkPackageResource; + turboFrameSrc: string; + + constructor( + private elementRef: ElementRef, + readonly PathHelper: PathHelperService, + readonly I18n: I18nService + ) {} + + ngOnInit(): void { + const storedSrc = localStorage.getItem(`turboFrameSrcIssuesTabForWorkPackage${this.workPackage.id}`); + this.turboFrameSrc = storedSrc ? storedSrc : `/work_packages/${this.workPackage.id}/issues/open`; + } + + ngAfterViewInit(): void { + const turboFrame = this.elementRef.nativeElement.querySelector('#work-package-issues-tab-content'); + if (turboFrame) { + turboFrame.addEventListener('turbo:frame-load', (event: Event) => { + console.log(event) + const target = event.target as HTMLElement; + const newSrc = target.getAttribute('src'); + localStorage.setItem(`turboFrameSrcIssuesTabForWorkPackage${this.workPackage.id}`, newSrc||''); + }); + } + } +} diff --git a/modules/meeting/frontend/module/issues-tab/issues-tab.template.html b/modules/meeting/frontend/module/issues-tab/issues-tab.template.html new file mode 100644 index 000000000000..de104cdeb336 --- /dev/null +++ b/modules/meeting/frontend/module/issues-tab/issues-tab.template.html @@ -0,0 +1 @@ + diff --git a/modules/meeting/frontend/module/main.ts b/modules/meeting/frontend/module/main.ts index 710653ef5d50..8935b1c2c4a0 100644 --- a/modules/meeting/frontend/module/main.ts +++ b/modules/meeting/frontend/module/main.ts @@ -33,6 +33,7 @@ import { OpSharedModule } from 'core-app/shared/shared.module'; import { OpenprojectTabsModule } from 'core-app/shared/components/tabs/openproject-tabs.module'; import { WorkPackageTabsService } from 'core-app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service'; import { MeetingsTabComponent } from './meetings-tab/meetings-tab.component'; +import { IssuesTabComponent } from './issues-tab/issues-tab.component'; export function initializeMeetingPlugin(injector:Injector) { const wpTabService = injector.get(WorkPackageTabsService); @@ -42,6 +43,12 @@ export function initializeMeetingPlugin(injector:Injector) { id: 'meetings', displayable: (workPackage) => true, }); + wpTabService.register({ + component: IssuesTabComponent, + name: "Issues", + id: 'issues', + displayable: (workPackage) => true, + }); } @NgModule({ @@ -51,9 +58,11 @@ export function initializeMeetingPlugin(injector:Injector) { ], declarations: [ MeetingsTabComponent, + IssuesTabComponent, ], exports: [ MeetingsTabComponent, + IssuesTabComponent, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], })