From cde8111e69f9e7af4aa6eb4922530685d72521e1 Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 31 Jul 2024 14:50:20 +0200 Subject: [PATCH 01/80] [#48274] Work packages export modal with settings Step 1: Turbo- and primerize the modal https://community.openproject.org/work_packages/48274 --- .../exports/base_export_settings_component.rb | 53 +++++++++ .../csv/export_settings_component.html.erb | 19 ++++ .../exports/csv/export_settings_component.rb | 38 +++++++ .../exports/modal_dialog_component.html.erb | 58 ++++++++++ .../exports/modal_dialog_component.rb | 66 +++++++++++ .../pdf/export_settings_component.html.erb | 107 ++++++++++++++++++ .../exports/pdf/export_settings_component.rb | 95 ++++++++++++++++ .../xls/export_settings_component.html.erb | 25 ++++ .../exports/xls/export_settings_component.rb | 38 +++++++ app/controllers/work_packages_controller.rb | 46 ++++---- .../work_packages_controller_helper.rb | 7 +- app/models/work_package/pdf_export/common.rb | 6 +- .../pdf_export/work_package_list_to_pdf.rb | 2 +- config/initializers/permissions.rb | 2 +- config/routes.rb | 4 +- .../draggable-autocomplete.component.ts | 13 ++- .../export-modal/wp-table-export.modal.ts | 2 +- .../op-settings-dropdown-menu.directive.ts | 62 +++++++++- .../work-packages/export/dialog.controller.ts | 27 +++++ .../work-packages/export/form.controller.ts | 52 +++++++++ .../export/pdf/settings.controller.ts | 27 +++++ .../work_package_collection_representer.rb | 9 +- ...ork_package_collection_representer_spec.rb | 12 +- .../work_package_list_to_pdf_gantt_spec.rb | 2 +- .../work_package_list_to_pdf_spec.rb | 10 +- 25 files changed, 728 insertions(+), 54 deletions(-) create mode 100644 app/components/work_packages/exports/base_export_settings_component.rb create mode 100644 app/components/work_packages/exports/csv/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/csv/export_settings_component.rb create mode 100644 app/components/work_packages/exports/modal_dialog_component.html.erb create mode 100644 app/components/work_packages/exports/modal_dialog_component.rb create mode 100644 app/components/work_packages/exports/pdf/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/pdf/export_settings_component.rb create mode 100644 app/components/work_packages/exports/xls/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/xls/export_settings_component.rb create mode 100644 frontend/src/stimulus/controllers/dynamic/work-packages/export/dialog.controller.ts create mode 100644 frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts create mode 100644 frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts diff --git a/app/components/work_packages/exports/base_export_settings_component.rb b/app/components/work_packages/exports/base_export_settings_component.rb new file mode 100644 index 000000000000..395d959a31a2 --- /dev/null +++ b/app/components/work_packages/exports/base_export_settings_component.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Exports + class BaseExportSettingsComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + include WorkPackagesHelper + + attr_reader :query + + def initialize(query) + super + + @query = query + end + + def selected_columns + query + .columns + .map { |s| { id: s.name, name: s.caption } } + end + end + end +end diff --git a/app/components/work_packages/exports/csv/export_settings_component.html.erb b/app/components/work_packages/exports/csv/export_settings_component.html.erb new file mode 100644 index 000000000000..1646d40d2e5f --- /dev/null +++ b/app/components/work_packages/exports/csv/export_settings_component.html.erb @@ -0,0 +1,19 @@ +<%= + flex_layout do |container| + container.with_row do |_columns| + helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: work_packages_columns_options, + selected: selected_columns, + protected: protected_work_packages_columns_options, + name: 'columns', + id: 'columns-select-export-csv', + dragAreaName: 'columns-select-export-csv', + inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), + dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } + end + end +%> diff --git a/app/components/work_packages/exports/csv/export_settings_component.rb b/app/components/work_packages/exports/csv/export_settings_component.rb new file mode 100644 index 000000000000..f44744540390 --- /dev/null +++ b/app/components/work_packages/exports/csv/export_settings_component.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Exports + module CSV + class ExportSettingsComponent < BaseExportSettingsComponent + end + end + end +end diff --git a/app/components/work_packages/exports/modal_dialog_component.html.erb b/app/components/work_packages/exports/modal_dialog_component.html.erb new file mode 100644 index 000000000000..adedc34b4ceb --- /dev/null +++ b/app/components/work_packages/exports/modal_dialog_component.html.erb @@ -0,0 +1,58 @@ +<%= render(Primer::Alpha::Dialog.new( + title: t('js.label_export'), + size: :xlarge, + id: MODAL_ID, + data: { + "application-target": "dynamic", + controller: "work-packages--export--dialog" + } +)) do |dialog| %> + <% dialog.with_header(variant: :large) %> + <% dialog.with_body do %> + <% flex_layout do |modal_body| %> + <% modal_body.with_row do |_format| %> + <%= render(Primer::Beta::Text.new(tag: "legend", font_size: :normal, mb: 2, font_weight: :bold)) { "Export format" } %> + <%= render(Primer::Alpha::SegmentedControl.new(ml: 0, mb: 2, "aria-label": "Export format", size: :medium)) do |component| + export_formats_settings.each do |format| + component.with_item( + label: format[:label], icon: format[:icon], selected: format[:selected], + data: { + "work-packages--export--dialog-format-param": format[:id], + action: "click:segmented-control#select click->work-packages--export--dialog#formatChanged" + }) + end + end %> +
+ <% end %> + <% export_formats_settings.each do |format| %> + <% modal_body.with_row( + classes: format[:selected] ? nil : "d-none", + data: { format: format[:id], "work-packages--export--dialog-target": "formatTab" } + ) do |_format_tabs| %> + <%= primer_form_with( + url: export_format_url(format[:id]), + id: "#{EXPORT_FORM_ID}-#{format[:id]}", + data: { + "application-target": "dynamic", + action: "submit->work-packages--export--form#submitForm", + controller: "work-packages--export--form", + } + ) do |form| %> + + <%= render(format[:component].new(query)) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + <% dialog.with_footer do %> + <%= render(Primer::ButtonComponent.new(data: { "close-dialog-id": MODAL_ID })) { I18n.t(:button_cancel) } %> + <%= render(Primer::ButtonComponent.new( + data: { + "close-dialog-id": MODAL_ID, + "work-packages--export--dialog-target": "submit" + }, + scheme: :primary, type: :submit, + form: "#{EXPORT_FORM_ID}-#{export_formats_settings.find { |e| e[:selected] }[:id]}")) { "Export" } %> + <% end %> +<% end %> diff --git a/app/components/work_packages/exports/modal_dialog_component.rb b/app/components/work_packages/exports/modal_dialog_component.rb new file mode 100644 index 000000000000..c9976f791a03 --- /dev/null +++ b/app/components/work_packages/exports/modal_dialog_component.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Exports + class ModalDialogComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + MODAL_ID = "op-work-packages-export-dialog" + EXPORT_FORM_ID = "op-work-packages-export-dialog-form" + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + attr_reader :query, :project, :query_params + + def initialize(query:, project:) + super + + @query = query + @project = project + @query_params = ::API::V3::Queries::QueryParamsRepresenter.new(query).to_h.to_json + end + + def export_format_url(format) + if @project.nil? + index_work_packages_path(format:) + else + project_work_packages_path(project, format:) + end + end + + def export_formats_settings + [ + { label: "PDF", id: 'pdf', icon: :"op-pdf", component: WorkPackages::Exports::PDF::ExportSettingsComponent, selected: true }, + { label: "XLS", id: 'xls', icon: :"op-xls", component: WorkPackages::Exports::XLS::ExportSettingsComponent }, + { label: "CSV", id: 'csv', icon: :"op-file-csv", component: WorkPackages::Exports::CSV::ExportSettingsComponent } + ] + end + end + end +end diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb new file mode 100644 index 000000000000..5774c63721b1 --- /dev/null +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -0,0 +1,107 @@ +<%= + flex_layout(data: { + "application-target": "dynamic", + controller: "work-packages--export--pdf--settings" + }) do |container| %> + + <%= container.with_row do %> + <%= render(Primer::Alpha::RadioButtonGroup.new( + mb: 3, + full_width: true, + name: "pdf_export_type", + label: "PDF export type")) do |component| + pdf_export_types.each do |entry| + component.radio_button(label: entry[:label], + value: entry[:value], + checked: current_pdf_export_type == entry[:value], + disabled: entry[:disabled] ? true : nil, + data: { + "work-packages--export--pdf--settings-name-param": entry[:value], + action: "work-packages--export--pdf--settings#typeChanged" + }, + caption: entry[:caption]) + end + end %> +
+ <% end %> + + <% container.with_row( + mt: 2, + classes: %w[table report].include?(current_pdf_export_type) ? nil : "d-none", + data: { + table: true, + report: true, + "work-packages--export--pdf--settings-target": "fields" + }) do |_columns| + helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: work_packages_columns_options, + selected: selected_columns, + protected: protected_work_packages_columns_options, + name: 'columns', + id: 'columns-select-export-pdf', + inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), + dragAreaName: 'columns-select-export-pdf', + dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } + end %> + + <%= container.with_row( + classes: current_pdf_export_type == "report" ? nil : "d-none", + data: { + report: true, + "work-packages--export--pdf--settings-target": "fields" + }) do |_pdf_report_images| %> +
+ <%= render(Primer::Alpha::CheckBox.new(name: 'show_images', + checked: true, + label: "Include images", + disabled: current_pdf_export_type != "report", + caption: "Exclude images to reduce the size of the PDF export.", + visually_hide_label: false)) %> + <% end %> + <% if is_gantt_chart_allowed? %> + <%= container.with_row(classes: current_pdf_export_type == "gantt" ? nil : "d-none", + data: { + gantt: true, + "work-packages--export--pdf--settings-target": "fields" + }) do |_pdf_gantt_paper_size| %> + <%= render(Primer::Alpha::Select.new( + name: "gantt_mode", + label: "Zoom level", + size: :medium, + disabled: current_pdf_export_type != "gantt", + caption: "Select what is the zoom level for dates displayed in the chart.", + value: gantt_zoom_levels.find { |e| e[:default] }[:value]) + ) do |component| + gantt_zoom_levels.each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <%= render(Primer::Alpha::Select.new( + name: "gantt_width", + label: "Table column width", + disabled: current_pdf_export_type != "gantt", + size: :medium, + value: gantt_column_widths.find { |e| e[:default] }[:value]) + ) do |component| + gantt_column_widths.each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <%= render(Primer::Alpha::Select.new( + name: "paper_size", + label: "Paper size", + size: :medium, + disabled: current_pdf_export_type != "gantt", + value: pdf_paper_sizes.find { |e| e[:default] }[:value]) + ) do |component| + pdf_paper_sizes.each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <% end %> + <% end %> +<% end %> diff --git a/app/components/work_packages/exports/pdf/export_settings_component.rb b/app/components/work_packages/exports/pdf/export_settings_component.rb new file mode 100644 index 000000000000..75bc83cd2f14 --- /dev/null +++ b/app/components/work_packages/exports/pdf/export_settings_component.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Exports + module PDF + class ExportSettingsComponent < BaseExportSettingsComponent + def current_pdf_export_type + 'table' + end + + def is_gantt_chart_allowed? + EnterpriseToken.allows_to?(:gantt_pdf_export) + end + + def pdf_export_types + [ + { label: "Table", value: 'table', + caption: "Export the work packages list in a table with the desired columns." }, + { label: "Report", value: 'report', + caption: "Export the work package on a detailed report of all work packages in the list." }, + { label: "Gantt chart", value: 'gantt', + caption: "Export the work packages list in a Gantt diagram view.", + disabled: !is_gantt_chart_allowed? } + ] + end + + def selected_columns + query + .columns + .map { |s| { id: s.name, name: s.caption } } + end + + def gantt_zoom_levels + [ + { label: t('js.gantt_chart.zoom.days'), value: 'day', default: true }, + { label: t('js.gantt_chart.zoom.weeks'), value: 'week' }, + { label: t('js.gantt_chart.zoom.months'), value: 'month' }, + { label: t('js.gantt_chart.zoom.quarters'), value: 'quarter' } + ] + end + + def gantt_column_widths + [ + { label: t('js.gantt_chart.export.column_widths.narrow'), value: 'narrow' }, + { label: t('js.gantt_chart.export.column_widths.medium'), value: 'medium', default: true }, + { label: t('js.gantt_chart.export.column_widths.wide'), value: 'wide' }, + { label: t('js.gantt_chart.export.column_widths.very_wide'), value: 'very_wide' } + ] + end + + def pdf_paper_sizes + [ + { label: 'A4', value: 'A4', default: true }, + { label: 'A3', value: 'A3' }, + { label: 'A2', value: 'A2' }, + { label: 'A1', value: 'A1' }, + { label: 'A0', value: 'A0' }, + { label: 'Executive', value: 'EXECUTIVE' }, + { label: 'Folio', value: 'FOLIO' }, + { label: 'Letter', value: 'LETTER' }, + { label: 'Tabloid', value: 'TABLOID' }, + ] + end + end + end + end +end diff --git a/app/components/work_packages/exports/xls/export_settings_component.html.erb b/app/components/work_packages/exports/xls/export_settings_component.html.erb new file mode 100644 index 000000000000..7f5799f862e0 --- /dev/null +++ b/app/components/work_packages/exports/xls/export_settings_component.html.erb @@ -0,0 +1,25 @@ +<%= + flex_layout do |container| + container.with_row(mb: 2) do |_columns| + helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: work_packages_columns_options, + selected: selected_columns, + protected: protected_work_packages_columns_options, + name: 'columns', + id: 'columns-select-export-xls', + inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), + dragAreaName: 'columns-select-export-xls', + dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } + end + container.with_row do |_xls_include_relations| + render(Primer::Alpha::CheckBox.new(name: 'show_relations', + label: "Include relations", + caption: "This option will create a duplicate of each work package for every relation this has with another work package.", + visually_hide_label: false)) + end + end +%> diff --git a/app/components/work_packages/exports/xls/export_settings_component.rb b/app/components/work_packages/exports/xls/export_settings_component.rb new file mode 100644 index 000000000000..130c2b7a2393 --- /dev/null +++ b/app/components/work_packages/exports/xls/export_settings_component.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Exports + module XLS + class ExportSettingsComponent < BaseExportSettingsComponent + end + end + end +end diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index b1236fe344c4..af7f1819d515 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -31,6 +31,7 @@ class WorkPackagesController < ApplicationController include PaginationHelper include Layout include WorkPackagesControllerHelper + include OpTurbo::DialogStreamHelper accept_key_auth :index, :show @@ -39,11 +40,14 @@ class WorkPackagesController < ApplicationController before_action :load_and_authorize_in_optional_project, :check_allowed_export, :protect_from_unauthorized_export, only: :index - authorization_checked! :index, :show + authorization_checked! :index, :show, :export_dialog before_action :load_and_validate_query, only: :index, unless: -> { request.format.html? } before_action :load_work_packages, only: :index, if: -> { request.format.atom? } + before_action :load_and_authorize_in_optional_project, only: :export_dialog, if: -> { request.format.html? } + before_action :load_query, only: :export_dialog, if: -> { request.format.html? } + def index respond_to do |format| format.html do @@ -84,13 +88,17 @@ def show end end + def export_dialog + respond_with_dialog WorkPackages::Exports::ModalDialogComponent.new(query: @query, project: @project) + end + protected def export_list(mime_type) job_id = WorkPackages::Exports::ScheduleService - .new(user: current_user) - .call(query: @query, mime_type:, params:) - .result + .new(user: current_user) + .call(query: @query, mime_type:, params:) + .result if request.headers["Accept"]&.include?("application/json") render json: { job_id: } @@ -101,8 +109,8 @@ def export_list(mime_type) def export_single(mime_type) exporter = Exports::Register - .single_exporter(WorkPackage, mime_type) - .new(work_package, params) + .single_exporter(WorkPackage, mime_type) + .new(work_package, params) export = exporter.export! send_data(export.content, type: export.mime_type, filename: export.title) @@ -144,19 +152,19 @@ def work_package def journals @journals ||= begin - order = - if current_user.wants_comments_in_reverse_order? - Journal.arel_table["created_at"].desc - else - Journal.arel_table["created_at"].asc - end - - work_package - .journals - .changing - .includes(:user) - .order(order).to_a - end + order = + if current_user.wants_comments_in_reverse_order? + Journal.arel_table["created_at"].desc + else + Journal.arel_table["created_at"].asc + end + + work_package + .journals + .changing + .includes(:user) + .order(order).to_a + end end def index_redirect_path diff --git a/app/helpers/work_packages_controller_helper.rb b/app/helpers/work_packages_controller_helper.rb index 55d9f019bc88..7b1cf15506c5 100644 --- a/app/helpers/work_packages_controller_helper.rb +++ b/app/helpers/work_packages_controller_helper.rb @@ -35,7 +35,7 @@ def protect_from_unauthorized_export end def check_allowed_export - return unless params[:format] == "pdf" && params[:gantt] == "true" + return unless params[:format] == "pdf" && params[:pdf_export_type] == "gantt" render_403 unless EnterpriseToken.allows_to?(:gantt_pdf_export) end @@ -52,6 +52,11 @@ def supported_single_formats ::Exports::Register.single_formats(WorkPackage).map(&:to_s) end + def load_query + @query ||= retrieve_query(@project) + @query.name = params[:title] if params[:title].present? + end + def load_and_validate_query @query ||= retrieve_query(@project) @query.name = params[:title] if params[:title].present? diff --git a/app/models/work_package/pdf_export/common.rb b/app/models/work_package/pdf_export/common.rb index 8d566dd3b337..f9e95c2b98e6 100644 --- a/app/models/work_package/pdf_export/common.rb +++ b/app/models/work_package/pdf_export/common.rb @@ -282,11 +282,11 @@ def get_groups end def wants_report? - options[:show_report] + options[:pdf_export_type] == "report" end def wants_gantt? - options[:gantt] + options[:pdf_export_type] == "gantt" end def with_cover? @@ -298,7 +298,7 @@ def with_sums_table? end def with_attachments? - options[:show_images] + options[:show_images] == "true" end def build_pdf_filename(base) diff --git a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb index 195e79cb4170..8ea7d89301a1 100644 --- a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb +++ b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb @@ -248,6 +248,6 @@ def footer_title end def with_images? - options[:show_images] + options[:show_images] == "true" end end diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 43adc3673709..3cbf2786414c 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -288,7 +288,7 @@ wpt.permission :export_work_packages, { - work_packages: %i[index all] + work_packages: %i[index export_dialog all] }, permissible_on: %i[work_package project], dependencies: :view_work_packages diff --git a/config/routes.rb b/config/routes.rb index 44e5ae35f536..4960de17050b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -302,6 +302,7 @@ get "/report/:detail" => "work_packages/reports#report_details" get "/report" => "work_packages/reports#report" get "menu" => "work_packages/menus#show" + get "/export_dialog" => "work_packages#export_dialog" end # states managed by client-side routing on work_package#index @@ -560,6 +561,7 @@ controller: "work_packages/progress", as: :work_package_progress end + get "/export_dialog" => "work_packages#export_dialog", on: :collection, as: "export_dialog" # states managed by client-side (angular) routing on work_package#show get "/" => "work_packages#index", on: :collection, as: "index" @@ -569,7 +571,7 @@ get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!shares).+/ } get "/share_upsale" => "work_packages#index", on: :collection, as: "share_upsale" get "/edit" => "work_packages#show", on: :member, as: "edit" - end + end resources :versions, only: %i[show edit update destroy] do member do diff --git a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts index 36b59bb82a81..35d0afcea989 100644 --- a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts +++ b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts @@ -61,6 +61,9 @@ export class DraggableAutocompleteComponent extends UntilDestroyedMixin implemen /** Label to display drag&drop area */ @Input() dragAreaLabel = ''; + /** Name of drag&drop area group */ + @Input() dragAreaName = 'columns'; + /** Decide whether to bind the component to the component or to the body */ /** Binding to the component in case the component is inside a Primer Dialog which uses popover */ @Input() appendToComponent = false; @@ -96,19 +99,19 @@ export class DraggableAutocompleteComponent extends UntilDestroyedMixin implemen // Setup groups this.columnsGroup = this.dragula.createGroup( - 'columns', + this.dragAreaName, { mirrorContainer: this.appendToComponent ? document.getElementById('op-draggable-autocomplete-container')! : document.body }, ); // Set cursor when dragging - this.dragula.drag('columns') + this.dragula.drag(this.dragAreaName) .pipe(this.untilDestroyed()) .subscribe(() => setBodyCursor('move', 'important')); // Reset cursor when cancel or dropped merge( - this.dragula.drop('columns'), - this.dragula.cancel('columns'), + this.dragula.drop(this.dragAreaName), + this.dragula.cancel(this.dragAreaName), ) .pipe(this.untilDestroyed()) .subscribe(() => setBodyCursor('auto')); @@ -147,7 +150,7 @@ export class DraggableAutocompleteComponent extends UntilDestroyedMixin implemen ngOnDestroy():void { super.ngOnDestroy(); - this.dragula.destroy('columns'); + this.dragula.destroy(this.dragAreaName); } select(item:DraggableOption|undefined) { diff --git a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts index 1dea6a7c2632..982f13748843 100644 --- a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts +++ b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts @@ -160,7 +160,7 @@ export class WpTableExportModalComponent extends OpModalComponent implements OnI } isGanttOption(option:ExportOptions):boolean { - return option.url.includes('&gantt=true'); + return option.url.includes('pdf_export_type=gantt'); } exportGantt(event:MouseEvent):void { diff --git a/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts b/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts index c2ed099b9fa8..873c12c6edcc 100644 --- a/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts +++ b/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts @@ -26,9 +26,7 @@ // See COPYRIGHT and LICENSE files for more details. //++ -import { - Directive, ElementRef, Injector, Input, -} from '@angular/core'; +import { Directive, ElementRef, Injector, Input } from '@angular/core'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; import { OpContextMenuTrigger } from 'core-app/shared/components/op-context-menu/handlers/op-context-menu-trigger.directive'; @@ -45,10 +43,16 @@ import { } from 'core-app/shared/components/editable-toolbar-title/editable-toolbar-title.component'; import { QuerySharingModalComponent } from 'core-app/shared/components/modals/share-modal/query-sharing.modal'; import { QueryGetIcalUrlModalComponent } from 'core-app/shared/components/modals/get-ical-url-modal/query-get-ical-url.modal'; -import { WpTableExportModalComponent } from 'core-app/shared/components/modals/export-modal/wp-table-export.modal'; import { SaveQueryModalComponent } from 'core-app/shared/components/modals/save-modal/save-query.modal'; import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource'; import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource'; +import * as Turbo from '@hotwired/turbo'; +import { + WorkPackageViewColumnsService +} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service'; +import { StaticQueriesService } from 'core-app/shared/components/op-view-select/op-static-queries.service'; +import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; +import * as URI from 'urijs'; @Directive({ selector: '[opSettingsContextMenu]', @@ -73,6 +77,9 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger { readonly states:States, readonly injector:Injector, readonly querySpace:IsolatedQuerySpace, + readonly wpTableColumns:WorkPackageViewColumnsService, + readonly urlParamsHelper:UrlParamsHelperService, + readonly opStaticQueries:StaticQueriesService, readonly I18n:I18nService, ) { super(elementRef, opContextMenu); @@ -161,6 +168,34 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger { return false; } + private addColumnsAndTitleToHref(href:string) { + const columns = this.wpTableColumns.getColumns(); + + const columnIds = columns.map((column) => column.id); + + const url = URI(href); + // Remove current columns + url.removeSearch('columns[]'); + url.addSearch('columns[]', columnIds); + + // Add the query title for the export + const query = this.querySpace.query.value; + if (query) { + url.removeSearch('title'); + url.addSearch('title', this.queryTitle(query)); + } + + return url.toString(); + } + + private queryTitle(query:QueryResource):string { + return isPersistedResource(query) ? query.name : this.staticQueryName(query); + } + + protected staticQueryName(query:QueryResource):string { + return this.opStaticQueries.getStaticName(query); + } + private buildItems() { this.items = [ { @@ -298,9 +333,24 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger { icon: 'icon-export', onClick: ($event:JQuery.TriggeredEvent) => { if (this.allowWorkPackageAction($event, 'representations')) { - this.opModalService.show(WpTableExportModalComponent, this.injector); + // old angular export form: this.opModalService.show(WpTableExportModalComponent, this.injector); + // TODO: better way to get the export URL + const url = new URL(window.location.href); + url.pathname = `${url.pathname}/export_dialog`; + const href = this.addColumnsAndTitleToHref(url.toString()); + // TODO: export angular=>fetch=>turbo in a utility function + fetch(href, { + method: 'GET', + headers: { Accept: 'text/vnd.turbo-stream.html' }, + credentials: 'same-origin', + }) + .then((r) => r.text()) + .then((html) => Turbo.renderStreamMessage(html)) + .catch(error => { + // TODO: error handling + console.error(error); + }); } - return true; }, }, diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/dialog.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/dialog.controller.ts new file mode 100644 index 000000000000..e0046aa2b503 --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/dialog.controller.ts @@ -0,0 +1,27 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class DialogController extends Controller { + static targets = [ + 'formatTab', 'submit', + ]; + + declare readonly formatTabTargets:HTMLInputElement[]; + declare readonly submitTarget:HTMLInputElement; + + formatChanged({ params: { format } }:{ params:{ format:string } }) { + this.formatTabTargets.forEach((element:HTMLElement) => { + if (element.getAttribute('data-format') === format) { + element.classList.remove('d-none'); + this.adjustFormSubmitTarget(element); + } else { + element.classList.add('d-none'); + } + }); + } + + private adjustFormSubmitTarget(element:HTMLElement) { + const form = element.querySelector('form'); + const form_id = form?.getAttribute('id') as string; + this.submitTarget.setAttribute('form', form_id); + } +} diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts new file mode 100644 index 000000000000..b673ef68fd5c --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts @@ -0,0 +1,52 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class FormController extends Controller { + submitForm(evt:CustomEvent) { + evt.preventDefault(); // Don't submit + const formatURL = this.element.getAttribute('action') + const searchParams = this.getExportParams() + const exportURL = `${formatURL}?${searchParams.toString()}`; + fetch(exportURL, { + method: 'GET', + headers: { Accept: 'application/json' }, + credentials: 'same-origin', + }) + .then((r) => r.json()) + .then((result:{ job_id:string }) => { + // TODO: implement with turbo and open job modal + window.location.href = `/job_statuses/${result.job_id}`; + }) + .catch(error => { + // TODO: error handling + alert(error.message); + }); + } + + private createSearchParams(params:Object) { + const searchParams = new URLSearchParams(); + Object.entries(params).forEach(([key, values]) => { + if (Array.isArray(values)) { + values.forEach((value) => { + searchParams.append(key + '[]', value); + }); + } else { + searchParams.append(key, values); + } + }); + return searchParams; + } + + private getExportParams() { + const data = new FormData(this.element) + const query = JSON.parse(data.get('query') as string) + for (let [key, value] of (data as any)) { + // Skip hidden fields + if (['query', 'utf8', 'authenticity_token'].includes(key)) { + continue + } + query[key] = (key === 'columns') ? value.split(' ') : value + } + const formatURL = this.element.getAttribute('action') + return this.createSearchParams(query); + } +} diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts new file mode 100644 index 000000000000..0fcdd5f2237c --- /dev/null +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts @@ -0,0 +1,27 @@ +import { Controller } from '@hotwired/stimulus'; + +export default class PDFExportSettingsController extends Controller { + static targets = [ + 'fields', + ]; + + declare readonly fieldsTargets:HTMLInputElement[]; + + private silenceFormFields(element:HTMLElement, silence:boolean) { + element.querySelectorAll('input, select').forEach((input:HTMLInputElement) => { + input.disabled = silence; + }); + } + + typeChanged({ params: { name } }:{ params:{ name:string } }) { + this.fieldsTargets.forEach((element:HTMLElement) => { + if (element.getAttribute(`data-${name}`) === 'true') { + element.classList.remove('d-none'); + this.silenceFormFields(element, false) + } else { + element.classList.add('d-none'); + this.silenceFormFields(element, true) + } + }); + } +} diff --git a/lib/api/v3/work_packages/work_package_collection_representer.rb b/lib/api/v3/work_packages/work_package_collection_representer.rb index 55ffc56e791f..822932e6d1a6 100644 --- a/lib/api/v3/work_packages/work_package_collection_representer.rb +++ b/lib/api/v3/work_packages/work_package_collection_representer.rb @@ -269,7 +269,8 @@ def representation_format(identifier, mime_type:, format: identifier, i18n_key: def representation_format_pdf representation_format "pdf", i18n_key: "pdf_overview_table", - mime_type: "application/pdf" + mime_type: "application/pdf", + url_query_extras: "pdf_export_type=table" end def representation_format_pdf_report_with_images @@ -277,7 +278,7 @@ def representation_format_pdf_report_with_images format: "pdf", i18n_key: "pdf_report_with_images", mime_type: "application/pdf", - url_query_extras: "show_images=true&show_report=true" + url_query_extras: "pdf_export_type=report&show_images=true" end def representation_format_pdf_report @@ -285,7 +286,7 @@ def representation_format_pdf_report format: "pdf", i18n_key: "pdf_report", mime_type: "application/pdf", - url_query_extras: "show_report=true" + url_query_extras: "pdf_export_type=report" end def representation_format_pdf_gantt @@ -295,7 +296,7 @@ def representation_format_pdf_gantt format: "pdf", i18n_key: "pdf_gantt", mime_type: "application/pdf", - url_query_extras: "gantt=true" + url_query_extras: "pdf_export_type=gantt" end def representation_format_xls diff --git a/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb index 8c6089022d86..fd9ca4b5a7e9 100644 --- a/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb @@ -125,20 +125,20 @@ expected_query_params = query_params.merge(pageSize: 30, offset: 1) JSON.parse([ { - href: work_packages_path({ format: "pdf" }.merge(expected_query_params)), + href: work_packages_path({ format: "pdf", pdf_export_type: "table" }.merge(expected_query_params)), type: "application/pdf", identifier: "pdf", title: I18n.t("export.format.pdf_overview_table") }, { - href: work_packages_path({ format: "pdf", show_images: true, show_report: true } + href: work_packages_path({ format: "pdf", pdf_export_type: "report", show_images: true } .merge(expected_query_params)), identifier: "pdf-with-descriptions", type: "application/pdf", title: I18n.t("export.format.pdf_report_with_images") }, { - href: work_packages_path({ format: "pdf", show_report: true }.merge(expected_query_params)), + href: work_packages_path({ format: "pdf", pdf_export_type: "report" }.merge(expected_query_params)), identifier: "pdf-descr", type: "application/pdf", title: I18n.t("export.format.pdf_report") @@ -175,20 +175,20 @@ expected_query_params = query_params.merge(pageSize: 30, offset: 1) JSON.parse([ { - href: project_work_packages_path(project, { format: "pdf" }.merge(expected_query_params)), + href: project_work_packages_path(project, { format: "pdf", pdf_export_type: "table" }.merge(expected_query_params)), type: "application/pdf", identifier: "pdf", title: I18n.t("export.format.pdf_overview_table") }, { - href: project_work_packages_path(project, { format: "pdf", show_images: true, show_report: true } + href: project_work_packages_path(project, { format: "pdf", pdf_export_type: "report", show_images: true } .merge(expected_query_params)), type: "application/pdf", identifier: "pdf-with-descriptions", title: I18n.t("export.format.pdf_report_with_images") }, { - href: project_work_packages_path(project, { format: "pdf", show_report: true }.merge(expected_query_params)), + href: project_work_packages_path(project, { format: "pdf", pdf_export_type: "report" }.merge(expected_query_params)), type: "application/pdf", identifier: "pdf-descr", title: I18n.t("export.format.pdf_report") diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_gantt_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_gantt_spec.rb index a1040f3d8ef0..d8c94d92cf5e 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_gantt_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_gantt_spec.rb @@ -68,7 +68,7 @@ def respond_to?(*) create(:user, member_with_permissions: { project => %w[view_work_packages export_work_packages] }) end - let(:options) { { gantt: true, gantt_mode: "day", gantt_width: "wide", paper_size: "EXECUTIVE" } } + let(:options) { { pdf_export_type: "gantt", gantt_mode: "day", gantt_width: "wide", paper_size: "EXECUTIVE" } } let(:query_attributes) { {} } let(:column_names) { %w[id subject status] } let!(:query) do diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 4f542a0b4f51..7dc9d95ef0a9 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -219,7 +219,7 @@ def pdf_eq_ignore_spacing(strings) end describe "with a request for a PDF Report" do - let(:options) { { show_report: true } } + let(:options) { { pdf_export_type: "report" } } it "contains correct data" do expect(pdf_strings).to eq [ @@ -236,7 +236,7 @@ def pdf_eq_ignore_spacing(strings) end describe "with a request for a PDF Report with hierarchies" do - let(:options) { { show_report: true } } + let(:options) { { pdf_export_type: "report" } } let(:query_attributes) { { show_hierarchies: true } } it "contains correct data" do @@ -254,7 +254,7 @@ def pdf_eq_ignore_spacing(strings) end describe "with a request for a PDF Report with sums" do - let(:options) { { show_report: true } } + let(:options) { { pdf_export_type: "report" } } let(:query_attributes) { { display_sums: true } } it "contains correct data" do @@ -275,7 +275,7 @@ def pdf_eq_ignore_spacing(strings) end describe "with a request for a PDF Report grouped with sums" do - let(:options) { { show_report: true } } + let(:options) { { pdf_export_type: "report" } } let(:query_attributes) { { display_sums: true, group_by: "type" } } it "contains correct data" do @@ -298,7 +298,7 @@ def pdf_eq_ignore_spacing(strings) end describe "with a request for a PDF Report grouped by a custom field with sums" do - let(:options) { { show_report: true } } + let(:options) { { pdf_export_type: "report" } } let(:query_attributes) { { display_sums: true, group_by: list_custom_field.column_name } } it "contains correct data" do From 209c13173f343d643fcc2ca80f7745b04045d501 Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 7 Aug 2024 16:44:08 +0200 Subject: [PATCH 02/80] obey rubocop --- .../exports/base_export_settings_component.rb | 2 +- .../exports/modal_dialog_component.rb | 18 ++++---- .../pdf/export_settings_component.html.erb | 2 +- .../exports/pdf/export_settings_component.rb | 46 +++++++++---------- app/controllers/work_packages_controller.rb | 28 +++++------ .../work_packages_controller_helper.rb | 5 -- config/routes.rb | 2 +- ...ork_package_collection_representer_spec.rb | 3 +- 8 files changed, 51 insertions(+), 55 deletions(-) diff --git a/app/components/work_packages/exports/base_export_settings_component.rb b/app/components/work_packages/exports/base_export_settings_component.rb index 395d959a31a2..5c6bafd12b14 100644 --- a/app/components/work_packages/exports/base_export_settings_component.rb +++ b/app/components/work_packages/exports/base_export_settings_component.rb @@ -30,7 +30,7 @@ module WorkPackages module Exports - class BaseExportSettingsComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + class BaseExportSettingsComponent < ApplicationComponent include OpPrimer::ComponentHelpers include OpTurbo::Streamable include WorkPackagesHelper diff --git a/app/components/work_packages/exports/modal_dialog_component.rb b/app/components/work_packages/exports/modal_dialog_component.rb index c9976f791a03..b00623470ff8 100644 --- a/app/components/work_packages/exports/modal_dialog_component.rb +++ b/app/components/work_packages/exports/modal_dialog_component.rb @@ -30,7 +30,7 @@ module WorkPackages module Exports - class ModalDialogComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent + class ModalDialogComponent < ApplicationComponent MODAL_ID = "op-work-packages-export-dialog" EXPORT_FORM_ID = "op-work-packages-export-dialog-form" include OpTurbo::Streamable @@ -47,18 +47,18 @@ def initialize(query:, project:) end def export_format_url(format) - if @project.nil? - index_work_packages_path(format:) - else - project_work_packages_path(project, format:) - end + @project.nil? ? index_work_packages_path(format:) : project_work_packages_path(project, format:) end def export_formats_settings [ - { label: "PDF", id: 'pdf', icon: :"op-pdf", component: WorkPackages::Exports::PDF::ExportSettingsComponent, selected: true }, - { label: "XLS", id: 'xls', icon: :"op-xls", component: WorkPackages::Exports::XLS::ExportSettingsComponent }, - { label: "CSV", id: 'csv', icon: :"op-file-csv", component: WorkPackages::Exports::CSV::ExportSettingsComponent } + { label: "PDF", id: "pdf", icon: :"op-pdf", + component: WorkPackages::Exports::PDF::ExportSettingsComponent, + selected: true }, + { label: "XLS", id: "xls", icon: :"op-xls", + component: WorkPackages::Exports::XLS::ExportSettingsComponent }, + { label: "CSV", id: "csv", icon: :"op-file-csv", + component: WorkPackages::Exports::CSV::ExportSettingsComponent } ] end end diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index 5774c63721b1..555c48020475 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -62,7 +62,7 @@ caption: "Exclude images to reduce the size of the PDF export.", visually_hide_label: false)) %> <% end %> - <% if is_gantt_chart_allowed? %> + <% if gantt_chart_allowed? %> <%= container.with_row(classes: current_pdf_export_type == "gantt" ? nil : "d-none", data: { gantt: true, diff --git a/app/components/work_packages/exports/pdf/export_settings_component.rb b/app/components/work_packages/exports/pdf/export_settings_component.rb index 75bc83cd2f14..4cbc5a56f8d3 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.rb +++ b/app/components/work_packages/exports/pdf/export_settings_component.rb @@ -33,22 +33,22 @@ module Exports module PDF class ExportSettingsComponent < BaseExportSettingsComponent def current_pdf_export_type - 'table' + "table" end - def is_gantt_chart_allowed? + def gantt_chart_allowed? EnterpriseToken.allows_to?(:gantt_pdf_export) end def pdf_export_types [ - { label: "Table", value: 'table', + { label: "Table", value: "table", caption: "Export the work packages list in a table with the desired columns." }, - { label: "Report", value: 'report', + { label: "Report", value: "report", caption: "Export the work package on a detailed report of all work packages in the list." }, - { label: "Gantt chart", value: 'gantt', + { label: "Gantt chart", value: "gantt", caption: "Export the work packages list in a Gantt diagram view.", - disabled: !is_gantt_chart_allowed? } + disabled: !gantt_chart_allowed? } ] end @@ -60,33 +60,33 @@ def selected_columns def gantt_zoom_levels [ - { label: t('js.gantt_chart.zoom.days'), value: 'day', default: true }, - { label: t('js.gantt_chart.zoom.weeks'), value: 'week' }, - { label: t('js.gantt_chart.zoom.months'), value: 'month' }, - { label: t('js.gantt_chart.zoom.quarters'), value: 'quarter' } + { label: t("js.gantt_chart.zoom.days"), value: "day", default: true }, + { label: t("js.gantt_chart.zoom.weeks"), value: "week" }, + { label: t("js.gantt_chart.zoom.months"), value: "month" }, + { label: t("js.gantt_chart.zoom.quarters"), value: "quarter" } ] end def gantt_column_widths [ - { label: t('js.gantt_chart.export.column_widths.narrow'), value: 'narrow' }, - { label: t('js.gantt_chart.export.column_widths.medium'), value: 'medium', default: true }, - { label: t('js.gantt_chart.export.column_widths.wide'), value: 'wide' }, - { label: t('js.gantt_chart.export.column_widths.very_wide'), value: 'very_wide' } + { label: t("js.gantt_chart.export.column_widths.narrow"), value: "narrow" }, + { label: t("js.gantt_chart.export.column_widths.medium"), value: "medium", default: true }, + { label: t("js.gantt_chart.export.column_widths.wide"), value: "wide" }, + { label: t("js.gantt_chart.export.column_widths.very_wide"), value: "very_wide" } ] end def pdf_paper_sizes [ - { label: 'A4', value: 'A4', default: true }, - { label: 'A3', value: 'A3' }, - { label: 'A2', value: 'A2' }, - { label: 'A1', value: 'A1' }, - { label: 'A0', value: 'A0' }, - { label: 'Executive', value: 'EXECUTIVE' }, - { label: 'Folio', value: 'FOLIO' }, - { label: 'Letter', value: 'LETTER' }, - { label: 'Tabloid', value: 'TABLOID' }, + { label: "A4", value: "A4", default: true }, + { label: "A3", value: "A3" }, + { label: "A2", value: "A2" }, + { label: "A1", value: "A1" }, + { label: "A0", value: "A0" }, + { label: "Executive", value: "EXECUTIVE" }, + { label: "Folio", value: "FOLIO" }, + { label: "Letter", value: "LETTER" }, + { label: "Tabloid", value: "TABLOID" } ] end end diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index af7f1819d515..0f156018764a 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -46,7 +46,7 @@ class WorkPackagesController < ApplicationController before_action :load_work_packages, only: :index, if: -> { request.format.atom? } before_action :load_and_authorize_in_optional_project, only: :export_dialog, if: -> { request.format.html? } - before_action :load_query, only: :export_dialog, if: -> { request.format.html? } + before_action :load_and_validate_query, only: :export_dialog, if: -> { request.format.html? } def index respond_to do |format| @@ -152,19 +152,19 @@ def work_package def journals @journals ||= begin - order = - if current_user.wants_comments_in_reverse_order? - Journal.arel_table["created_at"].desc - else - Journal.arel_table["created_at"].asc - end - - work_package - .journals - .changing - .includes(:user) - .order(order).to_a - end + order = + if current_user.wants_comments_in_reverse_order? + Journal.arel_table["created_at"].desc + else + Journal.arel_table["created_at"].asc + end + + work_package + .journals + .changing + .includes(:user) + .order(order).to_a + end end def index_redirect_path diff --git a/app/helpers/work_packages_controller_helper.rb b/app/helpers/work_packages_controller_helper.rb index 7b1cf15506c5..6e1ae6e78033 100644 --- a/app/helpers/work_packages_controller_helper.rb +++ b/app/helpers/work_packages_controller_helper.rb @@ -52,11 +52,6 @@ def supported_single_formats ::Exports::Register.single_formats(WorkPackage).map(&:to_s) end - def load_query - @query ||= retrieve_query(@project) - @query.name = params[:title] if params[:title].present? - end - def load_and_validate_query @query ||= retrieve_query(@project) @query.name = params[:title] if params[:title].present? diff --git a/config/routes.rb b/config/routes.rb index 4960de17050b..f83dec93343b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -571,7 +571,7 @@ get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!shares).+/ } get "/share_upsale" => "work_packages#index", on: :collection, as: "share_upsale" get "/edit" => "work_packages#show", on: :member, as: "edit" - end + end resources :versions, only: %i[show edit update destroy] do member do diff --git a/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb index fd9ca4b5a7e9..a6bd379769c9 100644 --- a/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_collection_representer_spec.rb @@ -188,7 +188,8 @@ title: I18n.t("export.format.pdf_report_with_images") }, { - href: project_work_packages_path(project, { format: "pdf", pdf_export_type: "report" }.merge(expected_query_params)), + href: project_work_packages_path(project, + { format: "pdf", pdf_export_type: "report" }.merge(expected_query_params)), type: "application/pdf", identifier: "pdf-descr", title: I18n.t("export.format.pdf_report") From e4f3247bdba40078f5a0e2aec0c4842e3eb7af1a Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 7 Aug 2024 16:45:25 +0200 Subject: [PATCH 03/80] remove unused code --- .../controllers/dynamic/work-packages/export/form.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts index b673ef68fd5c..059edc31a29a 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts @@ -46,7 +46,6 @@ export default class FormController extends Controller { } query[key] = (key === 'columns') ? value.split(' ') : value } - const formatURL = this.element.getAttribute('action') return this.createSearchParams(query); } } From 9c2fb38616cba85b548d59dfc39dbe049ba495a5 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 8 Aug 2024 10:52:03 +0200 Subject: [PATCH 04/80] do not overwrite rails before_action callbacks --- app/controllers/work_packages_controller.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index 0f156018764a..b2461ee0c4b4 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -45,8 +45,8 @@ class WorkPackagesController < ApplicationController before_action :load_and_validate_query, only: :index, unless: -> { request.format.html? } before_action :load_work_packages, only: :index, if: -> { request.format.atom? } - before_action :load_and_authorize_in_optional_project, only: :export_dialog, if: -> { request.format.html? } - before_action :load_and_validate_query, only: :export_dialog, if: -> { request.format.html? } + before_action :load_and_authorize_in_optional_project_for_export, only: :export_dialog, if: -> { request.format.html? } + before_action :load_and_validate_query_for_export, only: :export_dialog, if: -> { request.format.html? } def index respond_to do |format| @@ -94,6 +94,14 @@ def export_dialog protected + def load_and_authorize_in_optional_project_for_export + load_and_authorize_in_optional_project + end + + def load_and_validate_query_for_export + load_and_validate_query + end + def export_list(mime_type) job_id = WorkPackages::Exports::ScheduleService .new(user: current_user) From 0a0ab1c77aec249fb1c5ea131909f16589f2abf1 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 8 Aug 2024 16:53:36 +0200 Subject: [PATCH 05/80] use I18n, start adjusting dialog spec, start deconstructing angular dialog --- .../exports/modal_dialog_component.html.erb | 8 +- .../exports/modal_dialog_component.rb | 9 +- .../pdf/export_settings_component.html.erb | 55 +-- .../exports/pdf/export_settings_component.rb | 53 ++- .../xls/export_settings_component.html.erb | 4 +- app/models/work_package/pdf_export/common.rb | 2 +- .../pdf_export/work_package_list_to_pdf.rb | 2 +- config/locales/en.yml | 51 +++ config/locales/js-en.yml | 13 - .../app/core/turbo/turbo-requests.service.ts | 12 +- .../export-modal/wp-table-export.modal.html | 44 +-- .../export-modal/wp-table-export.modal.ts | 85 +--- .../op-settings-dropdown-menu.directive.ts | 15 +- spec/features/work_packages/export_spec.rb | 365 +++++++----------- 14 files changed, 277 insertions(+), 441 deletions(-) diff --git a/app/components/work_packages/exports/modal_dialog_component.html.erb b/app/components/work_packages/exports/modal_dialog_component.html.erb index adedc34b4ceb..564b3bda15e9 100644 --- a/app/components/work_packages/exports/modal_dialog_component.html.erb +++ b/app/components/work_packages/exports/modal_dialog_component.html.erb @@ -1,5 +1,5 @@ <%= render(Primer::Alpha::Dialog.new( - title: t('js.label_export'), + title: I18n.t('export.dialog.title'), size: :xlarge, id: MODAL_ID, data: { @@ -11,8 +11,8 @@ <% dialog.with_body do %> <% flex_layout do |modal_body| %> <% modal_body.with_row do |_format| %> - <%= render(Primer::Beta::Text.new(tag: "legend", font_size: :normal, mb: 2, font_weight: :bold)) { "Export format" } %> - <%= render(Primer::Alpha::SegmentedControl.new(ml: 0, mb: 2, "aria-label": "Export format", size: :medium)) do |component| + <%= render(Primer::Beta::Text.new(tag: "legend", font_size: :normal, mb: 2, font_weight: :bold)) { I18n.t('export.dialog.format.label') } %> + <%= render(Primer::Alpha::SegmentedControl.new(ml: 0, mb: 2, "aria-label": I18n.t('export.dialog.format.label'), size: :medium)) do |component| export_formats_settings.each do |format| component.with_item( label: format[:label], icon: format[:icon], selected: format[:selected], @@ -53,6 +53,6 @@ "work-packages--export--dialog-target": "submit" }, scheme: :primary, type: :submit, - form: "#{EXPORT_FORM_ID}-#{export_formats_settings.find { |e| e[:selected] }[:id]}")) { "Export" } %> + form: "#{EXPORT_FORM_ID}-#{export_formats_settings.find { |e| e[:selected] }[:id]}")) { I18n.t('export.dialog.submit') } %> <% end %> <% end %> diff --git a/app/components/work_packages/exports/modal_dialog_component.rb b/app/components/work_packages/exports/modal_dialog_component.rb index b00623470ff8..93f374c16076 100644 --- a/app/components/work_packages/exports/modal_dialog_component.rb +++ b/app/components/work_packages/exports/modal_dialog_component.rb @@ -52,12 +52,15 @@ def export_format_url(format) def export_formats_settings [ - { label: "PDF", id: "pdf", icon: :"op-pdf", + { id: "pdf", icon: :"op-pdf", + label: I18n.t("export.dialog.format.options.pdf.label"), component: WorkPackages::Exports::PDF::ExportSettingsComponent, selected: true }, - { label: "XLS", id: "xls", icon: :"op-xls", + { id: "xls", icon: :"op-xls", + label: I18n.t("export.dialog.format.options.xls.label"), component: WorkPackages::Exports::XLS::ExportSettingsComponent }, - { label: "CSV", id: "csv", icon: :"op-file-csv", + { id: "csv", icon: :"op-file-csv", + label: I18n.t("export.dialog.format.options.csv.label"), component: WorkPackages::Exports::CSV::ExportSettingsComponent } ] end diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index 555c48020475..81b072843ed3 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -9,7 +9,7 @@ mb: 3, full_width: true, name: "pdf_export_type", - label: "PDF export type")) do |component| + label: I18n.t("export.dialog.pdf.export_type.label"))) do |component| pdf_export_types.each do |entry| component.radio_button(label: entry[:label], value: entry[:value], @@ -57,9 +57,9 @@
<%= render(Primer::Alpha::CheckBox.new(name: 'show_images', checked: true, - label: "Include images", + label: I18n.t("export.dialog.pdf.include_images.label"), + caption: I18n.t("export.dialog.pdf.include_images.caption"), disabled: current_pdf_export_type != "report", - caption: "Exclude images to reduce the size of the PDF export.", visually_hide_label: false)) %> <% end %> <% if gantt_chart_allowed? %> @@ -68,40 +68,21 @@ gantt: true, "work-packages--export--pdf--settings-target": "fields" }) do |_pdf_gantt_paper_size| %> - <%= render(Primer::Alpha::Select.new( - name: "gantt_mode", - label: "Zoom level", - size: :medium, - disabled: current_pdf_export_type != "gantt", - caption: "Select what is the zoom level for dates displayed in the chart.", - value: gantt_zoom_levels.find { |e| e[:default] }[:value]) - ) do |component| - gantt_zoom_levels.each do |entry| - component.option(label: entry[:label], value: entry[:value]) - end - end %> - <%= render(Primer::Alpha::Select.new( - name: "gantt_width", - label: "Table column width", - disabled: current_pdf_export_type != "gantt", - size: :medium, - value: gantt_column_widths.find { |e| e[:default] }[:value]) - ) do |component| - gantt_column_widths.each do |entry| - component.option(label: entry[:label], value: entry[:value]) - end - end %> - <%= render(Primer::Alpha::Select.new( - name: "paper_size", - label: "Paper size", - size: :medium, - disabled: current_pdf_export_type != "gantt", - value: pdf_paper_sizes.find { |e| e[:default] }[:value]) - ) do |component| - pdf_paper_sizes.each do |entry| - component.option(label: entry[:label], value: entry[:value]) - end - end %> + + <% gantt_selects.each do |entry| %> + <%= render(Primer::Alpha::Select.new( + name: entry[:name], + label: entry[:label], + caption: entry[:caption], + size: :medium, + disabled: current_pdf_export_type != "gantt", + value: entry[:options].find { |e| e[:default] }[:value]) + ) do |component| + entry[:options].each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/components/work_packages/exports/pdf/export_settings_component.rb b/app/components/work_packages/exports/pdf/export_settings_component.rb index 4cbc5a56f8d3..093c39671352 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.rb +++ b/app/components/work_packages/exports/pdf/export_settings_component.rb @@ -42,12 +42,15 @@ def gantt_chart_allowed? def pdf_export_types [ - { label: "Table", value: "table", - caption: "Export the work packages list in a table with the desired columns." }, - { label: "Report", value: "report", - caption: "Export the work package on a detailed report of all work packages in the list." }, - { label: "Gantt chart", value: "gantt", - caption: "Export the work packages list in a Gantt diagram view.", + { value: "table", + label: I18n.t("export.dialog.pdf.export_type.options.table.label"), + caption: I18n.t("export.dialog.pdf.export_type.options.table.caption") }, + { value: "report", + label: I18n.t("export.dialog.pdf.export_type.options.report.label"), + caption: I18n.t("export.dialog.pdf.export_type.options.report.caption") }, + { value: "gantt", + label: I18n.t("export.dialog.pdf.export_type.options.gantt.label"), + caption: I18n.t("export.dialog.pdf.export_type.options.gantt.caption"), disabled: !gantt_chart_allowed? } ] end @@ -58,21 +61,43 @@ def selected_columns .map { |s| { id: s.name, name: s.caption } } end + def gantt_selects + [ + { + name: "gantt_mode", + label: I18n.t("export.dialog.pdf.gantt_zoom_levels.label"), + caption: I18n.t("export.dialog.pdf.gantt_zoom_levels.caption"), + options: gantt_zoom_levels + }, + { + name: "gantt_width", + label: I18n.t("export.dialog.pdf.column_width.label"), + options: gantt_column_widths + }, + { + name: "paper_size", + label: I18n.t("export.dialog.pdf.paper_size.label"), + caption: I18n.t("export.dialog.pdf.paper_size.caption"), + options: pdf_paper_sizes + } + ] + end + def gantt_zoom_levels [ - { label: t("js.gantt_chart.zoom.days"), value: "day", default: true }, - { label: t("js.gantt_chart.zoom.weeks"), value: "week" }, - { label: t("js.gantt_chart.zoom.months"), value: "month" }, - { label: t("js.gantt_chart.zoom.quarters"), value: "quarter" } + { label: t("export.dialog.pdf.gantt_zoom_levels.options.days"), value: "day", default: true }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.weeks"), value: "week" }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.months"), value: "month" }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.quarters"), value: "quarter" } ] end def gantt_column_widths [ - { label: t("js.gantt_chart.export.column_widths.narrow"), value: "narrow" }, - { label: t("js.gantt_chart.export.column_widths.medium"), value: "medium", default: true }, - { label: t("js.gantt_chart.export.column_widths.wide"), value: "wide" }, - { label: t("js.gantt_chart.export.column_widths.very_wide"), value: "very_wide" } + { label: t("export.dialog.pdf.column_width.options.narrow"), value: "narrow" }, + { label: t("export.dialog.pdf.column_width.options.medium"), value: "medium", default: true }, + { label: t("export.dialog.pdf.column_width.options.wide"), value: "wide" }, + { label: t("export.dialog.pdf.column_width.options.very_wide"), value: "very_wide" } ] end diff --git a/app/components/work_packages/exports/xls/export_settings_component.html.erb b/app/components/work_packages/exports/xls/export_settings_component.html.erb index 7f5799f862e0..85f6d3edc9ad 100644 --- a/app/components/work_packages/exports/xls/export_settings_component.html.erb +++ b/app/components/work_packages/exports/xls/export_settings_component.html.erb @@ -17,8 +17,8 @@ end container.with_row do |_xls_include_relations| render(Primer::Alpha::CheckBox.new(name: 'show_relations', - label: "Include relations", - caption: "This option will create a duplicate of each work package for every relation this has with another work package.", + label: I18n.t("export.dialog.xls.show_relations.label"), + caption: I18n.t("export.dialog.xls.show_relations.caption"), visually_hide_label: false)) end end diff --git a/app/models/work_package/pdf_export/common.rb b/app/models/work_package/pdf_export/common.rb index f9e95c2b98e6..cadbc07230ba 100644 --- a/app/models/work_package/pdf_export/common.rb +++ b/app/models/work_package/pdf_export/common.rb @@ -298,7 +298,7 @@ def with_sums_table? end def with_attachments? - options[:show_images] == "true" + options[:show_images] == "true" || options[:show_images] == "1" end def build_pdf_filename(base) diff --git a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb index 8ea7d89301a1..4b23f7f82b40 100644 --- a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb +++ b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb @@ -248,6 +248,6 @@ def footer_title end def with_images? - options[:show_images] == "true" + options[:show_images] == "true" || options[:show_images] == "1" end end diff --git a/config/locales/en.yml b/config/locales/en.yml index ec15f345508c..4c15d55f8004 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1742,6 +1742,57 @@ en: subproject: "Subproject: %{name}" export: + dialog: + title: "Export" + submit: "Export" + format: + label: "File format" + options: + csv: + label: "CSV" + pdf: + label: "PDF" + xls: + label: "XLS" + pdf: + export_type: + label: "PDF export type" + options: + table: + label: "Table" + caption: "Export the work packages list in a table with the desired columns." + report: + label: "Report" + caption: "Export the work package on a detailed report of all work packages in the list." + gantt: + label: "Gantt chart" + caption: "Export the work packages list in a Gantt diagram view." + include_images: + label: "Include images" + caption: "Exclude images to reduce the size of the PDF export." + gantt_zoom_levels: + label: "Zoom levels" + caption: "Select what is the zoom level for dates displayed in the chart." + options: + days: "Days" + weeks: "Weeks" + months: "Months" + quarters: "Quarters" + column_width: + label: "Table column width" + options: + narrow: "Narrow" + medium: "Medium" + wide: "Wide" + very_wide: "Very wide" + paper_size: + label: "Paper size" + caption: "Depending on the chart size more than one page might be exported." + + xls: + show_relations: + label: "Show relations" + caption: "This option will create a duplicate of each work package for every relation this has with another work package." your_work_packages_export: "Your work packages export" succeeded: "The export has completed successfully." failed: "The export has failed: %{message}" diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 94c578f00202..37036cd34266 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -378,19 +378,6 @@ en: description: > Select the initial zoom level that should be shown when autozoom is not available. - export: - title: "Gantt chart PDF options" - button_export: "Export" - column_widths: - narrow: "Narrow" - medium: "Medium" - wide: "Wide" - very_wide: "Very wide" - options: - date_zoom: "Date zoom" - paper_size: "Paper size" - column_widths: "Column widths" - general_text_no: "no" general_text_yes: "yes" general_text_No: "No" diff --git a/frontend/src/app/core/turbo/turbo-requests.service.ts b/frontend/src/app/core/turbo/turbo-requests.service.ts index 41532ce7c832..30d83c7dda9f 100644 --- a/frontend/src/app/core/turbo/turbo-requests.service.ts +++ b/frontend/src/app/core/turbo/turbo-requests.service.ts @@ -10,8 +10,8 @@ export class TurboRequestsService { } - public request(url:string):Promise { - return fetch(url) + public request(url:string, init:RequestInit = {}):Promise { + return fetch(url, init) .then((response) => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); @@ -22,4 +22,12 @@ export class TurboRequestsService { .then((html) => renderStreamMessage(html)) .catch((error) => this.toast.addError(error as string)); } + + public requestStream(url:string):Promise { + return this.request(url, { + method: 'GET', + headers: { Accept: 'text/vnd.turbo-stream.html' }, + credentials: 'same-origin', + }); + } } diff --git a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.html b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.html index 8eed57bdddca..3b7ab4fe5542 100644 --- a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.html +++ b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.html @@ -9,7 +9,7 @@
-
    +
    -
    -
    -
    -

    {{text.ganttOptionSectionTitle}}

    -
    - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    -
- -
- -
-
-
-
- diff --git a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.sass b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.sass deleted file mode 100644 index 5b5d49be610a..000000000000 --- a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.sass +++ /dev/null @@ -1,14 +0,0 @@ -.op-wp-table-export - #download-link - display: none - - .loading-indicator--location - height: 220px - - .export-options - &.-hidden - display: none - - .gantt-export-options - display: flex - justify-content: center diff --git a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts b/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts deleted file mode 100644 index ad07e283cdae..000000000000 --- a/frontend/src/app/shared/components/modals/export-modal/wp-table-export.modal.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Inject, OnInit, -} from '@angular/core'; -import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types'; -import { OpModalComponent } from 'core-app/shared/components/modal/modal.component'; -import { - WorkPackageViewColumnsService, -} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service'; -import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource'; -import { HalLink } from 'core-app/features/hal/hal-link/hal-link'; -import { I18nService } from 'core-app/core/i18n/i18n.service'; -import * as URI from 'urijs'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { LoadingIndicatorService } from 'core-app/core/loading-indicator/loading-indicator.service'; -import { ToastService } from 'core-app/shared/components/toaster/toast.service'; -import { JobStatusModalComponent } from 'core-app/features/job-status/job-status-modal/job-status.modal'; -import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; -import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service'; -import { QueryResource } from 'core-app/features/hal/resources/query-resource'; -import { StaticQueriesService } from 'core-app/shared/components/op-view-select/op-static-queries.service'; -import isPersistedResource from 'core-app/features/hal/helpers/is-persisted-resource'; - -interface ExportLink extends HalLink { - identifier:string; -} - -interface ExportOptions { - identifier:string; - label:string; - url:string; -} - -/** - Modal for exporting work packages to different formats. The user may choose from a variety of formats (e.g. PDF and CSV). - The modal might also be used to only display the progress of an export. This will happen if a link for exporting is provided via the locals. - */ -@Component({ - templateUrl: './wp-table-export.modal.html', - styleUrls: ['./wp-table-export.modal.sass'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class WpTableExportModalComponent extends OpModalComponent implements OnInit { - public $element:HTMLElement; - - public exportOptions:ExportOptions[]; - - public text = { - title: this.I18n.t('js.label_export'), - closePopup: this.I18n.t('js.close_popup_title'), - exportPreparing: this.I18n.t('js.label_export_preparing'), - cancelButton: this.I18n.t('js.button_cancel'), - backButton: this.I18n.t('js.button_back'), - }; - - constructor( - @Inject(OpModalLocalsToken) public locals:OpModalLocalsMap, - readonly I18n:I18nService, - readonly elementRef:ElementRef, - readonly querySpace:IsolatedQuerySpace, - readonly cdRef:ChangeDetectorRef, - readonly httpClient:HttpClient, - readonly wpTableColumns:WorkPackageViewColumnsService, - readonly opStaticQueries:StaticQueriesService, - readonly loadingIndicator:LoadingIndicatorService, - readonly toastService:ToastService, - ) { - super(locals, cdRef, elementRef); - } - - ngOnInit():void { - super.ngOnInit(); - - if (this.locals.link) { - this.requestExport(this.locals.link); - } else { - void this.querySpace.results - .valuesPromise() - .then((results:WorkPackageCollectionResource) => { - this.exportOptions = this.buildExportOptions(results); - this.cdRef.detectChanges(); - }); - } - } - - private buildExportOptions(results:WorkPackageCollectionResource) { - let options = results.representations.map((format) => { - const link = format.$link as ExportLink; - - return { - identifier: link.identifier, - label: link.title, - url: this.addColumnsAndTitleToHref(format.href as string), - }; - }); - return options; - } - - triggerByOption(option:ExportOptions, event:MouseEvent):void { - event.preventDefault(); - this.requestExport(option.url); - } - - /** - * Request the export link and return the job ID to observe - * - * @param url - */ - private requestExport(url:string):void { - this - .httpClient - .get(url, { observe: 'body', responseType: 'json' }) - .subscribe( - (json:{ job_id:string }) => this.replaceWithJobModal(json.job_id), - (error) => this.handleError(error), - ); - } - - private replaceWithJobModal(jobId:string) { - this.service.show(JobStatusModalComponent, 'global', { jobId }); - } - - private handleError(error:HttpErrorResponse) { - // There was an error but the status code is actually a 200. - // If that is the case the response's content-type probably does not match - // the expected type (json). - // Currently this happens e.g. when exporting Atom which actually is not an export - // but rather a feed to follow. - if (error.status === 200 && error.url) { - window.open(error.url); - } else { - this.showError(error); - } - } - - private showError(error:HttpErrorResponse) { - this.toastService.addError(error.message || this.I18n.t('js.error.internal')); - } - - private addColumnsAndTitleToHref(href:string) { - const columns = this.wpTableColumns.getColumns(); - - const columnIds = columns.map((column) => column.id); - - const url = URI(href); - // Remove current columns - url.removeSearch('columns[]'); - url.addSearch('columns[]', columnIds); - - // Add the query title for the export - const query = this.querySpace.query.value; - if (query) { - url.removeSearch('title'); - url.addSearch('title', this.queryTitle(query)); - } - - return url.toString(); - } - - private queryTitle(query:QueryResource):string { - return isPersistedResource(query) ? query.name : this.staticQueryName(query); - } - - protected staticQueryName(query:QueryResource):string { - return this.opStaticQueries.getStaticName(query); - } - - protected get afterFocusOn():HTMLElement { - return document.getElementById('work-packages-settings-button') as HTMLElement; - } -} From c8eb1bac0fbf22bd808784e6e9f518e923b642dc Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 11:03:51 +0200 Subject: [PATCH 23/80] obey eslint --- .../toolbar/import-export-bcf/bcf-export-button.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts index 4cb366f7ea6b..ffeeadae7ad2 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts @@ -107,9 +107,10 @@ export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnI .get(url, { observe: 'body', responseType: 'json' }) .subscribe( (json:{ job_id:string }) => this.showJobModal(json.job_id), - (error) => this.handleError(error), + (error:HttpErrorResponse) => this.handleError(error), ); } + private showJobModal(jobId:string) { this.opModalService.show(JobStatusModalComponent, this.injector, { jobId }); } From 4e12b4792c1c7ecf403e6ec0f64f8d7bba475d27 Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 11:05:07 +0200 Subject: [PATCH 24/80] remove unused code --- .../bcf-export-button.component.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts index ffeeadae7ad2..c98f6e21d9ac 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/import-export-bcf/bcf-export-button.component.ts @@ -116,19 +116,6 @@ export class BcfExportButtonComponent extends UntilDestroyedMixin implements OnI } private handleError(error:HttpErrorResponse) { - // There was an error but the status code is actually a 200. - // If that is the case the response's content-type probably does not match - // the expected type (json). - // Currently this happens e.g. when exporting Atom which actually is not an export - // but rather a feed to follow. - if (error.status === 200 && error.url) { - window.open(error.url); - } else { - this.showError(error); - } - } - - private showError(error:HttpErrorResponse) { this.toastService.addError(error.message || this.I18n.t('js.error.internal')); } } From e3eb6d99ba2f1aebe1332e55334b47b7479b8658 Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 12:17:09 +0200 Subject: [PATCH 25/80] set paddings from figma --- .../exports/modal_dialog_component.html.erb | 4 +- .../pdf/export_settings_component.html.erb | 36 +++++++------ .../xls/export_settings_component.html.erb | 53 +++++++++---------- config/locales/en.yml | 4 +- spec/features/work_packages/export_spec.rb | 4 +- 5 files changed, 52 insertions(+), 49 deletions(-) diff --git a/app/components/work_packages/exports/modal_dialog_component.html.erb b/app/components/work_packages/exports/modal_dialog_component.html.erb index 564b3bda15e9..a450f961a1b6 100644 --- a/app/components/work_packages/exports/modal_dialog_component.html.erb +++ b/app/components/work_packages/exports/modal_dialog_component.html.erb @@ -12,7 +12,7 @@ <% flex_layout do |modal_body| %> <% modal_body.with_row do |_format| %> <%= render(Primer::Beta::Text.new(tag: "legend", font_size: :normal, mb: 2, font_weight: :bold)) { I18n.t('export.dialog.format.label') } %> - <%= render(Primer::Alpha::SegmentedControl.new(ml: 0, mb: 2, "aria-label": I18n.t('export.dialog.format.label'), size: :medium)) do |component| + <%= render(Primer::Alpha::SegmentedControl.new(ml: 0, "aria-label": I18n.t('export.dialog.format.label'), size: :medium)) do |component| export_formats_settings.each do |format| component.with_item( label: format[:label], icon: format[:icon], selected: format[:selected], @@ -22,7 +22,7 @@ }) end end %> -
+
<% end %> <% export_formats_settings.each do |format| %> <% modal_body.with_row( diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index afead92c7d7d..75751b0bfa4e 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -22,11 +22,10 @@ caption: entry[:caption]) end end %> -
+
<% end %> <% container.with_row( - mt: 2, classes: %w[table report].include?(current_pdf_export_type) ? nil : "d-none", data: { table: true, @@ -54,7 +53,7 @@ report: true, "work-packages--export--pdf--settings-target": "fields" }) do |_pdf_report_images| %> -
+
<%= render(Primer::Alpha::CheckBox.new(name: 'show_images', checked: true, value: "true", @@ -71,19 +70,24 @@ "work-packages--export--pdf--settings-target": "fields" }) do |_pdf_gantt_paper_size| %> - <% gantt_selects.each do |entry| %> - <%= render(Primer::Alpha::Select.new( - name: entry[:name], - label: entry[:label], - caption: entry[:caption], - size: :medium, - disabled: current_pdf_export_type != "gantt", - value: entry[:options].find { |e| e[:default] }[:value]) - ) do |component| - entry[:options].each do |entry| - component.option(label: entry[:label], value: entry[:value]) - end - end %> + <% flex_layout do |flex| %> + <% gantt_selects.each_with_index do |entry, index| %> + <% flex.with_row(mt: index == 0 ? 0 : 3) do %> + <%= render(Primer::Alpha::Select.new( + name: entry[:name], + label: entry[:label], + caption: entry[:caption], + size: :medium, + input_width: :small, + disabled: current_pdf_export_type != "gantt", + value: entry[:options].find { |e| e[:default] }[:value]) + ) do |component| + entry[:options].each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <% end %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/components/work_packages/exports/xls/export_settings_component.html.erb b/app/components/work_packages/exports/xls/export_settings_component.html.erb index 8ee10ec781c1..6e0470b7ddbe 100644 --- a/app/components/work_packages/exports/xls/export_settings_component.html.erb +++ b/app/components/work_packages/exports/xls/export_settings_component.html.erb @@ -1,27 +1,26 @@ -<%= - flex_layout do |container| - container.with_row(mb: 2) do |_columns| - helpers.angular_component_tag "opce-draggable-autocompleter", - inputs: { - options: work_packages_columns_options, - selected: selected_columns, - protected: protected_work_packages_columns_options, - name: 'columns', - id: 'columns-select-export-xls', - inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), - inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), - dragAreaName: 'columns-select-export-xls', - dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), - appendToComponent: true - } - end - container.with_row do |_xls_include_relations| - render(Primer::Alpha::CheckBox.new(name: 'show_relations', - value: "true", - unchecked_value: "false", - label: I18n.t("export.dialog.xls.show_relations.label"), - caption: I18n.t("export.dialog.xls.show_relations.caption"), - visually_hide_label: false)) - end - end -%> +<%= flex_layout do |container| %> + <%= container.with_row do |_columns| %> + <% helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: work_packages_columns_options, + selected: selected_columns, + protected: protected_work_packages_columns_options, + name: 'columns', + id: 'columns-select-export-xls', + inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), + dragAreaName: 'columns-select-export-xls', + dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } %> + <% end %> + <%= container.with_row do |_xls_include_relations| %> +
+ <%= render(Primer::Alpha::CheckBox.new(name: 'show_relations', + value: "true", + unchecked_value: "false", + label: I18n.t("export.dialog.xls.include_relations.label"), + caption: I18n.t("export.dialog.xls.include_relations.caption"), + visually_hide_label: false)) %> + <% end %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 8d58272d2032..1ed81d2445a9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1796,8 +1796,8 @@ en: caption: "Depending on the chart size more than one page might be exported." xls: - show_relations: - label: "Show relations" + include_relations: + label: "Include relations" caption: "This option will create a duplicate of each work package for every relation this has with another work package." your_work_packages_export: "Your work packages export" succeeded: "The export has completed successfully." diff --git a/spec/features/work_packages/export_spec.rb b/spec/features/work_packages/export_spec.rb index 074949e3d81e..995697096dbe 100644 --- a/spec/features/work_packages/export_spec.rb +++ b/spec/features/work_packages/export_spec.rb @@ -158,14 +158,14 @@ def export! context "with relations" do it "exports a xls" do - check I18n.t("export.dialog.xls.show_relations.label") + check I18n.t("export.dialog.xls.include_relations.label") export! end end context "without relations" do it "exports a xls" do - uncheck I18n.t("export.dialog.xls.show_relations.label") + uncheck I18n.t("export.dialog.xls.include_relations.label") export! end end From eb4c4370c39d69bdc39c3c9a36f09bdc617c5776 Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 14:31:28 +0200 Subject: [PATCH 26/80] Enterprise diamond icon in disabled gantt option (workaround for not supported in Primer) --- app/components/_index.sass | 1 + .../exports/pdf/export_settings_component.html.erb | 4 ++++ .../exports/pdf/export_settings_component.sass | 6 ++++++ 3 files changed, 11 insertions(+) create mode 100644 app/components/work_packages/exports/pdf/export_settings_component.sass diff --git a/app/components/_index.sass b/app/components/_index.sass index 2f1db2aa1ed7..46d99c2ac065 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -9,3 +9,4 @@ @import "projects/row_component" @import "settings/project_custom_fields/project_custom_field_mapping/new_project_mapping_component" @import "op_primer/border_box_table_component" +@import "work_packages/exports/pdf/export_settings_component" diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index 75751b0bfa4e..f40d1895f34a 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -15,6 +15,10 @@ value: entry[:value], checked: current_pdf_export_type == entry[:value], disabled: entry[:disabled] ? true : nil, + label_arguments: entry[:disabled] ? { + class: "radio-button-label-enterprise-addons-icon", + title: I18n.t(:label_enterprise_edition) + } : nil, data: { "work-packages--export--pdf--settings-name-param": entry[:value], action: "work-packages--export--pdf--settings#typeChanged" diff --git a/app/components/work_packages/exports/pdf/export_settings_component.sass b/app/components/work_packages/exports/pdf/export_settings_component.sass new file mode 100644 index 000000000000..79a8d6e55108 --- /dev/null +++ b/app/components/work_packages/exports/pdf/export_settings_component.sass @@ -0,0 +1,6 @@ +.radio-button-label-enterprise-addons-icon::after + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23ef9e56' d='M16 8c0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8 8 3.582 8 8Zm-8.191 5.854c.064.195.312.195.376 0l1.948-6.081c.044-.138-.051-.291-.191-.291H6.045c-.14 0-.235.146-.191.291h.007Zm-1.35-.285c.14.188.414.021.344-.208L4.982 7.634c-.025-.09-.102-.145-.191-.145h-2.54c-.172 0-.261.215-.153.354Zm2.738-.215c-.07.229.197.396.344.208l4.361-5.726c.108-.139.013-.354-.153-.354h-2.54c-.083 0-.159.055-.191.146Zm.165-9.208c-.031-.083-.101-.139-.184-.139H6.822c-.083 0-.159.056-.185.139l-.764 2.175c-.05.139.045.292.185.292h3.884c.14 0 .235-.153.185-.292Zm-4.38 2.328.764-2.175c.051-.139-.045-.292-.185-.292H3.62c-.121 0-.242.09-.319.174L2.034 6.28c-.089.145.007.34.166.34h2.597c.083 0 .16-.056.185-.139Zm8.984-.202-1.267-2.098C12.616 4.083 12.501 4 12.38 4h-1.941c-.14 0-.236.153-.185.292l.764 2.175c.032.083.102.139.185.139H13.8v.007c.159 0 .255-.195.166-.341Z'/%3E%3C/svg%3E") + width: 16px + height: 16px + display: inline-block + vertical-align: text-top From 1025711cc620783a0b860444d42af722a0dc7e45 Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 16:37:12 +0200 Subject: [PATCH 27/80] add optional inputCaption below autocompleter input --- .../draggable-autocomplete.component.html | 3 +++ .../draggable-autocomplete/draggable-autocomplete.component.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html index a23de80cdc4c..2184b41224f6 100644 --- a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html +++ b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html @@ -21,6 +21,9 @@ (open)="opened()" (change)="select($event)" > + diff --git a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts index 35d0afcea989..1986701ae171 100644 --- a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts +++ b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts @@ -58,6 +58,9 @@ export class DraggableAutocompleteComponent extends UntilDestroyedMixin implemen /** Placeholder text to display in the autocompleter input */ @Input() inputPlaceholder = ''; + /** Label to display below the autocompleter input */ + @Input() inputCaption = ''; + /** Label to display drag&drop area */ @Input() dragAreaLabel = ''; From d9ba709df99c778d3f2b6597596529af020a1510 Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 16:37:33 +0200 Subject: [PATCH 28/80] split export dialog pdf settings to enable independent values --- .../exports/base_export_settings_component.rb | 8 +- .../column_selection_component.html.erb | 15 +++ .../exports/column_selection_component.rb | 57 ++++++++++++ .../csv/export_settings_component.html.erb | 24 +---- .../exports/csv/export_settings_component.rb | 2 +- .../exports/modal_dialog_component.rb | 2 +- .../pdf/export_settings_component.html.erb | 74 ++------------- .../exports/pdf/export_settings_component.rb | 71 ++------------ .../gantt/export_settings_component.html.erb | 18 ++++ .../pdf/gantt/export_settings_component.rb | 93 +++++++++++++++++++ .../report/export_settings_component.html.erb | 19 ++++ .../pdf/report/export_settings_component.rb | 40 ++++++++ .../table/export_settings_component.html.erb | 5 + .../pdf/table/export_settings_component.rb | 40 ++++++++ .../xls/export_settings_component.html.erb | 20 ++-- .../exports/xls/export_settings_component.rb | 2 +- config/locales/en.yml | 3 + .../op-settings-dropdown-menu.directive.ts | 1 - .../export/pdf/settings.controller.ts | 10 +- 19 files changed, 329 insertions(+), 175 deletions(-) create mode 100644 app/components/work_packages/exports/column_selection_component.html.erb create mode 100644 app/components/work_packages/exports/column_selection_component.rb create mode 100644 app/components/work_packages/exports/pdf/gantt/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/pdf/gantt/export_settings_component.rb create mode 100644 app/components/work_packages/exports/pdf/report/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/pdf/report/export_settings_component.rb create mode 100644 app/components/work_packages/exports/pdf/table/export_settings_component.html.erb create mode 100644 app/components/work_packages/exports/pdf/table/export_settings_component.rb diff --git a/app/components/work_packages/exports/base_export_settings_component.rb b/app/components/work_packages/exports/base_export_settings_component.rb index 5c6bafd12b14..16fdbc293dda 100644 --- a/app/components/work_packages/exports/base_export_settings_component.rb +++ b/app/components/work_packages/exports/base_export_settings_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2024 the OpenProject GmbH +# 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. @@ -42,12 +42,6 @@ def initialize(query) @query = query end - - def selected_columns - query - .columns - .map { |s| { id: s.name, name: s.caption } } - end end end end diff --git a/app/components/work_packages/exports/column_selection_component.html.erb b/app/components/work_packages/exports/column_selection_component.html.erb new file mode 100644 index 000000000000..0df57ba154f7 --- /dev/null +++ b/app/components/work_packages/exports/column_selection_component.html.erb @@ -0,0 +1,15 @@ +<%= helpers.angular_component_tag "opce-draggable-autocompleter", + inputs: { + options: available_columns, + selected: selected_columns, + protected: protected_work_packages_columns_options, + name: 'columns', + id:, + dragAreaName: id, + inputCaption: caption, + inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), + inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), + dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), + appendToComponent: true + } +%> diff --git a/app/components/work_packages/exports/column_selection_component.rb b/app/components/work_packages/exports/column_selection_component.rb new file mode 100644 index 000000000000..c70fa45b699f --- /dev/null +++ b/app/components/work_packages/exports/column_selection_component.rb @@ -0,0 +1,57 @@ +# 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 WorkPackages + module Exports + class ColumnSelectionComponent < ApplicationComponent + include WorkPackagesHelper + + attr_reader :query, :id, :caption + + def initialize(query, id, caption) + super() + + @query = query + @id = id + @caption = caption + end + + def available_columns + work_packages_columns_options + end + + def selected_columns + query + .columns + .map { |s| { id: s.name, name: s.caption } } + end + end + end +end diff --git a/app/components/work_packages/exports/csv/export_settings_component.html.erb b/app/components/work_packages/exports/csv/export_settings_component.html.erb index 1646d40d2e5f..4cc6a176a260 100644 --- a/app/components/work_packages/exports/csv/export_settings_component.html.erb +++ b/app/components/work_packages/exports/csv/export_settings_component.html.erb @@ -1,19 +1,5 @@ -<%= - flex_layout do |container| - container.with_row do |_columns| - helpers.angular_component_tag "opce-draggable-autocompleter", - inputs: { - options: work_packages_columns_options, - selected: selected_columns, - protected: protected_work_packages_columns_options, - name: 'columns', - id: 'columns-select-export-csv', - dragAreaName: 'columns-select-export-csv', - inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), - inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), - dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), - appendToComponent: true - } - end - end -%> +<%= render WorkPackages::Exports::ColumnSelectionComponent.new( + query, + "columns-select-export-csv", + I18n.t("export.dialog.columns.input_caption_table") +) %> diff --git a/app/components/work_packages/exports/csv/export_settings_component.rb b/app/components/work_packages/exports/csv/export_settings_component.rb index f44744540390..1ca563e7dc1e 100644 --- a/app/components/work_packages/exports/csv/export_settings_component.rb +++ b/app/components/work_packages/exports/csv/export_settings_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2024 the OpenProject GmbH +# 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. diff --git a/app/components/work_packages/exports/modal_dialog_component.rb b/app/components/work_packages/exports/modal_dialog_component.rb index f696a7151a7c..45255b25993a 100644 --- a/app/components/work_packages/exports/modal_dialog_component.rb +++ b/app/components/work_packages/exports/modal_dialog_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2024 the OpenProject GmbH +# 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. diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index f40d1895f34a..3606adc76aab 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -3,7 +3,6 @@ "application-target": "dynamic", controller: "work-packages--export--pdf--settings" }) do |container| %> - <%= container.with_row do %> <%= render(Primer::Alpha::RadioButtonGroup.new( mb: 3, @@ -28,71 +27,14 @@ end %>
<% end %> - - <% container.with_row( - classes: %w[table report].include?(current_pdf_export_type) ? nil : "d-none", - data: { - table: true, - report: true, - "work-packages--export--pdf--settings-target": "fields" - }) do |_columns| - helpers.angular_component_tag "opce-draggable-autocompleter", - inputs: { - options: work_packages_columns_options, - selected: selected_columns, - protected: protected_work_packages_columns_options, - name: 'columns', - id: 'columns-select-export-pdf', - inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), - inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), - dragAreaName: 'columns-select-export-pdf', - dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), - appendToComponent: true - } - end %> - - <%= container.with_row( - classes: current_pdf_export_type == "report" ? nil : "d-none", - data: { - report: true, - "work-packages--export--pdf--settings-target": "fields" - }) do |_pdf_report_images| %> -
- <%= render(Primer::Alpha::CheckBox.new(name: 'show_images', - checked: true, - value: "true", - unchecked_value: "false", - label: I18n.t("export.dialog.pdf.include_images.label"), - caption: I18n.t("export.dialog.pdf.include_images.caption"), - disabled: current_pdf_export_type != "report", - visually_hide_label: false)) %> - <% end %> - <% if gantt_chart_allowed? %> - <%= container.with_row(classes: current_pdf_export_type == "gantt" ? nil : "d-none", - data: { - gantt: true, - "work-packages--export--pdf--settings-target": "fields" - }) do |_pdf_gantt_paper_size| %> - - <% flex_layout do |flex| %> - <% gantt_selects.each_with_index do |entry, index| %> - <% flex.with_row(mt: index == 0 ? 0 : 3) do %> - <%= render(Primer::Alpha::Select.new( - name: entry[:name], - label: entry[:label], - caption: entry[:caption], - size: :medium, - input_width: :small, - disabled: current_pdf_export_type != "gantt", - value: entry[:options].find { |e| e[:default] }[:value]) - ) do |component| - entry[:options].each do |entry| - component.option(label: entry[:label], value: entry[:value]) - end - end %> - <% end %> - <% end %> - <% end %> + <% pdf_export_types.reject { |entry| entry[:disabled] }.each do |entry| %> + <%= container.with_row( + classes: current_pdf_export_type == entry[:value] ? nil : "d-none", + data: { + "pdf-export-type": entry[:value], + "work-packages--export--pdf--settings-target": "fields" + }) do |_pdf_export_type| %> + <%= render(entry[:component].new(query)) %> <% end %> <% end %> <% end %> diff --git a/app/components/work_packages/exports/pdf/export_settings_component.rb b/app/components/work_packages/exports/pdf/export_settings_component.rb index 093c39671352..daa4f6afaf3f 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.rb +++ b/app/components/work_packages/exports/pdf/export_settings_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2024 the OpenProject GmbH +# 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. @@ -44,74 +44,17 @@ def pdf_export_types [ { value: "table", label: I18n.t("export.dialog.pdf.export_type.options.table.label"), - caption: I18n.t("export.dialog.pdf.export_type.options.table.caption") }, + caption: I18n.t("export.dialog.pdf.export_type.options.table.caption"), + component: WorkPackages::Exports::PDF::Table::ExportSettingsComponent }, { value: "report", label: I18n.t("export.dialog.pdf.export_type.options.report.label"), - caption: I18n.t("export.dialog.pdf.export_type.options.report.caption") }, + caption: I18n.t("export.dialog.pdf.export_type.options.report.caption"), + component: WorkPackages::Exports::PDF::Report::ExportSettingsComponent }, { value: "gantt", label: I18n.t("export.dialog.pdf.export_type.options.gantt.label"), caption: I18n.t("export.dialog.pdf.export_type.options.gantt.caption"), - disabled: !gantt_chart_allowed? } - ] - end - - def selected_columns - query - .columns - .map { |s| { id: s.name, name: s.caption } } - end - - def gantt_selects - [ - { - name: "gantt_mode", - label: I18n.t("export.dialog.pdf.gantt_zoom_levels.label"), - caption: I18n.t("export.dialog.pdf.gantt_zoom_levels.caption"), - options: gantt_zoom_levels - }, - { - name: "gantt_width", - label: I18n.t("export.dialog.pdf.column_width.label"), - options: gantt_column_widths - }, - { - name: "paper_size", - label: I18n.t("export.dialog.pdf.paper_size.label"), - caption: I18n.t("export.dialog.pdf.paper_size.caption"), - options: pdf_paper_sizes - } - ] - end - - def gantt_zoom_levels - [ - { label: t("export.dialog.pdf.gantt_zoom_levels.options.days"), value: "day", default: true }, - { label: t("export.dialog.pdf.gantt_zoom_levels.options.weeks"), value: "week" }, - { label: t("export.dialog.pdf.gantt_zoom_levels.options.months"), value: "month" }, - { label: t("export.dialog.pdf.gantt_zoom_levels.options.quarters"), value: "quarter" } - ] - end - - def gantt_column_widths - [ - { label: t("export.dialog.pdf.column_width.options.narrow"), value: "narrow" }, - { label: t("export.dialog.pdf.column_width.options.medium"), value: "medium", default: true }, - { label: t("export.dialog.pdf.column_width.options.wide"), value: "wide" }, - { label: t("export.dialog.pdf.column_width.options.very_wide"), value: "very_wide" } - ] - end - - def pdf_paper_sizes - [ - { label: "A4", value: "A4", default: true }, - { label: "A3", value: "A3" }, - { label: "A2", value: "A2" }, - { label: "A1", value: "A1" }, - { label: "A0", value: "A0" }, - { label: "Executive", value: "EXECUTIVE" }, - { label: "Folio", value: "FOLIO" }, - { label: "Letter", value: "LETTER" }, - { label: "Tabloid", value: "TABLOID" } + disabled: !gantt_chart_allowed?, + component: WorkPackages::Exports::PDF::Gantt::ExportSettingsComponent } ] end end diff --git a/app/components/work_packages/exports/pdf/gantt/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/gantt/export_settings_component.html.erb new file mode 100644 index 000000000000..3f281ca3bb55 --- /dev/null +++ b/app/components/work_packages/exports/pdf/gantt/export_settings_component.html.erb @@ -0,0 +1,18 @@ +<%= flex_layout do |container| %> + <% gantt_selects.each_with_index do |entry, index| %> + <% container.with_row(mt: index == 0 ? 0 : 3) do %> + <%= render(Primer::Alpha::Select.new( + name: entry[:name], + label: entry[:label], + caption: entry[:caption], + size: :medium, + input_width: :small, + value: entry[:options].find { |e| e[:default] }[:value]) + ) do |component| + entry[:options].each do |entry| + component.option(label: entry[:label], value: entry[:value]) + end + end %> + <% end %> + <% end %> +<% end %> diff --git a/app/components/work_packages/exports/pdf/gantt/export_settings_component.rb b/app/components/work_packages/exports/pdf/gantt/export_settings_component.rb new file mode 100644 index 000000000000..3f1a2497d10e --- /dev/null +++ b/app/components/work_packages/exports/pdf/gantt/export_settings_component.rb @@ -0,0 +1,93 @@ +# 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 WorkPackages + module Exports + module PDF + module Gantt + class ExportSettingsComponent < BaseExportSettingsComponent + def gantt_selects + [ + { + name: "gantt_mode", + label: I18n.t("export.dialog.pdf.gantt_zoom_levels.label"), + caption: I18n.t("export.dialog.pdf.gantt_zoom_levels.caption"), + options: gantt_zoom_levels + }, + { + name: "gantt_width", + label: I18n.t("export.dialog.pdf.column_width.label"), + options: gantt_column_widths + }, + { + name: "paper_size", + label: I18n.t("export.dialog.pdf.paper_size.label"), + caption: I18n.t("export.dialog.pdf.paper_size.caption"), + options: pdf_paper_sizes + } + ] + end + + def gantt_zoom_levels + [ + { label: t("export.dialog.pdf.gantt_zoom_levels.options.days"), value: "day", default: true }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.weeks"), value: "week" }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.months"), value: "month" }, + { label: t("export.dialog.pdf.gantt_zoom_levels.options.quarters"), value: "quarter" } + ] + end + + def gantt_column_widths + [ + { label: t("export.dialog.pdf.column_width.options.narrow"), value: "narrow" }, + { label: t("export.dialog.pdf.column_width.options.medium"), value: "medium", default: true }, + { label: t("export.dialog.pdf.column_width.options.wide"), value: "wide" }, + { label: t("export.dialog.pdf.column_width.options.very_wide"), value: "very_wide" } + ] + end + + def pdf_paper_sizes + [ + { label: "A4", value: "A4", default: true }, + { label: "A3", value: "A3" }, + { label: "A2", value: "A2" }, + { label: "A1", value: "A1" }, + { label: "A0", value: "A0" }, + { label: "Executive", value: "EXECUTIVE" }, + { label: "Folio", value: "FOLIO" }, + { label: "Letter", value: "LETTER" }, + { label: "Tabloid", value: "TABLOID" } + ] + end + end + end + end + end +end diff --git a/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb new file mode 100644 index 000000000000..73aac3ac1660 --- /dev/null +++ b/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb @@ -0,0 +1,19 @@ +<%= flex_layout do |container| %> + <% container.with_row do |_columns| %> + <%= render WorkPackages::Exports::ColumnSelectionComponent.new( + query, + "columns-select-export-pdf-report", + I18n.t("export.dialog.columns.input_caption_report") + ) %> + <% end %> + <%= container.with_row do |_pdf_report_images| %> +
+ <%= render(Primer::Alpha::CheckBox.new(name: 'show_images', + checked: true, + value: "true", + unchecked_value: "false", + label: I18n.t("export.dialog.pdf.include_images.label"), + caption: I18n.t("export.dialog.pdf.include_images.caption"), + visually_hide_label: false)) %> + <% end %> +<% end %> diff --git a/app/components/work_packages/exports/pdf/report/export_settings_component.rb b/app/components/work_packages/exports/pdf/report/export_settings_component.rb new file mode 100644 index 000000000000..e3b4a5fe17cb --- /dev/null +++ b/app/components/work_packages/exports/pdf/report/export_settings_component.rb @@ -0,0 +1,40 @@ +# 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 WorkPackages + module Exports + module PDF + module Report + class ExportSettingsComponent < BaseExportSettingsComponent + end + end + end + end +end diff --git a/app/components/work_packages/exports/pdf/table/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/table/export_settings_component.html.erb new file mode 100644 index 000000000000..40cd43cd9eef --- /dev/null +++ b/app/components/work_packages/exports/pdf/table/export_settings_component.html.erb @@ -0,0 +1,5 @@ +<%= render WorkPackages::Exports::ColumnSelectionComponent.new( + query, + "columns-select-export-pdf-table", + I18n.t("export.dialog.columns.input_caption_table") +) %> diff --git a/app/components/work_packages/exports/pdf/table/export_settings_component.rb b/app/components/work_packages/exports/pdf/table/export_settings_component.rb new file mode 100644 index 000000000000..b8020bebacda --- /dev/null +++ b/app/components/work_packages/exports/pdf/table/export_settings_component.rb @@ -0,0 +1,40 @@ +# 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 WorkPackages + module Exports + module PDF + module Table + class ExportSettingsComponent < BaseExportSettingsComponent + end + end + end + end +end diff --git a/app/components/work_packages/exports/xls/export_settings_component.html.erb b/app/components/work_packages/exports/xls/export_settings_component.html.erb index 6e0470b7ddbe..32985b78ce5d 100644 --- a/app/components/work_packages/exports/xls/export_settings_component.html.erb +++ b/app/components/work_packages/exports/xls/export_settings_component.html.erb @@ -1,22 +1,14 @@ <%= flex_layout do |container| %> <%= container.with_row do |_columns| %> - <% helpers.angular_component_tag "opce-draggable-autocompleter", - inputs: { - options: work_packages_columns_options, - selected: selected_columns, - protected: protected_work_packages_columns_options, - name: 'columns', - id: 'columns-select-export-xls', - inputLabel: I18n.t(:"queries.configure_view.columns.input_label"), - inputPlaceholder: I18n.t(:"queries.configure_view.columns.input_placeholder"), - dragAreaName: 'columns-select-export-xls', - dragAreaLabel: I18n.t(:"queries.configure_view.columns.drag_area_label"), - appendToComponent: true - } %> + <%= render WorkPackages::Exports::ColumnSelectionComponent.new( + query, + "columns-select-export-xls", + I18n.t("export.dialog.columns.input_caption_table") + ) %> <% end %> <%= container.with_row do |_xls_include_relations| %>
- <%= render(Primer::Alpha::CheckBox.new(name: 'show_relations', + <%= render(Primer::Alpha::CheckBox.new(name: "show_relations", value: "true", unchecked_value: "false", label: I18n.t("export.dialog.xls.include_relations.label"), diff --git a/app/components/work_packages/exports/xls/export_settings_component.rb b/app/components/work_packages/exports/xls/export_settings_component.rb index 130c2b7a2393..eb42c2680541 100644 --- a/app/components/work_packages/exports/xls/export_settings_component.rb +++ b/app/components/work_packages/exports/xls/export_settings_component.rb @@ -2,7 +2,7 @@ # -- copyright # OpenProject is an open source project management software. -# Copyright (C) 2010-2024 the OpenProject GmbH +# 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. diff --git a/config/locales/en.yml b/config/locales/en.yml index 1ed81d2445a9..cbc0bf241581 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1760,6 +1760,9 @@ en: label: "PDF" xls: label: "XLS" + columns: + input_caption_report: "By default all attributes added as columns in the work package list are selected. Long text field will be displayed at the end of the attributes list." + input_caption_table: "By default all attributes added as columns in the work package list are selected. Long text fields are not available in table based exports." pdf: export_type: label: "PDF export type" diff --git a/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts b/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts index aff9cc93f654..d4be4c7d21aa 100644 --- a/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts +++ b/frontend/src/app/shared/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts @@ -334,7 +334,6 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger { icon: 'icon-export', onClick: ($event:JQuery.TriggeredEvent) => { if (this.allowWorkPackageAction($event, 'representations')) { - // old angular export form: this.opModalService.show(WpTableExportModalComponent, this.injector); const query = this.querySpace.query.value; if (query) { const href = this.buildExportDialogHref(query); diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts index fb22852b4584..d9abaa74aa2c 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts @@ -13,9 +13,17 @@ export default class PDFExportSettingsController extends Controller { }); } + connect() { + this.fieldsTargets.forEach((element:HTMLElement) => { + if (element.classList.contains('d-none')) { + this.silenceFormFields(element, true); + } + }); + } + typeChanged({ params: { name } }:{ params:{ name:string } }) { this.fieldsTargets.forEach((element:HTMLElement) => { - if (element.getAttribute(`data-${name}`) === 'true') { + if (element.getAttribute(`data-pdf-export-type`) === name) { element.classList.remove('d-none'); this.silenceFormFields(element, false); } else { From 7b723f61dad10225b1cf2ac2bdcf9b927dd003eb Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 13 Aug 2024 16:47:19 +0200 Subject: [PATCH 29/80] obey eslint --- .../dynamic/work-packages/export/pdf/settings.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts index d9abaa74aa2c..15b9347f4a86 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/pdf/settings.controller.ts @@ -23,7 +23,7 @@ export default class PDFExportSettingsController extends Controller { typeChanged({ params: { name } }:{ params:{ name:string } }) { this.fieldsTargets.forEach((element:HTMLElement) => { - if (element.getAttribute(`data-pdf-export-type`) === name) { + if (element.getAttribute('data-pdf-export-type') === name) { element.classList.remove('d-none'); this.silenceFormFields(element, false); } else { From d1477c702977a66b0b19f07f58d30f5c5a64c7f0 Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 14 Aug 2024 13:14:27 +0200 Subject: [PATCH 30/80] dom node ids configurable --- .../draggable-autocomplete.component.html | 2 +- .../draggable-autocomplete.component.sass | 2 +- .../draggable-autocomplete.component.ts | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html index 2184b41224f6..6819077bf4c4 100644 --- a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html +++ b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.html @@ -1,6 +1,6 @@
-
+
<% end %> - <% else %> + <% elsif job.present? %> <%= content_tag :div, "", data: { "job-status-polling-target": "finished" } %> <% end %> <%= render Primer::Beta::Blankslate.new do |component| From 14538edf2b9acb215758a0fe6558d586c02d50ae Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 21 Aug 2024 14:33:48 +0200 Subject: [PATCH 57/80] prioritize job.message --- .../app/components/job_status/dialog/body_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.rb b/modules/job_status/app/components/job_status/dialog/body_component.rb index 2879e333bd2f..d02f8cf98bb0 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.rb +++ b/modules/job_status/app/components/job_status/dialog/body_component.rb @@ -107,7 +107,7 @@ def title return I18n.t("job_status_dialog.errors") if has_error? - job.payload&.dig("title") || job.message || I18n.t("job_status_dialog.title") + job.message || job.payload&.dig("title") || I18n.t("job_status_dialog.title") end def message From fae553ac4a10bc5d3e5be73a3cbafe95a83d17bb Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 21 Aug 2024 15:36:06 +0200 Subject: [PATCH 58/80] do not open redirects in new tab --- .../app/components/job_status/dialog/body_component.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.html.erb b/modules/job_status/app/components/job_status/dialog/body_component.html.erb index 5d885812666e..3261d76c0674 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/body_component.html.erb @@ -25,7 +25,6 @@ if redirect_url.present? flex.with_row { render(Primer::Beta::Link.new( href: redirect_url, - target: "_blank", data: { "job-status-polling-target": job_errors.present? ? nil : "redirect" } From 0eeb5a9cf496abff17c9c870c95a74a7dd4ee451 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 12:21:48 +0200 Subject: [PATCH 59/80] show icon only without EEE token; resolves https://github.com/opf/openproject/pull/16364#discussion_r1726686630 --- .../work_packages/exports/pdf/export_settings_component.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/components/work_packages/exports/pdf/export_settings_component.rb b/app/components/work_packages/exports/pdf/export_settings_component.rb index 093dc5b41f3c..ab515abf3da1 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.rb +++ b/app/components/work_packages/exports/pdf/export_settings_component.rb @@ -48,6 +48,11 @@ def enterprise_icon )) end + def gantt_chart_label + label = I18n.t("export.dialog.pdf.export_type.options.gantt.label") + gantt_chart_allowed? ? label : (label + enterprise_icon).html_safe # rubocop:disable Rails/OutputSafety + end + def pdf_export_types [ { value: "table", @@ -59,7 +64,7 @@ def pdf_export_types caption: I18n.t("export.dialog.pdf.export_type.options.report.caption"), component: WorkPackages::Exports::PDF::Report::ExportSettingsComponent }, { value: "gantt", - label: (I18n.t("export.dialog.pdf.export_type.options.gantt.label") + enterprise_icon).html_safe, + label: gantt_chart_label, caption: I18n.t("export.dialog.pdf.export_type.options.gantt.caption"), disabled: !gantt_chart_allowed?, component: WorkPackages::Exports::PDF::Gantt::ExportSettingsComponent } From bc8e4c84ef3f3084e898e80b6a73c79eb1e898ca Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 12:28:13 +0200 Subject: [PATCH 60/80] show the specified icons, use color role constants resolves https://github.com/opf/openproject/pull/16364#discussion_r1726715373 --- .../app/components/job_status/dialog/body_component.html.erb | 2 +- .../app/components/job_status/dialog/body_component.rb | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.html.erb b/modules/job_status/app/components/job_status/dialog/body_component.html.erb index 3261d76c0674..a04000d23de9 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/body_component.html.erb @@ -10,7 +10,7 @@ <%= content_tag :div, "", data: { "job-status-polling-target": "finished" } %> <% end %> <%= render Primer::Beta::Blankslate.new do |component| - component.with_visual_icon(icon: icon[:icon], classes: icon[:classes], size: :medium) unless icon.nil? + component.with_visual_icon(icon: icon[:icon], color: icon[:color], size: :medium) unless icon.nil? component.with_heading(tag: :h2).with_content(title) component.with_description do flex_layout do |flex| diff --git a/modules/job_status/app/components/job_status/dialog/body_component.rb b/modules/job_status/app/components/job_status/dialog/body_component.rb index d02f8cf98bb0..7a3b4a17a13f 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.rb +++ b/modules/job_status/app/components/job_status/dialog/body_component.rb @@ -96,10 +96,9 @@ def mime_type_pdf? end def icon - return { icon: :alert, classes: "color-fg-danger" } if job.nil? - return { icon: :"x-circle", classes: "color-fg-danger" } if has_error? + return { icon: :"x-circle", color: :danger } if job.nil? || has_error? - { icon: :"issue-closed", classes: "color-fg-success" } if success_statuses.include?(job.status) + { icon: :"issue-closed", color: :success } if success_statuses.include?(job.status) end def title From c4c64139dd15e45bf7e4a5c9fcd97ec45b7e3b3e Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 12:30:29 +0200 Subject: [PATCH 61/80] sync text with Figma resolves https://github.com/opf/openproject/pull/16364#discussion_r1726701223 --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 0f98528ea856..4c4fb8eeda2c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1761,7 +1761,7 @@ en: xls: label: "XLS" columns: - input_caption_report: "By default all attributes added as columns in the work package list are selected. Long text field will be displayed at the end of the attributes list." + input_caption_report: "By default all attributes added as columns in the work package list are selected." input_caption_table: "By default all attributes added as columns in the work package list are selected. Long text fields are not available in table based exports." pdf: export_type: From d482c5b86f1a55fb36790321730bdbcc32f6f561 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 12:33:50 +0200 Subject: [PATCH 62/80] remove not needed and not working spacing; resolves https://github.com/opf/openproject/pull/16364#discussion_r1726683639 --- .../work_packages/exports/pdf/export_settings_component.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index e284cac531c4..2c0111039450 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -5,7 +5,6 @@ }) do |container| %> <%= container.with_row do %> <%= render(Primer::Alpha::RadioButtonGroup.new( - mb: 3, full_width: true, name: "pdf_export_type", label: I18n.t("export.dialog.pdf.export_type.label"))) do |component| From 8e7a9ceea5bec6e848d83a452a5017e88d4e9d95 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 12:56:46 +0200 Subject: [PATCH 63/80] add OpenProject::Common::DividerComponent; resolves https://github.com/opf/openproject/pull/16364#discussion_r1726681642 --- .../open_project/common/divider_component.rb | 42 +++++++++++++++++++ .../exports/modal_dialog_component.html.erb | 2 +- .../pdf/export_settings_component.html.erb | 2 +- .../report/export_settings_component.html.erb | 4 +- .../xls/export_settings_component.html.erb | 4 +- .../open_project/common/divider_preview.rb | 21 ++++++++++ 6 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 app/components/open_project/common/divider_component.rb create mode 100644 lookbook/previews/open_project/common/divider_preview.rb diff --git a/app/components/open_project/common/divider_component.rb b/app/components/open_project/common/divider_component.rb new file mode 100644 index 000000000000..ef672e7ad151 --- /dev/null +++ b/app/components/open_project/common/divider_component.rb @@ -0,0 +1,42 @@ +# 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 OpenProject + module Common + class DividerComponent < Primer::BaseComponent + def initialize(**system_arguments) + system_arguments[:tag] = :hr + system_arguments[:mt] = system_arguments.fetch(:mt, 4) + system_arguments[:mb] = system_arguments.fetch(:mb, 4) + super(**system_arguments) # rubocop:disable Style/SuperArguments + end + end + end +end diff --git a/app/components/work_packages/exports/modal_dialog_component.html.erb b/app/components/work_packages/exports/modal_dialog_component.html.erb index 986c2a64e3a9..ed2ca1c1cf7e 100644 --- a/app/components/work_packages/exports/modal_dialog_component.html.erb +++ b/app/components/work_packages/exports/modal_dialog_component.html.erb @@ -22,7 +22,7 @@ }) end end %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 4, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new %> <% end %> <% export_formats_settings.each do |format| %> <% modal_body.with_row( diff --git a/app/components/work_packages/exports/pdf/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/export_settings_component.html.erb index 2c0111039450..907171a030d8 100644 --- a/app/components/work_packages/exports/pdf/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/export_settings_component.html.erb @@ -20,7 +20,7 @@ caption: entry[:caption]) end end %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 4, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new %> <% end %> <% pdf_export_types.reject { |entry| entry[:disabled] }.each do |entry| %> <%= container.with_row( diff --git a/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb b/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb index a1ae7f6f70fa..b35226de16a8 100644 --- a/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb +++ b/app/components/work_packages/exports/pdf/report/export_settings_component.html.erb @@ -7,7 +7,7 @@ ) %> <% end %> <% container.with_row do |_columns| %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 2, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new(mt: 2) %> <%= helpers.angular_component_tag "opce-draggable-autocompleter", inputs: { id: "ltf-select-export-pdf-report", @@ -26,7 +26,7 @@ %> <% end %> <%= container.with_row do |_pdf_report_images| %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 2, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new(mt: 2) %> <%= render(Primer::Alpha::CheckBox.new(name: 'show_images', checked: true, value: "true", diff --git a/app/components/work_packages/exports/xls/export_settings_component.html.erb b/app/components/work_packages/exports/xls/export_settings_component.html.erb index 6e1d759f123a..0465d0db5488 100644 --- a/app/components/work_packages/exports/xls/export_settings_component.html.erb +++ b/app/components/work_packages/exports/xls/export_settings_component.html.erb @@ -7,7 +7,7 @@ ) %> <% end %> <%= container.with_row do |_xls_include_relations| %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 2, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new(mt: 2) %> <%= render(Primer::Alpha::CheckBox.new(name: "show_relations", value: "true", unchecked_value: "false", @@ -16,7 +16,7 @@ visually_hide_label: false)) %> <% end %> <%= container.with_row do |_xls_include_description| %> - <%= render Primer::BaseComponent.new(tag: :hr, mt: 4, mb: 4) %> + <%= render OpenProject::Common::DividerComponent.new %> <%= render(Primer::Alpha::CheckBox.new(name: "show_descriptions", value: "true", unchecked_value: "false", diff --git a/lookbook/previews/open_project/common/divider_preview.rb b/lookbook/previews/open_project/common/divider_preview.rb new file mode 100644 index 000000000000..82f52ba33e85 --- /dev/null +++ b/lookbook/previews/open_project/common/divider_preview.rb @@ -0,0 +1,21 @@ +module OpenProject + module Common + # @logical_path OpenProject/Common + class DividerPreview < Lookbook::Preview + ## + # **A simple divider (hr)** + # --------------------- + # Primer does not provide a HR component, so we rolled our own + # default spacing is 4 top and bottom + def default + render OpenProject::Common::DividerComponent.new + end + + # @param mt number + # @param mb number + def with_dynamic__margins(mt: 2, mb: 2) + render OpenProject::Common::DividerComponent.new(mt:, mb:) + end + end + end +end From b7c6d4b4c01e2ffa4d2dbf9ccb913d8c33157c7a Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 13:02:45 +0200 Subject: [PATCH 64/80] silence rubocop --- lookbook/previews/open_project/common/divider_preview.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lookbook/previews/open_project/common/divider_preview.rb b/lookbook/previews/open_project/common/divider_preview.rb index 82f52ba33e85..8e89ff3980d7 100644 --- a/lookbook/previews/open_project/common/divider_preview.rb +++ b/lookbook/previews/open_project/common/divider_preview.rb @@ -13,7 +13,7 @@ def default # @param mt number # @param mb number - def with_dynamic__margins(mt: 2, mb: 2) + def with_dynamic__margins(mt: 2, mb: 2) # rubocop:disable Naming/MethodParameterName render OpenProject::Common::DividerComponent.new(mt:, mb:) end end From fde8c54923d3c55f5e4abd6af69dcd02df518a86 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 13:15:29 +0200 Subject: [PATCH 65/80] show "job not found error" the same way as other errors; resolves https://github.com/opf/openproject/pull/16364#pullrequestreview-2253960591 --- .../app/components/job_status/dialog/body_component.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.rb b/modules/job_status/app/components/job_status/dialog/body_component.rb index 7a3b4a17a13f..bd03fb83d102 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.rb +++ b/modules/job_status/app/components/job_status/dialog/body_component.rb @@ -102,14 +102,14 @@ def icon end def title - return I18n.t("job_status_dialog.generic_messages.not_found") if job.nil? - - return I18n.t("job_status_dialog.errors") if has_error? + return I18n.t("job_status_dialog.errors") if job.nil? || has_error? job.message || job.payload&.dig("title") || I18n.t("job_status_dialog.title") end def message + return I18n.t("job_status_dialog.generic_messages.not_found") if job.nil? + return I18n.t("job_status_dialog.generic_messages.#{job.status}") if pending? return job.message if has_error? From 69af0d8dc7802857327cdab3c4b1b66fff241b20 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 13:33:00 +0200 Subject: [PATCH 66/80] show close button in non-pending states; resolves https://github.com/opf/openproject/pull/16364#pullrequestreview-2253960591 --- .../components/job_status/dialog/body_component.html.erb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.html.erb b/modules/job_status/app/components/job_status/dialog/body_component.html.erb index a04000d23de9..68ab941344a8 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/body_component.html.erb @@ -54,4 +54,13 @@ end end end %> + <% unless pending? %> + <%= flex_layout(justify_content: :flex_end) do |flex| %> + <% flex.with_column do %> + <%= render(Primer::Beta::Button.new(data: { + "close-dialog-id": JobStatus::Dialog::DialogComponent::MODAL_ID + })) { I18n.t(:button_close) } %> + <% end %> + <% end %> + <% end %> <% end %> From 31e2fc2ffb689758bd85077975dd09eae37164a6 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 14:11:53 +0200 Subject: [PATCH 67/80] fix spec --- modules/job_status/spec/features/job_status_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/job_status/spec/features/job_status_spec.rb b/modules/job_status/spec/features/job_status_spec.rb index 05ce842872fe..f4e7ef984424 100644 --- a/modules/job_status/spec/features/job_status_spec.rb +++ b/modules/job_status/spec/features/job_status_spec.rb @@ -38,7 +38,7 @@ it "renders a descriptive error in case of 404" do visit "/job_statuses/something-that-does-not-exist" - expect(page).to have_css(".octicon-alert", wait: 10) + expect(page).to have_css(".octicon-x-circle", wait: 10) expect(page).to have_content I18n.t("job_status_dialog.generic_messages.not_found") end From 9aa7c860f65be7a931fd214b80aad7cba8c003ff Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 14:37:30 +0200 Subject: [PATCH 68/80] loading indicator css animation is restarted on turbo frame reload. Move it into the form. --- .../dynamic/job-status-polling.controller.ts | 14 +++++++++++++- .../job_status/dialog/body_component.html.erb | 9 +-------- .../job_status/dialog/dialog_component.html.erb | 4 ++++ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts index 9dcef31850c0..c039b839e801 100644 --- a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts @@ -14,16 +14,28 @@ export default class JobStatusPollingController extends Controller } disconnect() { - this.finishedTargetConnected(); + this.stopPolling(); if (this.backOnCloseValue) { window.history.back(); } } finishedTargetConnected() { + this.stopPolling(); + this.hideProgressIndicator(); + } + + stopPolling() { clearInterval(this.interval); } + hideProgressIndicator() { + const node = document.querySelector('#job-status-modal-dialog .op-loading-indicator'); + if (node) { + node.remove(); + } + } + downloadTargetConnected(element:HTMLLinkElement) { setTimeout(() => element.click(), 50); } diff --git a/modules/job_status/app/components/job_status/dialog/body_component.html.erb b/modules/job_status/app/components/job_status/dialog/body_component.html.erb index 68ab941344a8..e384c6818a92 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/body_component.html.erb @@ -1,12 +1,5 @@ <%= turbo_frame_tag "job_status_modal" do %> - <% if pending? %> - <%= component_wrapper do %> -
-
-
-
- <% end %> - <% elsif job.present? %> + <% if !pending? && job.present? %> <%= content_tag :div, "", data: { "job-status-polling-target": "finished" } %> <% end %> <%= render Primer::Beta::Blankslate.new do |component| diff --git a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb index 1e87d6c37a37..af2a318c6176 100644 --- a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb @@ -5,6 +5,10 @@ id: MODAL_ID )) do |dialog| %> <% dialog.with_body do %> +
+
+
+
<%= turbo_frame_tag("job_status_modal", src: job_status_dialog_body_path(job_uuid: job_uuid), data: { From 2462ee6dee6e46b49f98d454970aacae5e8826df Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 15:55:47 +0200 Subject: [PATCH 69/80] stop polling and progress if job not found --- .../app/components/job_status/dialog/body_component.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/job_status/app/components/job_status/dialog/body_component.html.erb b/modules/job_status/app/components/job_status/dialog/body_component.html.erb index e384c6818a92..9fd33a19a808 100644 --- a/modules/job_status/app/components/job_status/dialog/body_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/body_component.html.erb @@ -1,5 +1,5 @@ <%= turbo_frame_tag "job_status_modal" do %> - <% if !pending? && job.present? %> + <% unless pending? %> <%= content_tag :div, "", data: { "job-status-polling-target": "finished" } %> <% end %> <%= render Primer::Beta::Blankslate.new do |component| From 84a5b514f26b516f25d505384d551566616ac3a4 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 15:59:58 +0200 Subject: [PATCH 70/80] implement error handling for job status dialog calling --- .../work-packages/export/form.controller.ts | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts index 67c7ea886d51..f3b0ca680844 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts @@ -1,5 +1,6 @@ import { Controller } from '@hotwired/stimulus'; import * as Turbo from '@hotwired/turbo'; +import { HttpErrorResponse } from '@angular/common/http'; export default class FormController extends Controller { static values = { @@ -20,7 +21,7 @@ export default class FormController extends Controller { if (response.ok) { Turbo.renderStreamMessage(await response.text()); } else { - throw new Error('Invalid response from server'); + throw new Error(response.statusText || 'Invalid response from server'); } } @@ -30,14 +31,14 @@ export default class FormController extends Controller { headers: { Accept: 'application/json' }, credentials: 'same-origin', }); - if (response.ok) { - const result = await response.json() as { job_id:string }; - if (!result.job_id) { - throw new Error('Invalid response from server'); - } - return result.job_id; + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + const result = await response.json() as { job_id:string }; + if (!result.job_id) { + throw new Error('Invalid response from server'); } - throw new Error(response.statusText); + return result.job_id; } submitForm(evt:CustomEvent) { @@ -47,10 +48,13 @@ export default class FormController extends Controller { const exportURL = `${formatURL}?${searchParams.toString()}`; this.requestExport(exportURL) .then((job_id) => this.showJobModal(job_id)) - .catch((error) => { - // TODO: error handling - console.error(error); - }); + .catch((error) => this.handleError(error)); + } + + private handleError(error:HttpErrorResponse) { + void window.OpenProject.getPluginContext().then((pluginContext) => { + pluginContext.services.notifications.addError(error); + }); } private getExportParams() { From a33ce8e966983f4186feb57367be5ae612a0f1d7 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 16:15:20 +0200 Subject: [PATCH 71/80] obey eslint --- .../dynamic/work-packages/export/form.controller.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts index f3b0ca680844..bdf0e1fa2517 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts @@ -1,6 +1,5 @@ import { Controller } from '@hotwired/stimulus'; import * as Turbo from '@hotwired/turbo'; -import { HttpErrorResponse } from '@angular/common/http'; export default class FormController extends Controller { static values = { @@ -48,12 +47,12 @@ export default class FormController extends Controller { const exportURL = `${formatURL}?${searchParams.toString()}`; this.requestExport(exportURL) .then((job_id) => this.showJobModal(job_id)) - .catch((error) => this.handleError(error)); + .catch((error) => this.handleError(error as string)); } - private handleError(error:HttpErrorResponse) { + private handleError(error:any) { void window.OpenProject.getPluginContext().then((pluginContext) => { - pluginContext.services.notifications.addError(error); + pluginContext.services.notifications.addError(error as string); }); } From 0509a2c2085e89e469951ed38009b2a2caf3fa6d Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 22 Aug 2024 16:30:00 +0200 Subject: [PATCH 72/80] obey eslint --- .../dynamic/work-packages/export/form.controller.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts index bdf0e1fa2517..149bd00801b6 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/form.controller.ts @@ -1,5 +1,6 @@ import { Controller } from '@hotwired/stimulus'; import * as Turbo from '@hotwired/turbo'; +import { HttpErrorResponse } from '@angular/common/http'; export default class FormController extends Controller { static values = { @@ -47,12 +48,12 @@ export default class FormController extends Controller { const exportURL = `${formatURL}?${searchParams.toString()}`; this.requestExport(exportURL) .then((job_id) => this.showJobModal(job_id)) - .catch((error) => this.handleError(error as string)); + .catch((error:HttpErrorResponse) => this.handleError(error)); } - private handleError(error:any) { + private handleError(error:HttpErrorResponse) { void window.OpenProject.getPluginContext().then((pluginContext) => { - pluginContext.services.notifications.addError(error as string); + pluginContext.services.notifications.addError(error); }); } From 1adff159d5d570a9a26675f2e154fbcf1d247923 Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 23 Aug 2024 08:10:43 +0200 Subject: [PATCH 73/80] Change color of normal `hr` separators --- frontend/src/global_styles/content/_forms.sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/global_styles/content/_forms.sass b/frontend/src/global_styles/content/_forms.sass index 45b47682b23c..426f2538857f 100644 --- a/frontend/src/global_styles/content/_forms.sass +++ b/frontend/src/global_styles/content/_forms.sass @@ -177,7 +177,7 @@ form hr width: 100% height: 1px - background: #ccc + background: var(--borderColor-default) border: 0 &.form--separator From 905b041dc4987a8ae46c3310a0af08492033ed7e Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 10:09:18 +0200 Subject: [PATCH 74/80] fix listing not allowed columns in query --- .../work_packages/exports/column_selection_component.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/components/work_packages/exports/column_selection_component.rb b/app/components/work_packages/exports/column_selection_component.rb index 0c2f47afa9e2..7715b74cd5eb 100644 --- a/app/components/work_packages/exports/column_selection_component.rb +++ b/app/components/work_packages/exports/column_selection_component.rb @@ -44,7 +44,10 @@ def initialize(query, id, caption) end def available_columns - work_packages_columns_options + query + .displayable_columns + .sort_by(&:caption) + .map { |column| { id: column.name.to_s, name: column.caption } } end def protected_options @@ -54,7 +57,7 @@ def protected_options def selected_columns query .columns - .map { |s| { id: s.name, name: s.caption } } + .map { |column| { id: column.name.to_s, name: column.caption } } end end end From b7086d64b123bcc5167a5cc5da0aae9eb17b95d8 Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 12:21:15 +0200 Subject: [PATCH 75/80] fix(angular): missing refactoring for removing static dragula area name --- .../draggable-autocomplete.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts index 4c98603c228b..0c2a323f633e 100644 --- a/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts +++ b/frontend/src/app/shared/components/autocompleter/draggable-autocomplete/draggable-autocomplete.component.ts @@ -98,10 +98,10 @@ export class DraggableAutocompleteComponent extends UntilDestroyedMixin implemen } ngOnInit():void { - this.dragula.destroy('columns'); - populateInputsFromDataset(this); + this.dragula.destroy(this.dragAreaName); + this.updateAvailableOptions(); // Setup groups From cf07948a34e5d4a27397074ece1e3d17d1358fea Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 13:49:40 +0200 Subject: [PATCH 76/80] consistent route pattern; resolves review: https://github.com/opf/openproject/pull/16364#discussion_r1731030473 --- modules/job_status/config/routes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/job_status/config/routes.rb b/modules/job_status/config/routes.rb index c15c37d31cb5..0c25772808dc 100644 --- a/modules/job_status/config/routes.rb +++ b/modules/job_status/config/routes.rb @@ -1,6 +1,6 @@ Rails.application.routes.draw do - get "/job_statuses/dialog/:job_uuid", to: "job_statuses#dialog", as: "job_status_dialog" - get "/job_statuses/dialog/:job_uuid/body", to: "job_statuses#dialog_body", as: "job_status_dialog_body" + get "/job_statuses/:job_uuid/dialog", to: "job_statuses#dialog", as: "job_status_dialog" + get "/job_statuses/:job_uuid/dialog/body", to: "job_statuses#dialog_body", as: "job_status_dialog_body" get "/job_statuses/:job_uuid", to: "job_statuses#show", as: "job_status" From 0963c5201f35c252e37d36a87cf0e70f1da17878 Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 13:54:39 +0200 Subject: [PATCH 77/80] move style into sass; resolves review: https://github.com/opf/openproject/pull/16364#discussion_r1731024214 --- .../components/job_status/dialog/dialog_component.html.erb | 5 +---- .../app/components/job_status/dialog/dialog_component.sass | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 modules/job_status/app/components/job_status/dialog/dialog_component.sass diff --git a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb index af2a318c6176..3a878693945e 100644 --- a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb @@ -5,10 +5,7 @@ id: MODAL_ID )) do |dialog| %> <% dialog.with_body do %> -
-
-
-
+
<%= turbo_frame_tag("job_status_modal", src: job_status_dialog_body_path(job_uuid: job_uuid), data: { diff --git a/modules/job_status/app/components/job_status/dialog/dialog_component.sass b/modules/job_status/app/components/job_status/dialog/dialog_component.sass new file mode 100644 index 000000000000..513cd0b3c066 --- /dev/null +++ b/modules/job_status/app/components/job_status/dialog/dialog_component.sass @@ -0,0 +1,3 @@ +#job-status-modal-dialog + .op-loading-indicator + margin-bottom: -20px From 5d1637f5f4cab5a402e4b6f39021f1a5e2aaf20d Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 14:19:50 +0200 Subject: [PATCH 78/80] loading indicator as stimulus target; resolves review: https://github.com/opf/openproject/pull/16364#discussion_r1731023060 --- .../dynamic/job-status-polling.controller.ts | 13 ++++++------ .../dialog/dialog_component.html.erb | 20 +++++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts index c039b839e801..e0e37ef30772 100644 --- a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts @@ -1,16 +1,18 @@ import { Controller } from '@hotwired/stimulus'; import { FrameElement } from '@hotwired/turbo'; -export default class JobStatusPollingController extends Controller { - static targets = ['finished', 'download', 'redirect']; +export default class JobStatusPollingController extends Controller { + static targets = ['finished', 'download', 'redirect', 'indicator', "frame"]; static values = { backOnClose: { type: Boolean, default: false } }; declare readonly backOnCloseValue:boolean; + declare readonly indicatorTarget:HTMLElement; + declare readonly frameTarget:FrameElement; interval:ReturnType; connect() { - this.interval = setInterval(() => this.element.reload(), 2000); + this.interval = setInterval(() => this.frameTarget.reload(), 2000); } disconnect() { @@ -30,10 +32,7 @@ export default class JobStatusPollingController extends Controller } hideProgressIndicator() { - const node = document.querySelector('#job-status-modal-dialog .op-loading-indicator'); - if (node) { - node.remove(); - } + this.indicatorTarget.remove(); } downloadTargetConnected(element:HTMLLinkElement) { diff --git a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb index 3a878693945e..422784a64b19 100644 --- a/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb +++ b/modules/job_status/app/components/job_status/dialog/dialog_component.html.erb @@ -2,18 +2,22 @@ title: I18n.t('job_status_dialog.title'), visually_hide_title: true, size: :large, - id: MODAL_ID + id: MODAL_ID, + data: { + "application-target": "dynamic", + controller: 'job-status-polling', + "job-status-polling-back-on-close-value": back_on_close + } )) do |dialog| %> <% dialog.with_body do %> -
+
+
+
+
<%= turbo_frame_tag("job_status_modal", src: job_status_dialog_body_path(job_uuid: job_uuid), data: { - "application-target": "dynamic", - controller: 'job-status-polling', - "job-status-polling-back-on-close-value": back_on_close, - } - ) do %> - <% end %> + "job-status-polling-target": "frame" + }) %> <% end %> <% end %> From 9d58ad5fb8d240bcf10c478e829334914dca2faa Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 15:05:28 +0200 Subject: [PATCH 79/80] obey eslint --- .../controllers/dynamic/job-status-polling.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts index e0e37ef30772..ce06ad9bcaea 100644 --- a/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/job-status-polling.controller.ts @@ -2,7 +2,7 @@ import { Controller } from '@hotwired/stimulus'; import { FrameElement } from '@hotwired/turbo'; export default class JobStatusPollingController extends Controller { - static targets = ['finished', 'download', 'redirect', 'indicator', "frame"]; + static targets = ['finished', 'download', 'redirect', 'indicator', 'frame']; static values = { backOnClose: { type: Boolean, default: false } }; declare readonly backOnCloseValue:boolean; From f0ab7e66b27c06cccbf01f371c3142ccebe2ed04 Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 26 Aug 2024 15:38:16 +0200 Subject: [PATCH 80/80] consistent route pattern; add missing change to angular helper --- frontend/src/app/core/path-helper/path-helper.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/core/path-helper/path-helper.service.ts b/frontend/src/app/core/path-helper/path-helper.service.ts index 48ba55bed8ed..1b2274db1c3c 100644 --- a/frontend/src/app/core/path-helper/path-helper.service.ts +++ b/frontend/src/app/core/path-helper/path-helper.service.ts @@ -315,6 +315,6 @@ export class PathHelperService { } public jobStatusModalPath(jobId:string) { - return `${this.staticBase}/job_statuses/dialog/${jobId}`; + return `${this.staticBase}/job_statuses/${jobId}/dialog`; } }