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
- allow us to provide different system defined statuses for work item types (for example requirements and Service Desk tickets)
- 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.
- Create a task and access it via the
work_items/iid
URL - Now set a status
/status to do
and reload the page - See the status is now visible.
- Change the status to something differen like
/status won't do
or/status in progress
.