"),
+ pdf_header_logo: logo_image_filename,
+ pdf_header: heading
+ }
+ # rubocop:enable Naming/VariableNumber
+ end
+
+ def styling_asset_path
+ File.dirname(File.expand_path(__FILE__))
+ end
+end
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 cf346d0f91c7..9a1ca49c949e 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -40,16 +42,20 @@
require "open3"
class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::QueryExporter
- include WorkPackage::PDFExport::Common
- include WorkPackage::PDFExport::Attachments
- include WorkPackage::PDFExport::OverviewTable
- include WorkPackage::PDFExport::SumsTable
- include WorkPackage::PDFExport::WorkPackageDetail
- include WorkPackage::PDFExport::TableOfContents
- include WorkPackage::PDFExport::Page
- include WorkPackage::PDFExport::Gantt
- include WorkPackage::PDFExport::Style
- include WorkPackage::PDFExport::Cover
+ include WorkPackage::PDFExport::Common::Common
+ include WorkPackage::PDFExport::Common::Logo
+ include WorkPackage::PDFExport::Common::Attachments
+ include WorkPackage::PDFExport::Export::ExportCommon
+ include WorkPackage::PDFExport::Export::WorkPackageDetail
+ include WorkPackage::PDFExport::Export::Page
+ include WorkPackage::PDFExport::Export::Style
+ include WorkPackage::PDFExport::Export::OverviewTable
+ include WorkPackage::PDFExport::Export::SumsTable
+ include WorkPackage::PDFExport::Export::WorkPackageDetail
+ include WorkPackage::PDFExport::Export::TableOfContents
+ include WorkPackage::PDFExport::Export::Style
+ include WorkPackage::PDFExport::Export::Cover
+ include WorkPackage::PDFExport::Export::Gantt
attr_accessor :pdf,
:options
diff --git a/app/models/work_package/pdf_export/work_package_to_pdf.rb b/app/models/work_package/pdf_export/work_package_to_pdf.rb
index 9f85c9d9572b..5dcfa09dd8c8 100644
--- a/app/models/work_package/pdf_export/work_package_to_pdf.rb
+++ b/app/models/work_package/pdf_export/work_package_to_pdf.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -27,11 +29,13 @@
#++
class WorkPackage::PDFExport::WorkPackageToPdf < Exports::Exporter
- include WorkPackage::PDFExport::Common
- include WorkPackage::PDFExport::Attachments
- include WorkPackage::PDFExport::WorkPackageDetail
- include WorkPackage::PDFExport::Page
- include WorkPackage::PDFExport::Style
+ include WorkPackage::PDFExport::Common::Common
+ include WorkPackage::PDFExport::Common::Logo
+ include WorkPackage::PDFExport::Common::Attachments
+ include WorkPackage::PDFExport::Export::ExportCommon
+ include WorkPackage::PDFExport::Export::WorkPackageDetail
+ include WorkPackage::PDFExport::Export::Page
+ include WorkPackage::PDFExport::Export::Style
attr_accessor :pdf, :columns
@@ -54,7 +58,7 @@ def export!
render_work_package
success(pdf.render)
rescue StandardError => e
- Rails.logger.error { "Failed to generated PDF export: #{e} #{e.message}}." }
+ Rails.logger.error { "Failed to generate PDF export: #{e} #{e.message}}." }
error(I18n.t(:error_pdf_failed_to_export, error: e.message))
end
diff --git a/app/seeders/basic_data/color_seeder.rb b/app/seeders/basic_data/color_seeder.rb
index 225c372b704d..760ec3ed6cf5 100644
--- a/app/seeders/basic_data/color_seeder.rb
+++ b/app/seeders/basic_data/color_seeder.rb
@@ -29,6 +29,7 @@ module BasicData
class ColorSeeder < ModelSeeder
self.model_class = Color
self.seed_data_model_key = "colors"
+ self.attribute_names_for_lookups = %i[name]
def model_attributes(color_data)
{
diff --git a/app/models/queries/filters/strategies/validations.rb b/app/seeders/basic_data/life_cycle_color_seeder.rb
similarity index 75%
rename from app/models/queries/filters/strategies/validations.rb
rename to app/seeders/basic_data/life_cycle_color_seeder.rb
index bd17a263a057..835dd00cb2d8 100644
--- a/app/models/queries/filters/strategies/validations.rb
+++ b/app/seeders/basic_data/life_cycle_color_seeder.rb
@@ -26,26 +26,19 @@
# See COPYRIGHT and LICENSE files for more details.
#++
-module Queries::Filters::Strategies
- module Validations
- private
+module BasicData
+ class LifeCycleColorSeeder < ColorSeeder
+ self.seed_data_model_key = "life_cycle_colors"
- def date?(str)
- true if Date.parse(str)
- rescue StandardError
- false
+ def applicable?
+ missing_color_names.any?
end
- def validate
- unless values.all? { |value| value.blank? || date?(value) }
- errors.add(:values, I18n.t("activerecord.errors.messages.not_a_date"))
- end
- end
+ private
- def integer?(str)
- true if Integer(str)
- rescue StandardError
- false
+ def missing_color_names
+ color_names = models_data.pluck("name")
+ color_names - Color.where(name: color_names).pluck(:name)
end
end
end
diff --git a/app/seeders/basic_data/life_cycle_step_definition_seeder.rb b/app/seeders/basic_data/life_cycle_step_definition_seeder.rb
new file mode 100644
index 000000000000..54166f731019
--- /dev/null
+++ b/app/seeders/basic_data/life_cycle_step_definition_seeder.rb
@@ -0,0 +1,46 @@
+#-- 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 BasicData
+ class LifeCycleStepDefinitionSeeder < ModelSeeder
+ self.model_class = Project::LifeCycleStepDefinition
+ self.seed_data_model_key = "life_cycles"
+ self.needs = [
+ BasicData::LifeCycleColorSeeder
+ ]
+
+ self.attribute_names_for_lookups = %i[name type]
+
+ def model_attributes(life_cyle_data)
+ {
+ name: life_cyle_data["name"],
+ type: life_cyle_data["type"],
+ color_id: color_id(life_cyle_data["color_name"])
+ }
+ end
+ end
+end
diff --git a/app/seeders/common.yml b/app/seeders/common.yml
index bd7e4a71f8b0..ee89fb7b1376 100644
--- a/app/seeders/common.yml
+++ b/app/seeders/common.yml
@@ -70,6 +70,23 @@ colors:
t_name: Black
hexcode: "#000000"
+life_cycle_colors:
+ - reference: :default_color_pm2_orange
+ t_name: PM2 Orange
+ hexcode: "#F7983A"
+ - reference: :default_color_pm2_purple
+ t_name: PM2 Purple
+ hexcode: "#682D91"
+ - reference: :default_color_pm2_red
+ t_name: PM2 Red
+ hexcode: "#F05823"
+ - reference: :default_color_pm2_magenta
+ t_name: PM2 Magenta
+ hexcode: "#EC038A"
+ - reference: :default_color_pm2_green_yellow
+ t_name: PM2 Green Yellow
+ hexcode: "#B1D13A"
+
document_categories:
- t_name: Documentation
position: 1
diff --git a/app/seeders/source/seed_data.rb b/app/seeders/source/seed_data.rb
index f3813a98e8c2..13f9726bfe39 100644
--- a/app/seeders/source/seed_data.rb
+++ b/app/seeders/source/seed_data.rb
@@ -60,7 +60,10 @@ def find_reference(reference, *fallbacks, default: :__unset__)
default
else
references = [reference, *fallbacks].map(&:inspect)
- message = "Nothing registered with #{'reference'.pluralize(references.count)} #{references.to_sentence(locale: false)}"
+ message = <<~STRING
+ Nothing registered with #{'reference'.pluralize(references.count)} #{references.to_sentence(locale: false)}
+ Perhaps you forgot to add the `attribute_names_for_lookups` for your seeder?
+ STRING
raise ArgumentError, message
end
end
diff --git a/app/seeders/standard.yml b/app/seeders/standard.yml
index a177de929704..0e00dea26de8 100644
--- a/app/seeders/standard.yml
+++ b/app/seeders/standard.yml
@@ -26,6 +26,36 @@
# See COPYRIGHT and LICENSE files for more details.
#++
+life_cycles:
+ - reference: :default_life_cycle_initiating
+ t_name: Initiating
+ type: Project::StageDefinition
+ color_name: :default_color_pm2_orange
+ - reference: :default_life_cycle_ready_for_planning
+ t_name: Ready for Planning
+ type: Project::GateDefinition
+ color_name: :default_color_pm2_purple
+ - reference: :default_life_cycle_planning
+ t_name: Planning
+ type: Project::StageDefinition
+ color_name: :default_color_pm2_red
+ - reference: :default_life_cycle_ready_for_executing
+ t_name: Ready for Executing
+ type: Project::GateDefinition
+ color_name: :default_color_pm2_purple
+ - reference: :default_life_cycle_executing
+ t_name: Executing
+ type: Project::StageDefinition
+ color_name: :default_color_pm2_magenta
+ - reference: :default_life_cycle_ready_for_closing
+ t_name: Ready for Closing
+ type: Project::GateDefinition
+ color_name: :default_color_pm2_purple
+ - reference: :default_life_cycle_closing
+ t_name: Closing
+ type: Project::StageDefinition
+ color_name: :default_color_pm2_green_yellow
+
priorities:
- reference: :default_priority_low
t_name: Low
diff --git a/app/seeders/standard/basic_data_seeder.rb b/app/seeders/standard/basic_data_seeder.rb
index 5c6c06c8ee29..d77525b74a16 100644
--- a/app/seeders/standard/basic_data_seeder.rb
+++ b/app/seeders/standard/basic_data_seeder.rb
@@ -37,6 +37,8 @@ def data_seeder_classes
::BasicData::TimeEntryActivitySeeder,
::BasicData::ColorSeeder,
::BasicData::ColorSchemeSeeder,
+ ::BasicData::LifeCycleColorSeeder,
+ ::BasicData::LifeCycleStepDefinitionSeeder,
::BasicData::WorkflowSeeder,
::BasicData::PrioritySeeder,
::BasicData::SettingSeeder,
diff --git a/app/services/authorization/enterprise_service.rb b/app/services/authorization/enterprise_service.rb
index 7ddd9730e29f..4e419b03d20e 100644
--- a/app/services/authorization/enterprise_service.rb
+++ b/app/services/authorization/enterprise_service.rb
@@ -34,6 +34,7 @@ class Authorization::EnterpriseService
board_view
conditional_highlighting
custom_actions
+ custom_field_hierarchies
date_alerts
define_custom_style
edit_attribute_groups
diff --git a/app/services/custom_fields/hierarchy/hierarchical_item_service.rb b/app/services/custom_fields/hierarchy/hierarchical_item_service.rb
index 72e682a23324..49b5f882da13 100644
--- a/app/services/custom_fields/hierarchy/hierarchical_item_service.rb
+++ b/app/services/custom_fields/hierarchy/hierarchical_item_service.rb
@@ -44,17 +44,18 @@ def generate_root(custom_field)
.bind { |validation| create_root_item(validation[:custom_field]) }
end
- # Insert a new node on the hierarchy tree.
+ # Insert a new node on the hierarchy tree at a desired position or at the end if no sort_order is passed.
# @param parent [CustomField::Hierarchy::Item] the parent of the node
# @param label [String] the node label/name that must be unique at the same tree level
# @param short [String] an alias for the node
+ # @param sort_order [Integer] the position into which insert the item.
# @return [Success(CustomField::Hierarchy::Item), Failure(Dry::Validation::Result), Failure(ActiveModel::Errors)]
- def insert_item(parent:, label:, short: nil)
+ def insert_item(parent:, label:, short: nil, sort_order: nil)
CustomFields::Hierarchy::InsertItemContract
.new
.call({ parent:, label:, short: }.compact)
.to_monad
- .bind { |validation| create_child_item(validation:) }
+ .bind { |validation| create_child_item(validation:, sort_order:) }
end
# Updates an item/node
@@ -97,18 +98,37 @@ def move_item(item:, new_parent:)
# Reorder the item along its siblings.
# @param item [CustomField::Hierarchy::Item] the parent of the node
- # @param new_sort_order [Integer] the new parent of the node
- # @return [Success(CustomField::Hierarchy::Item)]
+ # @param new_sort_order [Integer] the new position of the node
+ # @return [Success]
def reorder_item(item:, new_sort_order:)
- old_item = item.siblings.where(sort_order: new_sort_order).first
- Success(old_item.prepend_sibling(item))
+ return Success() if item.siblings.empty?
+
+ new_sort_order = [0, new_sort_order.to_i].max
+
+ return Success() if item.sort_order == new_sort_order
+
+ update_item_order(item:, new_sort_order:)
+
+ Success()
end
- def soft_delete_item(item)
+ def soft_delete_item(item:)
# Soft delete the item and children
raise NotImplementedError
end
+ def hashed_subtree(item:, depth:)
+ if depth >= 0
+ Success(item.hash_tree(limit_depth: depth + 1))
+ else
+ Success(item.hash_tree)
+ end
+ end
+
+ def descendant_of?(item:, parent:)
+ item.descendant_of?(parent) ? Success() : Failure()
+ end
+
private
def create_root_item(custom_field)
@@ -118,8 +138,11 @@ def create_root_item(custom_field)
Success(item)
end
- def create_child_item(validation:)
- item = validation[:parent].children.create(label: validation[:label], short: validation[:short])
+ def create_child_item(validation:, sort_order: nil)
+ attributes = validation.to_h
+ attributes[:sort_order] = sort_order - 1 if sort_order
+
+ item = validation[:parent].children.create(**attributes)
return Failure(item.errors) if item.new_record?
Success(item)
@@ -132,6 +155,16 @@ def update_item_attributes(item:, attributes:)
Failure(item.errors)
end
end
+
+ def update_item_order(item:, new_sort_order:)
+ target_item = item.siblings.find_by(sort_order: new_sort_order)
+ if target_item.present?
+ target_item.prepend_sibling(item)
+ else
+ target_item = item.siblings.last
+ target_item.append_sibling(item)
+ end
+ end
end
end
end
diff --git a/app/services/journals/update_service.rb b/app/services/journals/update_service.rb
index 229838dd8df2..db3ba619ede4 100644
--- a/app/services/journals/update_service.rb
+++ b/app/services/journals/update_service.rb
@@ -27,5 +27,15 @@
#++
module Journals
- class UpdateService < ::BaseServices::Update; end
+ class UpdateService < ::BaseServices::Update
+ protected
+
+ def after_perform(call)
+ OpenProject::Notifications.send(OpenProject::Events::JOURNAL_UPDATED,
+ journal: call.result,
+ send_notification: Journal::NotificationConfiguration.active?)
+
+ call
+ end
+ end
end
diff --git a/app/views/account/_password_login_form.html.erb b/app/views/account/_password_login_form.html.erb
index 04b2e0d9d507..7ae03893f00d 100644
--- a/app/views/account/_password_login_form.html.erb
+++ b/app/views/account/_password_login_form.html.erb
@@ -27,7 +27,12 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
-<%= styled_form_tag({action: "login"}, autocomplete: 'off', class: '-wide-labels user-login--form') do %>
+<%= styled_form_tag(
+ {action: "login"},
+ autocomplete: 'off',
+ class: '-wide-labels user-login--form',
+ data: { turbo: false } # allow redirects without turbo
+ ) do %>
<%= back_url_hidden_field_tag %>