Support quick action commands via description editing for work items
What does this MR do and why?
We want to support quick actions via editing work item's description field. Related to #382160 (closed).
What's Work Item?
Internally, Work item model is essentially an alias for Issue model at this point backed by the same issue
table.
Each work item has a type. For example, here's a work item that's a Task https://gitlab.com/gitlab-org/gitlab/-/work_items/121960434
Each work item type (e.g, Task
, Objective
, etc) supports a set of widgets. Task
supports assignee, labels and weight widgets (the list is not exhaustive.) Objective
might not support the same set of widgets.
Task
type supports the assignee widget and when I type /assign @euko
in the description field and update the work item, I should be assigned to the task.
How quick actions get interpreted and applied (aka how does it work?)
We define a set of quick actions for domain objects using a custom DSL:
# https://gitlab.com/gitlab-org/gitlab/-/blob/6b4b214bb2e79339dd908fbe6d1aa4f8d03ba3a7/lib/gitlab/quick_actions/issue_actions.rb#L9
desc { _('Set due date') }
explanation do |due_date|
_("Sets the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
end
execution_message do |due_date|
_("Set the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
end
params '<in 2 days | this Friday | December 31st>'
types Issue
condition do
quick_action_target.respond_to?(:due_date) &&
current_user.can?(:"set_#{quick_action_target.to_ability_name}_metadata", quick_action_target)
end
parse_params do |due_date_param|
Chronic.parse(due_date_param).try(:to_date)
end
command :due do |due_date|
if due_date
@updates[:due_date] = due_date
else
@execution_message[:due] = _('Failed to set due date because the date format is invalid.')
end
end
QuickActions::InterpretService
is the service that accepts an input containing quick action strings (raw string commands) and extracts the commands defined with the DSL mentioned earlier.
description, command_params = QuickActions::InterpretService
.new(work_item.project, current_user, {})
.execute(original_description, work_item)
-
original_description
may contains a raw input likeFoobar this is some text\n \assign @euko
. -
description
contains the input without the interpreted commands:Foobar this is some text
. -
command_params
contains the extracted parameters for updates:assignee_ids: [12312]
.
In the mutation resolver for updating a work item, we will run QuickActions::InterpretService
then assign each extracted update param to the appropriate widget available on the work item.
To continue with the example, say command_params
contained assignee_ids: [12312]
and the work item being updated had the assignee widget. Because the assignee widget knows it must handle the update parameter assignee_ids
, we will assign it to the assignee widget and so on:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110153/diffs#64bc31af50779ac0ebabc322cc4cbca88bdadd60_46_49
# Widgets have a set of quick action params that they must process.
# Map them to widget_params so they can be picked up by widget services.
work_item.work_item_type.widgets
.filter { |widget| widget.respond_to?(:quick_action_params) }
.each do |widget|
widget.quick_action_params
.filter { |param_name| command_params.key?(param_name) }
.each do |param_name|
widget_params[widget.api_symbol] ||= {}
widget_params[widget.api_symbol][param_name] = command_params.delete(param_name)
end
end
Concretely, we will execute a statement like widget_params[assignee_widget.api_symbol][:assignee_ids] = command_params.delete(param_name)
How to set up and validate locally
First, create a new work item. The most common work item in use is Task. You may also create Objective/KeyResult if you are familiar with them.
How to create a Task (work item)
Screen_Recording_2023-01-26_at_17.35.48Note the id of the created work item.
Here's a sample GraphQL query that edits the description of a work item with two quick actions.
- Change the title to
abc
- Add
@root
as an assignee
mutation UpdateWorkItem {
workItemUpdate(
input: {id: "gid://gitlab/WorkItem/640", descriptionWidget: {description: "/title abc \n/assign @root"}}
) {
workItem {
id
title
widgets {
... on WorkItemWidgetDescription {
description
}
... on WorkItemWidgetAssignees {
assignees {
nodes {
id
username
}
}
}
}
}
}
}
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Related to #382160 (closed)