diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index c8abf8ee4aae44bfafad04d3d0f5daf106690532..d264cb3907d5e1b01ab82ef8a4aa92085479aa8e 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -21,6 +21,7 @@ const boardDefaults = { iteration: {}, assignee: {}, weight: null, + workItemTypes: [], hideBacklogList: false, hideClosedList: false, }; @@ -273,12 +274,12 @@ export default { } else { this.$emit('addBoard', board); } + this.isDisabled = true; this.close(); } catch (error) { setError({ error, message: this.$options.i18n.saveErrorMessage }); } finally { this.isLoading = false; - this.isDisabled = true; } } }, @@ -325,6 +326,9 @@ export default { weight, }; }, + setWorkItemTypes(types) { + this.board.workItemTypes = types.map((type) => type.id); + }, }, formType, }; @@ -396,6 +400,7 @@ export default { @set-assignee="setAssignee" @set-milestone="setMilestone" @set-weight="setWeight" + @set-work-item-types="setWorkItemTypes" /> diff --git a/app/graphql/types/boards/board_issue_input_base_type.rb b/app/graphql/types/boards/board_issue_input_base_type.rb index 3f0e38ade41586f3c97d047960f1ea3dba83e92a..6c42d61f6398a9e7c0aa6e980540bfe07b0e2a4c 100644 --- a/app/graphql/types/boards/board_issue_input_base_type.rb +++ b/app/graphql/types/boards/board_issue_input_base_type.rb @@ -25,6 +25,10 @@ class BoardIssueInputBaseType < BoardIssuableInputBaseType description: 'Filter by the given issue types.', required: false + argument :work_item_types, [GraphQL::Types::String], + required: false, + description: 'Filter by work item types.' + argument :milestone_wildcard_id, ::Types::MilestoneWildcardIdEnum, required: false, description: 'Filter by milestone ID wildcard.' diff --git a/db/migrate/20251212041921_add_work_item_types_to_boards.rb b/db/migrate/20251212041921_add_work_item_types_to_boards.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf183bd77a98c85e8873f7831cfc5441b091c12c --- /dev/null +++ b/db/migrate/20251212041921_add_work_item_types_to_boards.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddWorkItemTypesToBoards < Gitlab::Database::Migration[2.3] + milestone '18.8' + + def change + add_column :boards, :work_item_types, :text, array: true, default: [], null: false + end +end diff --git a/db/structure.sql b/db/structure.sql index 2188d6e075cd59ee6dc3c7eb25bedadf5050a377..ec4c123e0437c064ce68c75f0c707d483bb3c5b4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13734,6 +13734,7 @@ CREATE TABLE boards ( hide_closed_list boolean DEFAULT false NOT NULL, iteration_id bigint, iteration_cadence_id bigint, + work_item_types text[] DEFAULT '{}'::text[] NOT NULL, CONSTRAINT check_a60857cc50 CHECK ((num_nonnulls(group_id, project_id) = 1)) ); diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 26ed284ec1e8a16e75b77f6923e94d81a63ab451..2d3e4a0e20f62ed0967bf8b5502e47a097661efa 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -5030,6 +5030,7 @@ Input type: `CreateBoardInput` | `name` | [`String`](#string) | Board name. | | `projectPath` | [`ID`](#id) | Full path of the project with which the resource is associated. | | `weight` | [`Int`](#int) | Weight value to be assigned to the board. | +| `workItemTypes` | [`[String!]`](#string) | Work item types to filter by on the board. | #### Fields @@ -6968,6 +6969,7 @@ Input type: `EpicBoardCreateInput` | `labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. | | `labels` | [`[String!]`](#string) | Labels of the issue. | | `name` | [`String`](#string) | Board name. | +| `workItemTypes` | [`[String!]`](#string) | Work item types to filter by on the board. | #### Fields @@ -7050,6 +7052,7 @@ Input type: `EpicBoardUpdateInput` | `labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. | | `labels` | [`[String!]`](#string) | Labels of the issue. | | `name` | [`String`](#string) | Board name. | +| `workItemTypes` | [`[String!]`](#string) | Work item types to filter by on the board. | #### Fields @@ -13321,6 +13324,7 @@ Input type: `UpdateBoardInput` | `milestoneId` | [`MilestoneID`](#milestoneid) | ID of milestone to be assigned to the board. | | `name` | [`String`](#string) | Board name. | | `weight` | [`Int`](#int) | Weight value to be assigned to the board. | +| `workItemTypes` | [`[String!]`](#string) | Work item types to filter by on the board. | #### Fields @@ -27024,6 +27028,7 @@ Represents a project or group issue board. | `webPath` | [`String!`](#string) | Web path of the board. | | `webUrl` | [`String!`](#string) | Web URL of the board. | | `weight` | [`Int`](#int) | Weight of the board. | +| `workItemTypes` | [`[String!]`](#string) | Work item types to filter by on the board. | #### Fields with arguments @@ -58523,6 +58528,7 @@ Input type for filtering projects by security attributes. | `types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. | | `weight` | [`String`](#string) | Filter by weight. | | `weightWildcardId` | [`WeightWildcardId`](#weightwildcardid) | Filter by weight ID wildcard. Incompatible with weight. | +| `workItemTypes` | [`[String!]`](#string) | Filter by work item types. | ### `BranchProtectionInput` @@ -58975,6 +58981,7 @@ Defines which user roles, users, or groups can merge into a protected branch. | `releaseTag` | [`String`](#string) | Filter by release tag. | | `types` | [`[IssueType!]`](#issuetype) | Filter by the given issue types. | | `weight` | [`String`](#string) | Filter by weight. | +| `workItemTypes` | [`[String!]`](#string) | Filter by work item types. | ### `NegatedComplianceFrameworkFilters` diff --git a/ee/app/assets/javascripts/boards/boards_util.js b/ee/app/assets/javascripts/boards/boards_util.js index ea4de8f4038a9059a949d1c5683e41dc7774a817..d749c0e14a3a464f981071ac15d2a6c02a34cd77 100644 --- a/ee/app/assets/javascripts/boards/boards_util.js +++ b/ee/app/assets/javascripts/boards/boards_util.js @@ -213,6 +213,15 @@ export function transformBoardConfig(boardConfig) { let updatedFilterPath = objectToQuery(updatedBoardConfig); const filterPath = updatedFilterPath ? updatedFilterPath.split('&') : []; + boardConfig.workItemTypes?.forEach((type) => { + // eslint-disable-next-line @gitlab/require-i18n-strings + const param = `types=${type}`; + + if (!passedFilterParams.types?.includes(type)) { + filterPath.push(param); + } + }); + boardConfig.labels?.forEach((label) => { const labelTitle = encodeURIComponent(label.title); const param = `label_name[]=${labelTitle}`; @@ -276,6 +285,10 @@ export const FiltersInfo = { transform: (v) => v === HealthStatusFilterType.any || v === HealthStatusFilterType.none ? v.toUpperCase() : v, }, + types: { + negatedSupport: false, + transform: (val) => val.toUpperCase(), + }, }; export function getBoardQuery(boardType, isEpicBoard) { diff --git a/ee/app/assets/javascripts/boards/components/board_filtered_search.vue b/ee/app/assets/javascripts/boards/components/board_filtered_search.vue index 1946e3535060ae7b138350ecbfb2b4746670827f..555d95cc3afc6cd641312d64d5b1b29b6eb4f7a0 100644 --- a/ee/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/ee/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -38,6 +38,7 @@ export default { labels: board.labels || [], labelIds: board.labels?.map((label) => label.id) || [], weight: board.weight, + workItemTypes: board.workItemTypes || [], }; }, }, diff --git a/ee/app/assets/javascripts/boards/components/board_form.vue b/ee/app/assets/javascripts/boards/components/board_form.vue index 1a5fcbdd872d7fa5d3bae1241959eb715c1f0717..e02aff0930a7c7286950ea4bbd627f108a510d71 100644 --- a/ee/app/assets/javascripts/boards/components/board_form.vue +++ b/ee/app/assets/javascripts/boards/components/board_form.vue @@ -32,6 +32,7 @@ export default { boardScopeMutationVariables() { return { labelIds: this.board.labels.map(fullLabelId), + workItemTypes: this.board.workItemTypes || [], ...(this.isIssueBoard && this.issueBoardScopeMutationVariables), }; }, diff --git a/ee/app/assets/javascripts/boards/components/board_scope.vue b/ee/app/assets/javascripts/boards/components/board_scope.vue index bc240e1bab4c8abd052f322f81d72da4a4687ce7..d8966c12a732e4eefe44228e82dcfac24c980483 100644 --- a/ee/app/assets/javascripts/boards/components/board_scope.vue +++ b/ee/app/assets/javascripts/boards/components/board_scope.vue @@ -6,6 +6,7 @@ import BoardLabelsSelect from './labels_select.vue'; import BoardIterationSelect from './iteration_select.vue'; import BoardMilestoneSelect from './milestone_select.vue'; import BoardWeightSelect from './weight_select.vue'; +import WorkItemTypeSelect from './work_item_type_select.vue'; export default { components: { @@ -15,6 +16,7 @@ export default { BoardIterationSelect, BoardMilestoneSelect, BoardWeightSelect, + WorkItemTypeSelect, }, inject: ['isIssueBoard'], props: { @@ -79,6 +81,13 @@ export default { {{ scopeText }}