Skip to content

Draft: POC Static default status for work items

What does this MR do and why?

This MR is intended to showcase the "hard-coded system defined statuses" approach outlined in this comment for work items custom statuses. Iteration 1 aims to deliver system defined statuses for tasks while iterations 2 - 3 will focus on rolling system defined statuses out to other work item types and allowing users to customize statuses on the top level namespace (see details in proposed architecture).

This POC doesn't implement all classes mentioned below. It focusses on iteration 1 and highlighting in comments how the code could work for the future iterations.

We're currently discussing whether status should only be available in GitLab Premium and above, so some files in this POC are in /ee folder and some not. These will be moved to the correct place once a decision has been made.

To me it's also unclear whether the static association class and the static model concern should live in lib/gem_extensions or whether there's a better place.

Proposed architecture

The proposed architecture (for iterations 1 to 3) contains WorkItems::Statuses::SystemDefined::Lifecycle and WorkItems::Statuses::SystemDefined::Status which will hold items in code. We don't expect them to change although we'd still be able to change attributes with ease.

By not using separate cluster wide tables we get around specifying how data duplication for cluster wide tables should be done. Additionally, we don't expect these "models" to hold a lot of items (probably < 20). The current approach contains no cluster wide tables, all tables are cell local.

When the user wants to change existing system defined statuses (in iterations 2-3) we'll create copies of system defined statuses and lifecycles and tie them to the top level namespace the user is working on. From that moment on, they will use "custom" statuses only. We'll save that decision on the root namespace level.

In iterations 2-3 we'll introduce lifecycles as a concept to centralize status workflows and roll them out to work item types. We can take this concept also for system defined statuses to

  1. allow us to provide different system defined statuses for work item types (for example requirements and Service Desk tickets)
  2. use the same API for both system defined statuses and custom statuses

This diagram represents the "new" proposal which has minor changes compared to the implemented structure in this POC.

Display status in frontend and set status with quick action

To use this feature the POC also contains fetching the data from the backend, displaying it in a new widget and setting the status via the /status quick action. The quick action is not properly implemented, so you need to reload the page to see the updated status.

Screen_Recording_2025-01-17_at_17.44.16

Use custom status in frontend

When custom status and custom lifecycle (and connection to work item type via WorkItems::TypeCustomLifecycle) are set.

Screen_Recording_2025-01-28_at_14.54.43

GraphQL queries

Fetch the status of a given work item

Expand query
query namespaceWorkItem($fullPath: ID!, $iid: String!) {
  workspace: namespace(fullPath: $fullPath) {
    id
    workItem(iid: $iid) {
      ...WorkItem
      __typename
    }
    __typename
  }
}

fragment WorkItem on WorkItem {
  id
  iid
  workItemType {
    id
    name
    __typename
  }
  widgets {
    ...WorkItemWidgets
    __typename
  }
  __typename
}

fragment WorkItemWidgets on WorkItemWidget {
  type
  ... on WorkItemWidgetCustomStatus {
    id
    name
    iconName
    color
    position
    __typename
  }
  __typename
}

With variables:

{
  "fullPath": "flightjs/Flight",
  "iid": "107"
}
Expand result (system defined status, uses old namespaces)
{
  "data": {
    "workspace": {
      "id": "gid://gitlab/Namespaces::ProjectNamespace/34",
      "workItem": {
        "id": "gid://gitlab/WorkItem/732",
        "iid": "107",
        "archived": false,
        "title": "This is a task",
        "state": "OPEN",
        "description": null,
        "confidential": false,
        "createdAt": "2025-01-14T14:31:41Z",
        "closedAt": null,
        "webUrl": "http://127.0.0.1:3000/flightjs/Flight/-/work_items/107",
        "reference": "flightjs/Flight#107",
        "createNoteEmail": "[REDACTED]",
        "project": {
          "id": "gid://gitlab/Project/7",
          "__typename": "Project"
        },
        "namespace": {
          "id": "gid://gitlab/Namespaces::ProjectNamespace/34",
          "fullPath": "flightjs/Flight",
          "name": "Flight",
          "__typename": "Namespace"
        },
        "author": {
          "id": "gid://gitlab/User/1",
          "avatarUrl": "https://www.gravatar.com/avatar/258d8dc916db8cea2cafb6c3cd0cb0246efe061421dbd83ec3a350428cabda4f?s=80&d=identicon",
          "name": "Administrator",
          "username": "root",
          "webUrl": "http://127.0.0.1:3000/root",
          "webPath": "/root",
          "__typename": "UserCore"
        },
        "workItemType": {
          "id": "gid://gitlab/WorkItems::Type/5",
          "name": "Task",
          "iconName": "issue-type-task",
          "__typename": "WorkItemType"
        },
        "userPermissions": {
          "deleteWorkItem": true,
          "updateWorkItem": true,
          "adminParentLink": true,
          "setWorkItemMetadata": true,
          "createNote": true,
          "adminWorkItemLink": true,
          "markNoteAsInternal": true,
          "__typename": "WorkItemPermissions"
        },
        "widgets": [
          // removed empty widgets
          {
            "type": "CUSTOM_STATUS",
            "id": "gid://gitlab/WorkItems::SystemDefined::Status/4",
            "name": "Won't do",
            "iconName": "status-cancelled",
            "color": "#ae1901",
            "position": 0,
            "__typename": "WorkItemWidgetCustomStatus"
          }
        ],
        "__typename": "WorkItem"
      },
      "__typename": "Namespace"
    }
  },
  "correlationId": "01JHTJ1C2Z87KGR37Q8135DH31"
}
Expand result for custom status
{
  "data": {
    "workspace": {
      "id": "gid://gitlab/Namespaces::ProjectNamespace/34",
      "workItem": {
        "id": "gid://gitlab/WorkItem/732",
        "iid": "107",
        "workItemType": {
          "id": "gid://gitlab/WorkItems::Type/5",
          "name": "Task",
          "__typename": "WorkItemType"
        },
        "widgets": [
          {
            "type": "ASSIGNEES",
            "__typename": "WorkItemWidgetAssignees"
          },
          {
            "type": "LABELS",
            "__typename": "WorkItemWidgetLabels"
          },
          {
            "type": "DESCRIPTION",
            "__typename": "WorkItemWidgetDescription"
          },
          {
            "type": "HIERARCHY",
            "__typename": "WorkItemWidgetHierarchy"
          },
          {
            "type": "START_AND_DUE_DATE",
            "__typename": "WorkItemWidgetStartAndDueDate"
          },
          {
            "type": "MILESTONE",
            "__typename": "WorkItemWidgetMilestone"
          },
          {
            "type": "NOTES",
            "__typename": "WorkItemWidgetNotes"
          },
          {
            "type": "ITERATION",
            "__typename": "WorkItemWidgetIteration"
          },
          {
            "type": "WEIGHT",
            "__typename": "WorkItemWidgetWeight"
          },
          {
            "type": "NOTIFICATIONS",
            "__typename": "WorkItemWidgetNotifications"
          },
          {
            "type": "CURRENT_USER_TODOS",
            "__typename": "WorkItemWidgetCurrentUserTodos"
          },
          {
            "type": "AWARD_EMOJI",
            "__typename": "WorkItemWidgetAwardEmoji"
          },
          {
            "type": "LINKED_ITEMS",
            "__typename": "WorkItemWidgetLinkedItems"
          },
          {
            "type": "PARTICIPANTS",
            "__typename": "WorkItemWidgetParticipants"
          },
          {
            "type": "TIME_TRACKING",
            "__typename": "WorkItemWidgetTimeTracking"
          },
          {
            "type": "CUSTOM_STATUS",
            "id": "gid://gitlab/WorkItems::Statuses::Custom::Status/1",
            "name": "Whocka",
            "iconName": "status-running",
            "color": "#00FF00",
            "position": 0,
            "__typename": "WorkItemWidgetCustomStatus"
          }
        ],
        "__typename": "WorkItem"
      },
      "__typename": "Namespace"
    }
  }
}

Fetch available statuses per work item type

Expand query
query namespaceWorkItemTypes($fullPath: ID!, $name: IssueType) {
  workspace: namespace(fullPath: $fullPath) {
    id
    workItemTypes(name: $name) {
      nodes {
        id
        name
        widgetDefinitions {
          type
          ... on WorkItemWidgetDefinitionCustomStatus {
            allowedCustomStatuses {
              nodes {
                id
                name
                iconName
                color
                position
              }
            }
          }
        }
        __typename
      }
      __typename
    }
    __typename
  }
}

With variables:

{
  "fullPath": "flightjs/Flight",
  "name": "TASK"
}
Expand result
{
  "data": {
    "workspace": {
      "id": "gid://gitlab/Namespaces::ProjectNamespace/34",
      "workItemTypes": {
        "nodes": [
          {
            "id": "gid://gitlab/WorkItems::Type/5",
            "name": "Task",
            "widgetDefinitions": [
              // Removed other definitions
              {
                "type": "CUSTOM_STATUS",
                "allowedCustomStatuses": {
                  "nodes": [
                    {
                      "id": "gid://gitlab/WorkItems::SystemDefined::Status/1",
                      "name": "To do",
                      "iconName": "status-waiting",
                      "color": "#535158",
                      "position": 0
                    },
                    {
                      "id": "gid://gitlab/WorkItems::SystemDefined::Status/2",
                      "name": "In progress",
                      "iconName": "status-running",
                      "color": "#0b5cad",
                      "position": 0
                    },
                    {
                      "id": "gid://gitlab/WorkItems::SystemDefined::Status/3",
                      "name": "Done",
                      "iconName": "status-success",
                      "color": "#23663b",
                      "position": 0
                    },
                    {
                      "id": "gid://gitlab/WorkItems::SystemDefined::Status/4",
                      "name": "Won't do",
                      "iconName": "status-cancelled",
                      "color": "#ae1901",
                      "position": 0
                    },
                    {
                      "id": "gid://gitlab/WorkItems::SystemDefined::Status/5",
                      "name": "Duplicate",
                      "iconName": "status-cancelled",
                      "color": "#ae1901",
                      "position": 10
                    }
                  ]
                }
              }
            ],
            "__typename": "WorkItemType"
          }
        ],
        "__typename": "WorkItemTypeConnection"
      },
      "__typename": "Namespace"
    }
  },
  "correlationId": "01JHTJ488TCP36VQQKFH0Y4BW4"
}

References

Please include cross links to any resources that are relevant to this MR. This will give reviewers and future readers helpful context to give an efficient review of the changes introduced.

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Screenshots or screen recordings

Screenshots are required for UI changes, and strongly recommended for all other merge requests.

See above

How to set up and validate locally

Numbered steps to set up and validate the change are strongly suggested.

  1. Create a task and access it via the work_items/iid URL
  2. Now set a status /status to do and reload the page
  3. See the status is now visible.
  4. Change the status to something differen like /status won't do or /status in progress.
Edited by Marc Saleiko

Merge request reports

Loading