Draft: custom numeric work item widget
This is a WIP exercise to implement a custom numeric widget used to register and aggregate numeric values for a work item hierarchy.
- The aggregated values are calculated on-the-fly in this implementation using
work_item_parent_links
table. One downside of this approach is sorting work items by aggregated values isn't feasible for a large hierarchy likegitlab-org
. The approach could work for some situations like with Object/Key Result WI types. We know a single hierarchy would be shallow with a limited number of Objects for a particular time horizon of interest.- Alternatively, we can materialize and save aggregated values in the database. Whenever a numeric record or a work item hierarchy tree changes, we can propagate the delta value up the tree to minimize cascading updates.
Related to #335110
Sample GraphQL APIs
Add a new numeric widget for a work item type (scoped to a namespace.)
mutation workItemCreateNumericWidget {
workItemCreateNumericWidget(
input: {
name: "Estimate",
unit: null, # Unitless
workItemTypeId: "gid://gitlab/WorkItems::Type/5", # Task
namespaceId: "gid://gitlab/Namespace/74",
aggregationMode: false,
}
) {
numericWidget {
customWidgetId
name
unit
workItemType {
id
name
}
namespace {
id
fullName
}
aggregator
}
}
}
Add a new numeric widget to work as an aggregator
mutation workItemCreateNumericWidget {
workItemCreateNumericWidget(
input: {
name: "Total Estimate",
unit: null, # Unitless
workItemTypeId: "gid://gitlab/WorkItems::Type/1", # Issue type
namespaceId: "gid://gitlab/Namespace/74",
sourceNumericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/1"
aggregationMode: true,
}
) {
numericWidget {
customWidgetId
name
unit
workItemType {
id
name
}
namespace {
id
fullName
}
aggregator
}
}
}
Query custom widgets for a group
{
group(fullPath: "umbrella") {
workItemTypes {
nodes {
id
name
customWidgets(namespaceId: "gid://gitlab/Namespace/74") {
name
... on WorkItemCustomWidgetNumeric {
customWidgetId
aggregator
unit
aggregators {
nodes {
customWidgetId
workItemType {
id
name
}
name
}
}
}
}
}
}
}
}
Query work item (of the WI type that's using numeric widget(s))
{
project(fullPath: "umbrella/umbrella-project") {
fullPath
workItems {
nodes {
id
title
workItemType {
id
name
}
priceWidget: numericWidgetRecord(
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/1"
) {
value
createdAt
updatedAt
}
estimateWidget: numericWidgetRecord(
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/2"
) {
value
createdAt
updatedAt
}
}
}
}
}
Update numeric widget(s) for a work item
mutation updateWorkItemWithCustomWidgets {
workItemUpdate(input: {
id: "gid://gitlab/WorkItem/640"
numericCustomWidgets: [
{
# for price widget
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/1"
value: 80.0
},
{
# for estimate widget
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/2"
value: 3.0
}
]
}) {
workItem {
id
priceWidget: numericWidgetRecord(
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/1"
) {
value
createdAt
updatedAt
numericWidget {
customWidgetId
aggregator
}
}
estimateWidget: numericWidgetRecord(
numericWidgetId: "gid://gitlab/WorkItems::CustomWidgets::Numeric/2"
) {
value
createdAt
updatedAt
numericWidget {
customWidgetId
aggregator
}
}
}
}
}
Edited by euko