Skip to content

Draft: custom numeric work item widget

euko requested to merge poc-numeric-agg-widget into master

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 like gitlab-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

Merge request reports