-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Work Package subject using a Type defined blueprint #17516
base: dev
Are you sure you want to change the base?
Conversation
cbf9b44
to
8636fa3
Compare
a34c017
to
fe14ed9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's looking pretty good. I can take another look after it is moved from draft.
def stringify(value) | ||
case value | ||
when Date, Time, DateTime | ||
value.strftime("%Y-%m-%d") | ||
when NilClass | ||
"N/A" | ||
else | ||
value.to_s | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having ideas here, but we might want to build something clever to define default values for types alongside this structure here, so we only need to parse all types in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... this is very naive right now on purpose.
MAPPING = { | ||
accountable: ->(wp) { wp.responsible&.name }, | ||
assignee: ->(wp) { wp.assigned_to&.name }, | ||
author: ->(wp) { wp.author&.name }, | ||
category: ->(wp) { wp.category&.name }, | ||
creation_date: ->(wp) { wp.created_at }, | ||
estimated_time: ->(wp) { wp.estimated_hours }, | ||
finish_date: ->(wp) { wp.due_date }, | ||
parent: ->(wp) { wp.parent&.id }, | ||
parent_author: ->(wp) { wp.parent&.author&.name }, | ||
parent_category: ->(wp) { wp.parent&.category&.name }, | ||
parent_creation_date: ->(wp) { wp.parent&.created_at }, | ||
parent_estimated_time: ->(wp) { wp.parent&.estimated_hours }, | ||
parent_finish_date: ->(wp) { wp.parent&.due_date }, | ||
parent_priority: ->(wp) { wp.parent&.priority }, | ||
priority: ->(wp) { wp.priority }, | ||
project: ->(wp) { wp.project_id }, | ||
project_active: ->(wp) { wp.project&.active? }, | ||
project_name: ->(wp) { wp.project&.name }, | ||
project_status: ->(wp) { wp.project&.status_code }, | ||
project_parent: ->(wp) { wp.project&.parent_id }, | ||
project_public: ->(wp) { wp.project&.public? }, | ||
start_date: ->(wp) { wp.start_date }, | ||
status: ->(wp) { wp.status&.name }, | ||
type: ->(wp) { wp.type&.name } | ||
}.freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
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}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow. Is this a temporary subject? Does this get persisted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. If there's no subject the contract will fail as it is a required field, so I fill in with something for now so that we can overwrite it later on.
It will get persisted as I might need "persisted" info (like created_at
) for the pattern, but all is done within the context of the same request, so the user won't (hopefully) see it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. Makes sense to me.
... I thought we might want to somehow tag these so that we can query any lingering/stuck cases later. But honestly Templated by
is probably good enough for that.
contract_class:) | ||
.call(attributes) | ||
def set_templated_subject(work_package) | ||
return true unless work_package.type&.replacement_patterns_defined? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're doing this a few times. Does it make sense to add a helper in the work_package for syntax sugar?
work_package.type&.replacement_patterns_defined?
work_package.replacement_patterns_defined?
... probably no 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wondered the same thing. Ended up not doing it... for now. :P
Related WP: OP#60003
Wat?
This PR aims to make possible the definition/replacement of a
WorkPackage#subject
based on blueprint defined on itsType
.How?
There are a lot of moving parts here. The
PatternCollection
,Pattern
and their ActiveRecord value types where already in place.Now comes the
PatternMapper
(better name pending), that is capable of taking the blueprint from aPattern
parsing it and resolving it into a string for replacement.To update the Work Packages, changes where necessary on the
SetAttributeService
,CreateService
andUpdateService
.Details
PatternMapper
resolves the tokens (basically fragments on double curly braces, i.e.{{token}}
) using a lookup table. If the key isn't found, it tries to call the method on the work package and get its value.Things get more complicated when we get to custom fields, as those are dynamically added to work packages and can vary from installation to installation.
But all custom field assigned values can be accessed via the
custom_field_ID
method, so we first check the context:{{custom_field_123}}
is considered part of the Work Package{{parent_custom_field_123}}
will try to getwork_package.parent.custom_field_123
{{project_custom_field_123}}
will try to get the project attribute 123 in the same way above.The
SetAttributesService
needed to be updated so that it overrides whatever subject was there in the first place with a message saying that it will be updated later. This is important in case something fails later on.Update and Create services do their respective updates very late in the process, to make sure all the changes that might impact the subject are already assigned.
On Create specifically we save first, just in case we need some auto-generated field like
created_at
.