Support creating board lists with status values

What does this MR do and why?

As part of the configurable work item statuses initiative, this MR adds support to create board lists with system-defined or custom status values.

Changes

  • Added system_defined_status_id and custom_status_id columns to lists table with appropriate indexes and foreign keys
  • Updated the GraphQL boardListCreate mutation to support status-based lists
  • Added validation logic to ensure:
    • Only one status type (system-defined or custom) can be used per list
    • No duplicate status lists on the same board
    • Status lists are only available with appropriate license

Checks

This ensures board lists tied to status are only created when the entire status feature set is active, all of the following has to be enabled:

  • The board_status_lists licensed feature
  • The work_item_status licensed features
  • The work_item_status_feature_flag feature flag

Notes

  • This MR builds on top of the placeholder API introduced via Add statusId argument to boardListCreate mutation MR.
  • This MR doesn't yet include functionality to query status details for a board list once it's created. The placeholder API still returns nil. I'll address that in a separate MR shortly.
  • As per our decision registry, we're implementing support for statuses in legacy issue boards, since the new board experience won't be ready for GA by July 2025.

References

Screenshots or screen recordings

backend only

How to set up and validate locally

  1. Enable the work_item_status_feature_flag feature flag.
  2. Create a new issue board under flightjs/Flight project.
  3. Use the mutation below to create a new issue board list based on the following scenarios:

Scenario 1: With a valid system-defined status

View mutation
mutation createBoardListEE(
  $boardId: BoardID!
  $backlog: Boolean
  $labelId: LabelID
  $milestoneId: MilestoneID
  $iterationId: IterationID
  $assigneeId: UserID
  $statusId: WorkItemsStatusesStatusID
  $position: Int
) {
  boardListCreate(
    input: {
      boardId: $boardId
      backlog: $backlog
      labelId: $labelId
      milestoneId: $milestoneId
      iterationId: $iterationId
      assigneeId: $assigneeId
      statusId: $statusId
      position: $position
    }
  ) {
    list {
      id
      title
    }
    errors
  }
}
{
  "boardId": "gid://gitlab/Board/6", # TODO: Replace with your board GID
  "statusId": "gid://gitlab/WorkItems::Statuses::SystemDefined::Status/1"
}
View response
{
  "data": {
    "boardListCreate": {
      "list": {
        "id": "gid://gitlab/List/30",
        "title": "To do"
      },
      "errors": []
    }
  },
  "correlationId": "01JV1FGVD3GWXCA0H4JDEB75KC"
}

Scenario 2: With an invalid system-defined status

View mutation
mutation createBoardListEE(
  $boardId: BoardID!
  $backlog: Boolean
  $labelId: LabelID
  $milestoneId: MilestoneID
  $iterationId: IterationID
  $assigneeId: UserID
  $statusId: WorkItemsStatusesStatusID
  $position: Int
) {
  boardListCreate(
    input: {
      boardId: $boardId
      backlog: $backlog
      labelId: $labelId
      milestoneId: $milestoneId
      iterationId: $iterationId
      assigneeId: $assigneeId
      statusId: $statusId
      position: $position
    }
  ) {
    list {
      id
      title
    }
    errors
  }
}
{
  "boardId": "gid://gitlab/Board/6", # TODO: Replace with your board GID
  "statusId": "gid://gitlab/WorkItems::Statuses::SystemDefined::Status/10"
}
View response
{
  "data": {
    "boardListCreate": {
      "list": null,
      "errors": [
        "Status not found"
      ]
    }
  },
  "correlationId": "01JV1CR5V7E0VSDTV6N346FGAG"
}

Scenario 3: With a valid custom status

Prerequisites

  1. Create a custom lifecycle and statuses via Rails console.
Click to expand
  1. Find a namespace
project = Project.find(7) # flightjs/Flight
namespace = project.root_ancestor
  1. Create a custom status for a given namespace
WorkItems::Statuses::Custom::Status.create!(
  namespace: namespace,
  name: "To do",
  color: "#737278",
  category: :to_do
)
  1. Run the mutation and verify the response
View mutation
mutation createBoardListEE(
  $boardId: BoardID!
  $backlog: Boolean
  $labelId: LabelID
  $milestoneId: MilestoneID
  $iterationId: IterationID
  $assigneeId: UserID
  $statusId: WorkItemsStatusesStatusID
  $position: Int
) {
  boardListCreate(
    input: {
      boardId: $boardId
      backlog: $backlog
      labelId: $labelId
      milestoneId: $milestoneId
      iterationId: $iterationId
      assigneeId: $assigneeId
      statusId: $statusId
      position: $position
    }
  ) {
    list {
      id
      title
    }
    errors
  }
}
{
  "boardId": "gid://gitlab/Board/6", # TODO: Replace with your board GID
  "statusId": "gid://gitlab/WorkItems::Statuses::Custom::Status/60" # TODO: Replace with your ID
}
View response
{
  "data": {
    "boardListCreate": {
      "list": {
        "id": "gid://gitlab/List/31",
        "title": "To do"
      },
      "errors": []
    }
  },
  "correlationId": "01JV3X88SR47QAJEFN25TE8693"
}

Scenario 4: With an invalid custom status

View mutation
mutation createBoardListEE(
  $boardId: BoardID!
  $backlog: Boolean
  $labelId: LabelID
  $milestoneId: MilestoneID
  $iterationId: IterationID
  $assigneeId: UserID
  $statusId: WorkItemsStatusesStatusID
  $position: Int
) {
  boardListCreate(
    input: {
      boardId: $boardId
      backlog: $backlog
      labelId: $labelId
      milestoneId: $milestoneId
      iterationId: $iterationId
      assigneeId: $assigneeId
      statusId: $statusId
      position: $position
    }
  ) {
    list {
      id
      title
    }
    errors
  }
}
{
  "boardId": "gid://gitlab/Board/6", # TODO: Replace with your board GID
  "statusId": "gid://gitlab/WorkItems::Statuses::Custom::Status/100"
}
View response
{
  "data": {
    "boardListCreate": {
      "list": null,
      "errors": [
        "Status not found"
      ]
    }
  },
  "correlationId": "01JV1F7K4MRW6Z070ZEXCVT763"
}

Post-testing cleanup

⚠️ Since there are still a few tasks remaining to fully support custom statuses, please remember to clean up any custom status-related records after testing. You can use the command below to remove custom statuses and all associated records:

WorkItems::Statuses::Custom::Status.destroy_all

MR acceptance checklist

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

Edited by Agnes Slota

Merge request reports

Loading