mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Changes the Update / Create work package contract.
This commit is contained in:
+27
-16
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -34,10 +36,12 @@ class Type < ApplicationRecord
|
||||
|
||||
include ::Scopes::Scoped
|
||||
|
||||
attribute :patterns, Types::PatternCollectionType.new
|
||||
attribute :patterns, Types::Patterns::CollectionType.new
|
||||
|
||||
before_destroy :check_integrity
|
||||
|
||||
belongs_to :color, optional: true, class_name: "Color"
|
||||
|
||||
has_many :work_packages
|
||||
has_many :workflows, dependent: :delete_all do
|
||||
def copy_from_type(source_type)
|
||||
@@ -52,12 +56,9 @@ class Type < ApplicationRecord
|
||||
join_table: "#{table_name_prefix}custom_fields_types#{table_name_suffix}",
|
||||
association_foreign_key: "custom_field_id"
|
||||
|
||||
belongs_to :color, optional: true, class_name: "Color"
|
||||
|
||||
acts_as_list
|
||||
|
||||
validates :name, presence: true, uniqueness: { case_sensitive: false }, length: { maximum: 255 }
|
||||
|
||||
validates :is_default, :is_milestone, inclusion: { in: [true, false] }
|
||||
|
||||
scopes :milestone
|
||||
@@ -65,8 +66,9 @@ class Type < ApplicationRecord
|
||||
default_scope { order("position ASC") }
|
||||
|
||||
scope :without_standard, -> { where(is_standard: false).order(:position) }
|
||||
scope :default, -> { where(is_default: true) }
|
||||
|
||||
def to_s; name end
|
||||
delegate :to_s, to: :name
|
||||
|
||||
def <=>(other)
|
||||
name <=> other.name
|
||||
@@ -81,26 +83,20 @@ class Type < ApplicationRecord
|
||||
end
|
||||
|
||||
def self.standard_type
|
||||
::Type.where(is_standard: true).first
|
||||
end
|
||||
|
||||
def self.default
|
||||
::Type.where(is_default: true)
|
||||
where(is_standard: true).first
|
||||
end
|
||||
|
||||
def self.enabled_in(project)
|
||||
::Type.includes(:projects).where(projects: { id: project })
|
||||
includes(:projects).where(projects: { id: project })
|
||||
end
|
||||
|
||||
def statuses(include_default: false)
|
||||
if new_record?
|
||||
Status.none
|
||||
elsif include_default
|
||||
::Type
|
||||
.statuses([id])
|
||||
.or(Status.where_default)
|
||||
self.class.statuses([id]).or(Status.where_default)
|
||||
else
|
||||
::Type.statuses([id])
|
||||
self.class.statuses([id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,9 +104,24 @@ class Type < ApplicationRecord
|
||||
object.types.include?(self)
|
||||
end
|
||||
|
||||
def replacement_patterns_defined?
|
||||
return false if patterns.blank?
|
||||
|
||||
patterns.all_enabled.any?
|
||||
end
|
||||
|
||||
def enabled_patterns
|
||||
return {} if patterns.blank?
|
||||
|
||||
patterns.all_enabled
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_integrity
|
||||
raise "Can't delete type" if WorkPackage.where(type_id: id).any?
|
||||
throw :abort if is_standard?
|
||||
throw :abort if WorkPackage.exists?(type_id: id)
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
|
||||
module Types
|
||||
Pattern = Data.define(:blueprint, :enabled) do
|
||||
def call(object)
|
||||
# calculate string using object
|
||||
blueprint.to_s + object.to_s
|
||||
def enabled? = !!enabled
|
||||
|
||||
def resolve(work_package)
|
||||
PatternMapper.new(self).resolve(work_package)
|
||||
end
|
||||
|
||||
def to_h
|
||||
|
||||
@@ -1,40 +1,77 @@
|
||||
# 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 Types
|
||||
class PatternMapper
|
||||
TOKEN_REGEX = /{{[A-z_]+}}/
|
||||
TOKEN_REGEX = /{{[0-9A-Za-z_]+}}/
|
||||
|
||||
MAPPING = {
|
||||
assignee: ->(wp) { wp.assigned_to.name },
|
||||
type: ->(wp) { wp.type.name },
|
||||
assignee: ->(wp) { wp.assigned_to&.name },
|
||||
created: ->(wp) { wp.created_at },
|
||||
author: ->(wp) { wp.author.name }
|
||||
author: ->(wp) { wp.author.name },
|
||||
parent_id: ->(wp) { wp.parent&.id },
|
||||
project_name: ->(wp) { wp.project.name }
|
||||
}.freeze
|
||||
|
||||
private_constant :MAPPING
|
||||
|
||||
def initialize(pattern)
|
||||
@pattern = pattern
|
||||
@tokens = pattern.scan(TOKEN_REGEX).map { |token| Patterns::Token.new(token) }
|
||||
@tokens = pattern.scan(TOKEN_REGEX).map { |token| Patterns::Token.build(token) }
|
||||
end
|
||||
|
||||
def valid?(work_package)
|
||||
@tokens.each { |token| fn(token.key).call(work_package) }
|
||||
@tokens.each { |token| get_value(work_package, token) }
|
||||
rescue NoMethodError
|
||||
false
|
||||
end
|
||||
|
||||
def resolve(work_package)
|
||||
@tokens.inject(@pattern) do |pattern, token|
|
||||
value = fn(token.key).call(work_package)
|
||||
pattern.gsub(token.pattern, stringify(value))
|
||||
pattern.gsub(token.pattern, get_value(work_package, token))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_value(work_package, token)
|
||||
raw_value = if token.custom_field? && token.context != work_package.context
|
||||
fn(key).call(work_package.public_send(token.context))
|
||||
else
|
||||
fn(token.key).call(work_package)
|
||||
end
|
||||
|
||||
stringify(raw_value)
|
||||
end
|
||||
|
||||
def fn(key)
|
||||
MAPPING.fetch(key) { ->(wp) { wp.public_send(key.to_sym) } }
|
||||
end
|
||||
@@ -43,6 +80,8 @@ module Types
|
||||
case value
|
||||
when Date, Time, DateTime
|
||||
value.strftime("%Y-%m-%d")
|
||||
when NilClass
|
||||
"N/A"
|
||||
else
|
||||
value.to_s
|
||||
end
|
||||
|
||||
@@ -29,25 +29,31 @@
|
||||
#++
|
||||
|
||||
module Types
|
||||
PatternCollection = Data.define(:patterns) do
|
||||
private_class_method :new
|
||||
module Patterns
|
||||
Collection = Data.define(:patterns) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(patterns:, contract: PatternCollectionContract.new)
|
||||
contract.call(patterns).to_monad.fmap { |success| new(success.to_h) }
|
||||
end
|
||||
def self.build(patterns:, contract: CollectionContract.new)
|
||||
contract.call(patterns).to_monad.fmap { |success| new(success.to_h) }
|
||||
end
|
||||
|
||||
def initialize(patterns:)
|
||||
transformed = patterns.transform_values { Pattern.new(**_1) }.freeze
|
||||
def initialize(patterns:)
|
||||
transformed = patterns.transform_values { Pattern.new(**_1) }.freeze
|
||||
|
||||
super(patterns: transformed)
|
||||
end
|
||||
super(patterns: transformed)
|
||||
end
|
||||
|
||||
def [](value)
|
||||
patterns.fetch(value)
|
||||
end
|
||||
def all_enabled
|
||||
patterns.select { |_, pattern| pattern.enabled? }
|
||||
end
|
||||
|
||||
def to_h
|
||||
patterns.stringify_keys.transform_values(&:to_h)
|
||||
def [](value)
|
||||
patterns.fetch(value)
|
||||
end
|
||||
|
||||
def to_h
|
||||
patterns.stringify_keys.transform_values(&:to_h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+7
-5
@@ -29,11 +29,13 @@
|
||||
#++
|
||||
|
||||
module Types
|
||||
class PatternCollectionContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:subject).hash do
|
||||
required(:blueprint).filled(:string)
|
||||
required(:enabled).filled(:bool)
|
||||
module Patterns
|
||||
class CollectionContract < Dry::Validation::Contract
|
||||
params do
|
||||
required(:subject).hash do
|
||||
required(:blueprint).filled(:string)
|
||||
required(:enabled).filled(:bool)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+17
-15
@@ -29,26 +29,28 @@
|
||||
#++
|
||||
|
||||
module Types
|
||||
class PatternCollectionType < ActiveModel::Type::Value
|
||||
def assert_valid_value(value)
|
||||
cast(value)
|
||||
end
|
||||
module Patterns
|
||||
class CollectionType < ActiveModel::Type::Value
|
||||
def assert_valid_value(value)
|
||||
cast(value)
|
||||
end
|
||||
|
||||
def cast(value)
|
||||
PatternCollection.build(patterns: value).value_or { nil }
|
||||
end
|
||||
def cast(value)
|
||||
Collection.build(patterns: value).value_or { nil }
|
||||
end
|
||||
|
||||
def serialize(pattern)
|
||||
return super if pattern.nil?
|
||||
def serialize(pattern)
|
||||
return super if pattern.nil?
|
||||
|
||||
YAML.dump(pattern.to_h)
|
||||
end
|
||||
YAML.dump(pattern.to_h)
|
||||
end
|
||||
|
||||
def deserialize(value)
|
||||
return if value.blank?
|
||||
def deserialize(value)
|
||||
return if value.blank?
|
||||
|
||||
data = YAML.safe_load(value)
|
||||
cast(data)
|
||||
data = YAML.safe_load(value)
|
||||
cast(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,56 @@
|
||||
# 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 Types
|
||||
module Patterns
|
||||
Token = Data.define(:pattern) do
|
||||
def key = pattern.tr("{}", "").to_sym
|
||||
Token = Data.define(:pattern, :key) do
|
||||
private_class_method :new
|
||||
|
||||
def self.build(pattern)
|
||||
new(pattern, pattern.tr("{}", "").to_sym)
|
||||
end
|
||||
|
||||
def custom_field? = key.to_s.include?("custom_field")
|
||||
|
||||
def custom_field_id
|
||||
return nil unless custom_field?
|
||||
|
||||
Integer(key.to_s.gsub(/\D+/, ""))
|
||||
end
|
||||
|
||||
def custom_field_context
|
||||
context = key.to_s.gsub(/_?custom_field_\d+/, "")
|
||||
return :work_package if context.blank?
|
||||
|
||||
context.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -30,17 +32,15 @@ class WorkPackages::CreateService < BaseServices::BaseCallable
|
||||
include ::WorkPackages::Shared::UpdateAncestors
|
||||
include ::Shared::ServiceContext
|
||||
|
||||
attr_accessor :user,
|
||||
:contract_class
|
||||
attr_reader :user, :contract_class
|
||||
|
||||
def initialize(user:, contract_class: WorkPackages::CreateContract)
|
||||
self.user = user
|
||||
self.contract_class = contract_class
|
||||
super()
|
||||
@user = user
|
||||
@contract_class = contract_class
|
||||
end
|
||||
|
||||
def perform(work_package: WorkPackage.new,
|
||||
send_notifications: nil,
|
||||
**attributes)
|
||||
def perform(work_package: WorkPackage.new, send_notifications: nil, **attributes)
|
||||
in_user_context(send_notifications:) do
|
||||
create(attributes, work_package)
|
||||
end
|
||||
@@ -55,8 +55,8 @@ class WorkPackages::CreateService < BaseServices::BaseCallable
|
||||
if result.success
|
||||
work_package.attachments = work_package.attachments_replacements if work_package.attachments_replacements
|
||||
work_package.save
|
||||
else
|
||||
false
|
||||
|
||||
set_templated_subject(work_package)
|
||||
end
|
||||
|
||||
if result.success?
|
||||
@@ -67,25 +67,25 @@ class WorkPackages::CreateService < BaseServices::BaseCallable
|
||||
end
|
||||
|
||||
set_user_as_watcher(work_package)
|
||||
else
|
||||
result.success = false
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def set_attributes(attributes, wp)
|
||||
attributes_service_class
|
||||
.new(user:,
|
||||
model: wp,
|
||||
contract_class:)
|
||||
.call(attributes)
|
||||
def set_templated_subject(work_package)
|
||||
return true unless work_package.type&.replacement_patterns_defined?
|
||||
return true unless work_package.type.enabled_patterns[:subject]
|
||||
|
||||
work_package.subject = work_package.type.enabled_patterns[:subject].resolve(work_package)
|
||||
work_package.save
|
||||
end
|
||||
|
||||
def set_attributes(attributes, work_package)
|
||||
attributes_service_class.new(user:, model: work_package, contract_class:).call(attributes)
|
||||
end
|
||||
|
||||
def reschedule_related(work_package)
|
||||
result = WorkPackages::SetScheduleService
|
||||
.new(user:, work_package:)
|
||||
.call
|
||||
result = WorkPackages::SetScheduleService.new(user:, work_package:).call
|
||||
|
||||
result.self_and_dependent.each do |r|
|
||||
unless r.result.save
|
||||
@@ -100,9 +100,7 @@ class WorkPackages::CreateService < BaseServices::BaseCallable
|
||||
def set_user_as_watcher(work_package)
|
||||
# We don't care if it fails here. If it does
|
||||
# the user simply does not become watcher
|
||||
Services::CreateWatcher
|
||||
.new(work_package, user)
|
||||
.run(send_notifications: false)
|
||||
Services::CreateWatcher.new(work_package, user).run(send_notifications: false)
|
||||
end
|
||||
|
||||
def attributes_service_class
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -43,6 +45,15 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes
|
||||
end
|
||||
|
||||
set_custom_attributes(attributes)
|
||||
mark_templated_subject
|
||||
end
|
||||
|
||||
def mark_templated_subject
|
||||
return true unless work_package.type&.replacement_patterns_defined?
|
||||
|
||||
if work_package.type.patterns.all_enabled[:subject]
|
||||
work_package.subject = "Templated by #{work_package.type.name}"
|
||||
end
|
||||
end
|
||||
|
||||
def set_static_attributes(attributes)
|
||||
@@ -298,15 +309,14 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes
|
||||
|
||||
def set_version_to_nil
|
||||
if work_package.version &&
|
||||
work_package.project &&
|
||||
work_package.project.shared_versions.exclude?(work_package.version)
|
||||
work_package.project&.shared_versions&.exclude?(work_package.version)
|
||||
work_package.version = nil
|
||||
end
|
||||
end
|
||||
|
||||
def set_parent_to_nil
|
||||
if !Setting.cross_project_work_package_relations? &&
|
||||
!work_package.parent_changed?
|
||||
!work_package.parent_changed?
|
||||
|
||||
work_package.parent = nil
|
||||
end
|
||||
@@ -368,7 +378,7 @@ class WorkPackages::SetAttributesService < BaseServices::SetAttributes
|
||||
|
||||
def new_start_date_from_parent
|
||||
return unless work_package.parent_id_changed? &&
|
||||
work_package.parent
|
||||
work_package.parent
|
||||
|
||||
work_package.parent.soonest_start
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -39,7 +41,16 @@ class WorkPackages::UpdateService < BaseServices::Update
|
||||
|
||||
private
|
||||
|
||||
def set_templated_attributes
|
||||
return unless model.type.replacement_patterns_defined?
|
||||
|
||||
model.type.patterns.all_enabled.each do |key, pattern|
|
||||
model.public_send(:"#{key}=", pattern.resolve(model))
|
||||
end
|
||||
end
|
||||
|
||||
def after_perform(service_call)
|
||||
set_templated_attributes
|
||||
update_related_work_packages(service_call)
|
||||
cleanup(service_call.result)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -31,6 +33,7 @@ FactoryBot.define do
|
||||
sequence(:position)
|
||||
name { |a| "Type No. #{a.position}" }
|
||||
description { nil }
|
||||
patterns { nil }
|
||||
created_at { Time.zone.now }
|
||||
updated_at { Time.zone.now }
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ RSpec.describe Type do
|
||||
subject: { blueprint: "{{work_package:custom_field_123}} - {{project:custom_field_321}}", enabled: true }
|
||||
})
|
||||
|
||||
expect(type.patterns).to be_a(Types::PatternCollection)
|
||||
expect(type.patterns).to be_a(Types::Patterns::Collection)
|
||||
expect(type.patterns[:subject])
|
||||
.to eq(Types::Pattern.new("{{work_package:custom_field_123}} - {{project:custom_field_321}}", true))
|
||||
end
|
||||
@@ -161,7 +161,7 @@ RSpec.describe Type do
|
||||
it "converts the incoming hash into a PatternCollection" do
|
||||
type.patterns = { subject: { blueprint: "some_string", enabled: false } }
|
||||
|
||||
expect(type.patterns).to be_a(Types::PatternCollection)
|
||||
expect(type.patterns).to be_a(Types::Patterns::Collection)
|
||||
expect(type.patterns[:subject]).to be_a(Types::Pattern)
|
||||
|
||||
expect { type.save! }.not_to raise_error
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Types::PatternMapper do
|
||||
let(:type) { build(:type, patterns: { subject: subject_pattern }) }
|
||||
let(:subject_pattern) { "ID Please: {{id}}" }
|
||||
let(:work_package) { create(:work_package) }
|
||||
|
||||
@@ -21,7 +45,7 @@ RSpec.describe Types::PatternMapper do
|
||||
|
||||
it "resolves the pattern" do
|
||||
expect(subject.resolve(work_package))
|
||||
.to eq("#{work_package.id} | #{work_package.done_ratio} | #{work_package.created_at.to_date.iso8601}")
|
||||
.to eq("#{work_package.id} | N/A | #{work_package.created_at.to_date.iso8601}")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -43,6 +45,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
|
||||
p
|
||||
end
|
||||
|
||||
let(:work_package) do
|
||||
wp = build_stubbed(:work_package, project:, status: status_0_pct_complete)
|
||||
wp.type = initial_type
|
||||
@@ -50,9 +53,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
|
||||
wp
|
||||
end
|
||||
let(:new_work_package) do
|
||||
WorkPackage.new
|
||||
end
|
||||
let(:new_work_package) { WorkPackage.new }
|
||||
let(:initial_type) { build_stubbed(:type) }
|
||||
let(:milestone_type) { build_stubbed(:type_milestone) }
|
||||
let(:statuses) { [] }
|
||||
@@ -78,24 +79,25 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
end
|
||||
|
||||
shared_examples_for "service call" do |description: nil|
|
||||
subject do
|
||||
allow(work_package)
|
||||
.to receive(:save)
|
||||
subject(:service_result) { instance.call(call_attributes) }
|
||||
|
||||
instance.call(call_attributes)
|
||||
end
|
||||
before { allow(work_package).to receive(:save) }
|
||||
|
||||
it description || "sets the value" do
|
||||
all_expected_attributes = {}
|
||||
all_expected_attributes.merge!(expected_attributes) if defined?(expected_attributes)
|
||||
|
||||
if defined?(expected_kept_attributes)
|
||||
kept = work_package.attributes.slice(*expected_kept_attributes)
|
||||
|
||||
if kept.size != expected_kept_attributes.size
|
||||
raise ArgumentError, "expected_kept_attributes contains attributes that are not present in the work_package: " \
|
||||
"#{expected_kept_attributes - kept.keys} not present in #{work_package.attributes}"
|
||||
end
|
||||
|
||||
all_expected_attributes.merge!(kept)
|
||||
end
|
||||
|
||||
next if all_expected_attributes.blank?
|
||||
|
||||
subject
|
||||
@@ -354,9 +356,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
let(:new_statuses) { [other_status, default_status] }
|
||||
|
||||
before do
|
||||
allow(Status)
|
||||
.to receive(:default)
|
||||
.and_return(default_status)
|
||||
allow(Status).to receive(:default).and_return(default_status)
|
||||
end
|
||||
|
||||
context "with no value set before for a new work package" do
|
||||
@@ -364,9 +364,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
let(:expected_attributes) { { status: default_status } }
|
||||
let(:work_package) { new_work_package }
|
||||
|
||||
before do
|
||||
work_package.status = nil
|
||||
end
|
||||
before { work_package.status = nil }
|
||||
|
||||
it_behaves_like "service call"
|
||||
end
|
||||
@@ -447,17 +445,15 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
|
||||
it_behaves_like "service call" do
|
||||
it "sets the service's author" do
|
||||
subject
|
||||
instance.call(call_attributes)
|
||||
|
||||
expect(work_package.author)
|
||||
.to eql user
|
||||
expect(work_package.author).to eql user
|
||||
end
|
||||
|
||||
it "notes the author to be system changed" do
|
||||
subject
|
||||
|
||||
expect(work_package.changed_by_system["author_id"])
|
||||
.to eql [nil, user.id]
|
||||
expect(work_package.changed_by_system["author_id"]).to eql [nil, user.id]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -565,7 +561,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
before do
|
||||
allow(parent)
|
||||
.to receive(:soonest_start)
|
||||
.and_return(parent_start_date + 3.days)
|
||||
.and_return(parent_start_date + 3.days)
|
||||
end
|
||||
|
||||
it_behaves_like "service call" do
|
||||
@@ -1475,10 +1471,10 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
|
||||
allow(IssuePriority)
|
||||
.to receive(:active)
|
||||
.and_return(scope)
|
||||
.and_return(scope)
|
||||
allow(scope)
|
||||
.to receive(:default)
|
||||
.and_return(default_priority)
|
||||
.and_return(default_priority)
|
||||
end
|
||||
|
||||
context "with no value set before for a new work package" do
|
||||
@@ -1639,22 +1635,22 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
instance_double(ActiveRecord::Relation).tap do |categories_stub|
|
||||
allow(new_project)
|
||||
.to receive(:categories)
|
||||
.and_return(categories_stub)
|
||||
.and_return(categories_stub)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
without_partial_double_verification do
|
||||
allow(new_project_categories)
|
||||
.to receive(:find_by)
|
||||
.with(name: category.name)
|
||||
.and_return nil
|
||||
.to receive(:find_by)
|
||||
.with(name: category.name)
|
||||
.and_return nil
|
||||
allow(new_project)
|
||||
.to receive_messages(shared_versions: new_versions, types: new_types)
|
||||
allow(new_types)
|
||||
.to receive(:order)
|
||||
.with(:position)
|
||||
.and_return(new_types)
|
||||
.with(:position)
|
||||
.and_return(new_types)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1703,8 +1699,8 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
before do
|
||||
allow(new_project_categories)
|
||||
.to receive(:find_by)
|
||||
.with(name: category.name)
|
||||
.and_return new_category
|
||||
.with(name: category.name)
|
||||
.and_return new_category
|
||||
end
|
||||
|
||||
it "uses the equally named category" do
|
||||
@@ -1859,7 +1855,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
|
||||
allow(wp)
|
||||
.to receive(:soonest_start)
|
||||
.and_return(soonest_start)
|
||||
.and_return(soonest_start)
|
||||
|
||||
wp
|
||||
end
|
||||
@@ -1940,7 +1936,7 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
before do
|
||||
allow(work_package)
|
||||
.to receive(:children)
|
||||
.and_return([child])
|
||||
.and_return([child])
|
||||
end
|
||||
|
||||
context "when the child`s start date is after soonest_start" do
|
||||
@@ -1968,4 +1964,33 @@ RSpec.describe WorkPackages::SetAttributesService,
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the type defines a pattern for an attribute" do
|
||||
let(:type) { build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: true } }) }
|
||||
let(:work_package) { WorkPackage.new(type:) }
|
||||
|
||||
it "assigns a to be updated value to the field" do
|
||||
instance.call({})
|
||||
|
||||
expect(work_package.subject).to eq("Templated by #{type.name}")
|
||||
end
|
||||
|
||||
it "overrides even a passed subject" do
|
||||
instance.call(subject: "I will be overwritten")
|
||||
|
||||
expect(work_package.subject).to eq("Templated by #{type.name}")
|
||||
end
|
||||
|
||||
context "when the pattern is disabled" do
|
||||
let(:type) do
|
||||
build_stubbed(:type, patterns: { subject: { blueprint: "{{type}} {{project_name}}", enabled: false } })
|
||||
end
|
||||
|
||||
it "does not overwrite the attribute" do
|
||||
instance.call(subject: "I will be kept")
|
||||
|
||||
expect(work_package.subject).to eq("I will be kept")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user