summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/boards
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 13:37:47 +0000
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/boards
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
downloadgitlab-ce-aee0a117a889461ce8ced6fcf73207fe017f1d99.tar.gz
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/boards_util.js12
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue103
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue16
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue39
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue3
-rw-r--r--app/assets/javascripts/boards/components/issue_board_filtered_search.vue85
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue173
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue75
-rw-r--r--app/assets/javascripts/boards/constants.js14
-rw-r--r--app/assets/javascripts/boards/graphql/board_labels.query.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/board_lists.query.graphql8
-rw-r--r--app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql6
-rw-r--r--app/assets/javascripts/boards/graphql/group_board.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/group_board_members.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/group_boards.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/group_projects.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql11
-rw-r--r--app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/lists_issues.query.graphql6
-rw-r--r--app/assets/javascripts/boards/graphql/project_board.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/project_board_members.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/project_boards.query.graphql1
-rw-r--r--app/assets/javascripts/boards/graphql/project_milestones.query.graphql1
-rw-r--r--app/assets/javascripts/boards/index.js3
-rw-r--r--app/assets/javascripts/boards/mount_filtered_search_issue_boards.js3
-rw-r--r--app/assets/javascripts/boards/stores/actions.js133
34 files changed, 342 insertions, 373 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index e6c91c7ac1f..7e4d3ebb686 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,6 +1,6 @@
import { sortBy, cloneDeep } from 'lodash';
import { isGid } from '~/graphql_shared/utils';
-import { ListType, MilestoneIDs } from './constants';
+import { ListType, MilestoneIDs, AssigneeFilterType, MilestoneFilterType } from './constants';
export function getMilestone() {
return null;
@@ -186,6 +186,7 @@ export function isListDraggable(list) {
export const FiltersInfo = {
assigneeUsername: {
negatedSupport: true,
+ remap: (k, v) => (v === AssigneeFilterType.any ? 'assigneeWildcardId' : k),
},
assigneeId: {
// assigneeId should be renamed to assigneeWildcardId.
@@ -204,6 +205,11 @@ export const FiltersInfo = {
},
milestoneTitle: {
negatedSupport: true,
+ remap: (k, v) => (Object.values(MilestoneFilterType).includes(v) ? 'milestoneWildcardId' : k),
+ },
+ milestoneWildcardId: {
+ negatedSupport: true,
+ transform: (val) => val.toUpperCase(),
},
myReactionEmoji: {
negatedSupport: true,
@@ -214,6 +220,10 @@ export const FiltersInfo = {
types: {
negatedSupport: true,
},
+ confidential: {
+ negatedSupport: false,
+ transform: (val) => val === 'yes',
+ },
search: {
negatedSupport: false,
},
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index b6ccc6a00fe..ea80496c3f5 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -13,7 +13,7 @@ import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
import { sprintf, __, n__ } from '~/locale';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { ListType } from '../constants';
import eventHub from '../eventhub';
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index 54668c9e88e..f89f8e5feb8 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -4,7 +4,6 @@ import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { __, sprintf } from '~/locale';
-import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
@@ -26,7 +25,6 @@ export default {
SidebarDateWidget,
SidebarConfidentialityWidget,
BoardSidebarTimeTracker,
- BoardSidebarLabelsSelect,
SidebarLabelsWidget,
SidebarSubscriptionsWidget,
SidebarDropdownWidget,
@@ -210,7 +208,6 @@ export default {
data-testid="sidebar-due-date"
/>
<sidebar-labels-widget
- v-if="glFeatures.labelsWidget"
class="block labels"
data-testid="sidebar-labels"
:iid="activeBoardItem.iid"
@@ -230,7 +227,6 @@ export default {
>
{{ __('None') }}
</sidebar-labels-widget>
- <board-sidebar-labels-select v-else class="block labels" />
<sidebar-weight-widget
v-if="weightFeatureAvailable"
:iid="activeBoardItem.iid"
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
index 6e6ada2d109..09ec385bbba 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -1,7 +1,7 @@
<script>
import { pickBy, isEmpty } from 'lodash';
import { mapActions } from 'vuex';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -39,30 +39,33 @@ export default {
assigneeUsername,
search,
milestoneTitle,
+ iterationId,
types,
weight,
epicId,
myReactionEmoji,
+ releaseTag,
+ confidential,
} = this.filterParams;
const filteredSearchValue = [];
if (authorUsername) {
filteredSearchValue.push({
- type: 'author_username',
+ type: 'author',
value: { data: authorUsername, operator: '=' },
});
}
if (assigneeUsername) {
filteredSearchValue.push({
- type: 'assignee_username',
+ type: 'assignee',
value: { data: assigneeUsername, operator: '=' },
});
}
if (types) {
filteredSearchValue.push({
- type: 'types',
+ type: 'type',
value: { data: types, operator: '=' },
});
}
@@ -70,7 +73,7 @@ export default {
if (labelName?.length) {
filteredSearchValue.push(
...labelName.map((label) => ({
- type: 'label_name',
+ type: 'label',
value: { data: label, operator: '=' },
})),
);
@@ -78,11 +81,18 @@ export default {
if (milestoneTitle) {
filteredSearchValue.push({
- type: 'milestone_title',
+ type: 'milestone',
value: { data: milestoneTitle, operator: '=' },
});
}
+ if (iterationId) {
+ filteredSearchValue.push({
+ type: 'iteration',
+ value: { data: iterationId, operator: '=' },
+ });
+ }
+
if (weight) {
filteredSearchValue.push({
type: 'weight',
@@ -92,32 +102,53 @@ export default {
if (myReactionEmoji) {
filteredSearchValue.push({
- type: 'my_reaction_emoji',
+ type: 'my-reaction',
value: { data: myReactionEmoji, operator: '=' },
});
}
+ if (releaseTag) {
+ filteredSearchValue.push({
+ type: 'release',
+ value: { data: releaseTag, operator: '=' },
+ });
+ }
+
+ if (confidential !== undefined) {
+ filteredSearchValue.push({
+ type: 'confidential',
+ value: { data: confidential },
+ });
+ }
+
if (epicId) {
filteredSearchValue.push({
- type: 'epic_id',
+ type: 'epic',
value: { data: epicId, operator: '=' },
});
}
if (this.filterParams['not[authorUsername]']) {
filteredSearchValue.push({
- type: 'author_username',
+ type: 'author',
value: { data: this.filterParams['not[authorUsername]'], operator: '!=' },
});
}
if (this.filterParams['not[milestoneTitle]']) {
filteredSearchValue.push({
- type: 'milestone_title',
+ type: 'milestone',
value: { data: this.filterParams['not[milestoneTitle]'], operator: '!=' },
});
}
+ if (this.filterParams['not[iteration_id]']) {
+ filteredSearchValue.push({
+ type: 'iteration_id',
+ value: { data: this.filterParams['not[iteration_id]'], operator: '!=' },
+ });
+ }
+
if (this.filterParams['not[weight]']) {
filteredSearchValue.push({
type: 'weight',
@@ -127,7 +158,7 @@ export default {
if (this.filterParams['not[assigneeUsername]']) {
filteredSearchValue.push({
- type: 'assignee_username',
+ type: 'assignee',
value: { data: this.filterParams['not[assigneeUsername]'], operator: '!=' },
});
}
@@ -135,7 +166,7 @@ export default {
if (this.filterParams['not[labelName]']) {
filteredSearchValue.push(
...this.filterParams['not[labelName]'].map((label) => ({
- type: 'label_name',
+ type: 'label',
value: { data: label, operator: '!=' },
})),
);
@@ -143,25 +174,32 @@ export default {
if (this.filterParams['not[types]']) {
filteredSearchValue.push({
- type: 'types',
+ type: 'type',
value: { data: this.filterParams['not[types]'], operator: '!=' },
});
}
if (this.filterParams['not[epicId]']) {
filteredSearchValue.push({
- type: 'epic_id',
+ type: 'epic',
value: { data: this.filterParams['not[epicId]'], operator: '!=' },
});
}
if (this.filterParams['not[myReactionEmoji]']) {
filteredSearchValue.push({
- type: 'my_reaction_emoji',
+ type: 'my-reaction',
value: { data: this.filterParams['not[myReactionEmoji]'], operator: '!=' },
});
}
+ if (this.filterParams['not[releaseTag]']) {
+ filteredSearchValue.push({
+ type: 'release',
+ value: { data: this.filterParams['not[releaseTag]'], operator: '!=' },
+ });
+ }
+
if (search) {
filteredSearchValue.push(search);
}
@@ -179,8 +217,10 @@ export default {
weight,
epicId,
myReactionEmoji,
+ iterationId,
+ releaseTag,
+ confidential,
} = this.filterParams;
-
let notParams = {};
if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
@@ -194,6 +234,8 @@ export default {
'not[weight]': this.filterParams.not.weight,
'not[epic_id]': this.filterParams.not.epicId,
'not[my_reaction_emoji]': this.filterParams.not.myReactionEmoji,
+ 'not[iteration_id]': this.filterParams.not.iterationId,
+ 'not[release_tag]': this.filterParams.not.releaseTag,
},
undefined,
);
@@ -205,11 +247,14 @@ export default {
'label_name[]': labelName,
assignee_username: assigneeUsername,
milestone_title: milestoneTitle,
+ iteration_id: iterationId,
search,
types,
weight,
- epic_id: getIdFromGraphQLId(epicId),
+ epic_id: isGid(epicId) ? getIdFromGraphQLId(epicId) : epicId,
my_reaction_emoji: myReactionEmoji,
+ release_tag: releaseTag,
+ confidential,
};
},
},
@@ -246,30 +291,39 @@ export default {
filters.forEach((filter) => {
switch (filter.type) {
- case 'author_username':
+ case 'author':
filterParams.authorUsername = filter.value.data;
break;
- case 'assignee_username':
+ case 'assignee':
filterParams.assigneeUsername = filter.value.data;
break;
- case 'types':
+ case 'type':
filterParams.types = filter.value.data;
break;
- case 'label_name':
+ case 'label':
labels.push(filter.value.data);
break;
- case 'milestone_title':
+ case 'milestone':
filterParams.milestoneTitle = filter.value.data;
break;
+ case 'iteration':
+ filterParams.iterationId = filter.value.data;
+ break;
case 'weight':
filterParams.weight = filter.value.data;
break;
- case 'epic_id':
+ case 'epic':
filterParams.epicId = filter.value.data;
break;
- case 'my_reaction_emoji':
+ case 'my-reaction':
filterParams.myReactionEmoji = filter.value.data;
break;
+ case 'release':
+ filterParams.releaseTag = filter.value.data;
+ break;
+ case 'confidential':
+ filterParams.confidential = filter.value.data;
+ break;
case 'filtered-search-term':
if (filter.value.data) plainText.push(filter.value.data);
break;
@@ -285,6 +339,7 @@ export default {
if (plainText.length) {
filterParams.search = plainText.join(' ');
}
+
return filterParams;
},
},
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 47dffc985aa..e4c3c3206a8 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -6,6 +6,7 @@ import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_opt
import { sprintf, __ } from '~/locale';
import defaultSortableConfig from '~/sortable/sortable_config';
import Tracking from '~/tracking';
+import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
import { toggleFormEventPrefix, DraggableItemTypes } from '../constants';
import eventHub from '../eventhub';
import BoardCard from './board_card.vue';
@@ -50,11 +51,22 @@ export default {
showEpicForm: false,
};
},
+ apollo: {
+ boardList: {
+ query: listQuery,
+ variables() {
+ return {
+ id: this.list.id,
+ filters: this.filterParams,
+ };
+ },
+ },
+ },
computed: {
- ...mapState(['pageInfoByListId', 'listsFlags']),
+ ...mapState(['pageInfoByListId', 'listsFlags', 'filterParams']),
...mapGetters(['isEpicBoard']),
listItemsCount() {
- return this.isEpicBoard ? this.list.epicsCount : this.list.issuesCount;
+ return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount;
},
paginatedIssueText() {
return sprintf(__('Showing %{pageSize} of %{total} %{issuableType}'), {
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index e985a368e64..19004518edf 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -17,6 +17,7 @@ import sidebarEventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking';
import { formatDate } from '~/lib/utils/datetime_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
import AccessorUtilities from '../../lib/utils/accessor';
import { inactiveId, LIST, ListType, toggleFormEventPrefix } from '../constants';
import eventHub from '../eventhub';
@@ -74,7 +75,7 @@ export default {
},
},
computed: {
- ...mapState(['activeId']),
+ ...mapState(['activeId', 'filterParams']),
...mapGetters(['isEpicBoard', 'isSwimlanesOn']),
isLoggedIn() {
return Boolean(this.currentUserId);
@@ -119,14 +120,11 @@ export default {
}
return false;
},
- itemsCount() {
- return this.list.issuesCount;
- },
countIcon() {
return 'issues';
},
itemsTooltipLabel() {
- return n__(`%d issue`, `%d issues`, this.itemsCount);
+ return n__(`%d issue`, `%d issues`, this.boardLists?.issuesCount);
},
chevronTooltip() {
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
@@ -158,6 +156,23 @@ export default {
userCanDrag() {
return !this.disabled && isListDraggable(this.list);
},
+ isLoading() {
+ return this.$apollo.queries.boardList.loading;
+ },
+ },
+ apollo: {
+ boardList: {
+ query: listQuery,
+ variables() {
+ return {
+ id: this.list.id,
+ filters: this.filterParams,
+ };
+ },
+ skip() {
+ return this.isEpicBoard;
+ },
+ },
},
created() {
const localCollapsed = parseBoolean(localStorage.getItem(`${this.uniqueKey}.collapsed`));
@@ -375,10 +390,10 @@ export default {
</gl-sprintf>
</div>
<div v-else>• {{ itemsTooltipLabel }}</div>
- <div v-if="weightFeatureAvailable">
+ <div v-if="weightFeatureAvailable && !isLoading">
•
<gl-sprintf :message="__('%{totalWeight} total weight')">
- <template #totalWeight>{{ list.totalWeight }}</template>
+ <template #totalWeight>{{ boardList.totalWeight }}</template>
</gl-sprintf>
</div>
</gl-tooltip>
@@ -396,14 +411,18 @@ export default {
<gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" />
<span ref="itemCount" class="gl-display-inline-flex gl-align-items-center">
<gl-icon class="gl-mr-2" :name="countIcon" />
- <item-count :items-size="itemsCount" :max-issue-count="list.maxIssueCount" />
+ <item-count
+ v-if="!isLoading"
+ :items-size="isEpicBoard ? list.epicsCount : boardList.issuesCount"
+ :max-issue-count="list.maxIssueCount"
+ />
</span>
<!-- EE start -->
- <template v-if="weightFeatureAvailable && !isEpicBoard">
+ <template v-if="weightFeatureAvailable && !isEpicBoard && !isLoading">
<gl-tooltip :target="() => $refs.weightTooltip" :title="weightCountToolTip" />
<span ref="weightTooltip" class="gl-display-inline-flex gl-ml-3">
<gl-icon class="gl-mr-2" name="weight" />
- {{ list.totalWeight }}
+ {{ boardList.totalWeight }}
</span>
</template>
<!-- EE end -->
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 71facba1378..69343cd78d8 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -349,6 +349,9 @@ export default {
v-if="showCreate"
v-gl-modal-directive="'board-config-modal'"
data-qa-selector="create_new_board_button"
+ data-track-action="click_button"
+ data-track-label="create_new_board"
+ data-track-property="dropdown"
@click.prevent="showPage('new')"
>
{{ s__('IssueBoards|Create new board') }}
diff --git a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
index bdb9c2be836..7fc87f9f672 100644
--- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
@@ -2,22 +2,25 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { mapActions } from 'vuex';
+import { orderBy } from 'lodash';
import BoardFilteredSearch from 'ee_else_ce/boards/components/board_filtered_search.vue';
import { BoardType } from '~/boards/constants';
import axios from '~/lib/utils/axios_utils';
+import { joinPaths } from '~/lib/utils/url_utility';
import issueBoardFilters from '~/boards/issue_board_filters';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import {
- DEFAULT_MILESTONES_GRAPHQL,
TOKEN_TITLE_MY_REACTION,
+ OPERATOR_IS_AND_IS_NOT,
+ OPERATOR_IS_ONLY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
-import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
+import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
export default {
types: {
@@ -34,12 +37,11 @@ export default {
incident: __('Incident'),
issue: __('Issue'),
milestone: __('Milestone'),
- weight: __('Weight'),
- is: __('is'),
- isNot: __('is not'),
+ release: __('Release'),
+ confidential: __('Confidential'),
},
components: { BoardFilteredSearch },
- inject: ['isSignedIn'],
+ inject: ['isSignedIn', 'releasesFetchPath'],
props: {
fullPath: {
type: String,
@@ -62,15 +64,14 @@ export default {
tokensCE() {
const {
label,
- is,
- isNot,
author,
assignee,
issue,
incident,
type,
milestone,
- weight,
+ release,
+ confidential,
} = this.$options.i18n;
const { types } = this.$options;
const { fetchAuthors, fetchLabels } = issueBoardFilters(
@@ -79,15 +80,12 @@ export default {
this.boardType,
);
- return [
+ const tokens = [
{
icon: 'user',
title: assignee,
- type: 'assignee_username',
- operators: [
- { value: '=', description: is },
- { value: '!=', description: isNot },
- ],
+ type: 'assignee',
+ operators: OPERATOR_IS_AND_IS_NOT,
token: AuthorToken,
unique: true,
fetchAuthors,
@@ -96,11 +94,8 @@ export default {
{
icon: 'pencil',
title: author,
- type: 'author_username',
- operators: [
- { value: '=', description: is },
- { value: '!=', description: isNot },
- ],
+ type: 'author',
+ operators: OPERATOR_IS_AND_IS_NOT,
symbol: '@',
token: AuthorToken,
unique: true,
@@ -110,11 +105,8 @@ export default {
{
icon: 'labels',
title: label,
- type: 'label_name',
- operators: [
- { value: '=', description: is },
- { value: '!=', description: isNot },
- ],
+ type: 'label',
+ operators: OPERATOR_IS_AND_IS_NOT,
token: LabelToken,
unique: false,
symbol: '~',
@@ -123,7 +115,7 @@ export default {
...(this.isSignedIn
? [
{
- type: 'my_reaction_emoji',
+ type: 'my-reaction',
title: TOKEN_TITLE_MY_REACTION,
icon: 'thumb-up',
token: EmojiToken,
@@ -144,22 +136,33 @@ export default {
});
},
},
+ {
+ type: 'confidential',
+ icon: 'eye-slash',
+ title: confidential,
+ unique: true,
+ token: GlFilteredSearchToken,
+ operators: OPERATOR_IS_ONLY,
+ options: [
+ { icon: 'eye-slash', value: 'yes', title: __('Yes') },
+ { icon: 'eye', value: 'no', title: __('No') },
+ ],
+ },
]
: []),
{
- type: 'milestone_title',
+ type: 'milestone',
title: milestone,
icon: 'clock',
symbol: '%',
token: MilestoneToken,
unique: true,
- defaultMilestones: DEFAULT_MILESTONES_GRAPHQL,
fetchMilestones: this.fetchMilestones,
},
{
icon: 'issues',
title: type,
- type: 'types',
+ type: 'type',
token: GlFilteredSearchToken,
unique: true,
options: [
@@ -168,13 +171,27 @@ export default {
],
},
{
- type: 'weight',
- title: weight,
- icon: 'weight',
- token: WeightToken,
- unique: true,
+ type: 'release',
+ title: release,
+ icon: 'rocket',
+ token: ReleaseToken,
+ fetchReleases: (search) => {
+ // TODO: Switch to GraphQL query when backend is ready: https://gitlab.com/gitlab-org/gitlab/-/issues/337686
+ return axios
+ .get(joinPaths(gon.relative_url_root, this.releasesFetchPath))
+ .then(({ data }) => {
+ if (search) {
+ return fuzzaldrinPlus.filter(data, search, {
+ key: ['tag'],
+ });
+ }
+ return data;
+ });
+ },
},
];
+
+ return orderBy(tokens, ['title']);
},
tokens() {
return this.tokensCE;
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
deleted file mode 100644
index ec53947fd5f..00000000000
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ /dev/null
@@ -1,173 +0,0 @@
-<script>
-import { GlLabel } from '@gitlab/ui';
-import { mapGetters, mapActions } from 'vuex';
-import Api from '~/api';
-import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { isScopedLabel } from '~/lib/utils/common_utils';
-import { mergeUrlParams } from '~/lib/utils/url_utility';
-import { __ } from '~/locale';
-import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
-
-export default {
- components: {
- BoardEditableItem,
- LabelsSelect,
- GlLabel,
- },
- inject: {
- labelsFetchPath: {
- default: null,
- },
- labelsManagePath: {},
- labelsFilterBasePath: {},
- },
- data() {
- return {
- loading: false,
- oldIid: null,
- isEditing: false,
- };
- },
- computed: {
- ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']),
- selectedLabels() {
- const { labels = [] } = this.activeBoardItem;
-
- return labels.map((label) => ({
- ...label,
- id: getIdFromGraphQLId(label.id),
- }));
- },
- issueLabels() {
- const { labels = [] } = this.activeBoardItem;
-
- return labels.map((label) => ({
- ...label,
- scoped: isScopedLabel(label),
- }));
- },
- fetchPath() {
- /*
- Labels fetched in epic boards are always group-level labels
- and the correct path are passed from the backend (injected through labelsFetchPath)
-
- For issue boards, we should always include project-level labels and use a different endpoint.
- (it requires knowing the project path of a selected issue.)
-
- Note 1. that we will be using GraphQL to fetch labels when we create a labels select widget.
- And this component will be removed _wholesale_ https://gitlab.com/gitlab-org/gitlab/-/issues/300653.
-
- Note 2. Moreover, 'fetchPath' needs to be used as a key for 'labels-select' component to force updates.
- 'labels-select' has its own vuex store and initializes the passed props as states
- and these states aren't reactively bound to the passed props.
- */
-
- const projectLabelsFetchPath = mergeUrlParams(
- { include_ancestor_groups: true },
- Api.buildUrl(Api.projectLabelsPath).replace(
- ':namespace_path/:project_path',
- this.projectPathForActiveIssue,
- ),
- );
-
- return this.labelsFetchPath || projectLabelsFetchPath;
- },
- },
- watch: {
- activeBoardItem(_, oldVal) {
- if (this.isEditing) {
- this.oldIid = oldVal.iid;
- } else {
- this.oldIid = null;
- }
- },
- },
- methods: {
- ...mapActions(['setActiveBoardItemLabels', 'setError']),
- async setLabels(payload) {
- this.loading = true;
- this.$refs.sidebarItem.collapse();
-
- try {
- const addLabelIds = payload.filter((label) => label.set).map((label) => label.id);
- const removeLabelIds = payload.filter((label) => !label.set).map((label) => label.id);
-
- const input = {
- addLabelIds,
- removeLabelIds,
- projectPath: this.projectPathForActiveIssue,
- iid: this.oldIid,
- };
- await this.setActiveBoardItemLabels(input);
- this.oldIid = null;
- } catch (e) {
- this.setError({ error: e, message: __('An error occurred while updating labels.') });
- } finally {
- this.loading = false;
- }
- },
- async removeLabel(id) {
- this.loading = true;
-
- try {
- const removeLabelIds = [getIdFromGraphQLId(id)];
- const input = { removeLabelIds, projectPath: this.projectPathForActiveIssue };
- await this.setActiveBoardItemLabels(input);
- } catch (e) {
- this.setError({ error: e, message: __('An error occurred when removing the label.') });
- } finally {
- this.loading = false;
- }
- },
- },
-};
-</script>
-
-<template>
- <board-editable-item
- ref="sidebarItem"
- :title="__('Labels')"
- :loading="loading"
- data-testid="sidebar-labels"
- @open="isEditing = true"
- @close="isEditing = false"
- >
- <template #collapsed>
- <gl-label
- v-for="label in issueLabels"
- :key="label.id"
- :background-color="label.color"
- :title="label.title"
- :description="label.description"
- :scoped="label.scoped"
- :show-close-button="true"
- :disabled="loading"
- class="gl-mr-2 gl-mb-2"
- @close="removeLabel(label.id)"
- />
- </template>
- <template #default="{ edit }">
- <labels-select
- ref="labelsSelect"
- :key="fetchPath"
- :allow-label-edit="false"
- :allow-label-create="false"
- :allow-multiselect="true"
- :allow-scoped-labels="true"
- :selected-labels="selectedLabels"
- :labels-fetch-path="fetchPath"
- :labels-manage-path="labelsManagePath"
- :labels-filter-base-path="labelsFilterBasePath"
- :labels-list-title="__('Select label')"
- :dropdown-button-text="__('Choose labels')"
- :is-editing="edit"
- variant="sidebar"
- class="gl-display-block labels gl-w-full"
- @updateSelectedLabels="setLabels"
- >
- {{ __('None') }}
- </labels-select>
- </template>
- </board-editable-item>
-</template>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
deleted file mode 100644
index 4f5c55d0c5d..00000000000
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<script>
-import { GlToggle } from '@gitlab/ui';
-import { mapGetters, mapActions } from 'vuex';
-import { __, s__ } from '~/locale';
-
-export default {
- i18n: {
- header: {
- title: __('Notifications'),
- /* Any change to subscribeDisabledDescription
- must be reflected in app/helpers/notifications_helper.rb */
- subscribeDisabledDescription: __(
- 'Notifications have been disabled by the project or group owner',
- ),
- },
- updateSubscribedErrorMessage: s__(
- 'IssueBoards|An error occurred while setting notifications status. Please try again.',
- ),
- },
- components: {
- GlToggle,
- },
- inject: ['emailsDisabled'],
- data() {
- return {
- loading: false,
- };
- },
- computed: {
- ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue', 'isEpicBoard']),
- isEmailsDisabled() {
- return this.isEpicBoard ? this.emailsDisabled : this.activeBoardItem.emailsDisabled;
- },
- notificationText() {
- return this.isEmailsDisabled
- ? this.$options.i18n.header.subscribeDisabledDescription
- : this.$options.i18n.header.title;
- },
- },
- methods: {
- ...mapActions(['setActiveItemSubscribed', 'setError']),
- async handleToggleSubscription() {
- this.loading = true;
- try {
- await this.setActiveItemSubscribed({
- subscribed: !this.activeBoardItem.subscribed,
- projectPath: this.projectPathForActiveIssue,
- });
- } catch (error) {
- this.setError({ error, message: this.$options.i18n.updateSubscribedErrorMessage });
- } finally {
- this.loading = false;
- }
- },
- },
-};
-</script>
-
-<template>
- <div
- class="gl-display-flex gl-align-items-center gl-justify-content-space-between"
- data-testid="sidebar-notifications"
- >
- <span data-testid="notification-header-text"> {{ notificationText }} </span>
- <gl-toggle
- v-if="!isEmailsDisabled"
- :value="activeBoardItem.subscribed"
- :is-loading="loading"
- :label="$options.i18n.header.title"
- label-position="hidden"
- data-testid="notification-subscribe-toggle"
- @change="handleToggleSubscription"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 391e0d1fb0a..851b5eca40d 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -104,8 +104,10 @@ export const FilterFields = {
'assigneeUsername',
'assigneeWildcardId',
'authorUsername',
+ 'confidential',
'labelName',
'milestoneTitle',
+ 'milestoneWildcardId',
'myReactionEmoji',
'releaseTag',
'search',
@@ -114,6 +116,18 @@ export const FilterFields = {
],
};
+/* eslint-disable @gitlab/require-i18n-strings */
+export const AssigneeFilterType = {
+ any: 'Any',
+};
+
+export const MilestoneFilterType = {
+ any: 'Any',
+ none: 'None',
+ started: 'Started',
+ upcoming: 'Upcoming',
+};
+
export const DraggableItemTypes = {
card: 'card',
list: 'list',
diff --git a/app/assets/javascripts/boards/graphql/board_labels.query.graphql b/app/assets/javascripts/boards/graphql/board_labels.query.graphql
index b19a24e8808..525a4863379 100644
--- a/app/assets/javascripts/boards/graphql/board_labels.query.graphql
+++ b/app/assets/javascripts/boards/graphql/board_labels.query.graphql
@@ -7,6 +7,7 @@ query BoardLabels(
$isProject: Boolean = false
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
+ id
labels(searchTerm: $searchTerm, onlyGroupLabels: true, includeAncestorGroups: true) {
nodes {
...Label
@@ -14,6 +15,7 @@ query BoardLabels(
}
}
project(fullPath: $fullPath) @include(if: $isProject) {
+ id
labels(searchTerm: $searchTerm, includeAncestorGroups: true) {
nodes {
...Label
diff --git a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
index 0e1d11727cf..81cc7b4d246 100644
--- a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql
@@ -2,6 +2,8 @@
mutation createBoardList($boardId: BoardID!, $backlog: Boolean, $labelId: LabelID) {
boardListCreate(input: { boardId: $boardId, backlog: $backlog, labelId: $labelId }) {
+ # We have ID in a deeply nested fragment
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
list {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql
index d85b736720b..5b532906f6a 100644
--- a/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql
@@ -4,7 +4,6 @@ fragment BoardListShared on BoardList {
position
listType
collapsed
- issuesCount
label {
id
title
diff --git a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
index b474c9acb93..7ea0e2f915a 100644
--- a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql
@@ -2,6 +2,8 @@
mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) {
updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) {
+ # We have ID in a deeply nested fragment
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
list {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_lists.query.graphql b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
index 47e87907d76..e6e98864aad 100644
--- a/app/assets/javascripts/boards/graphql/board_lists.query.graphql
+++ b/app/assets/javascripts/boards/graphql/board_lists.query.graphql
@@ -8,9 +8,13 @@ query BoardLists(
$isProject: Boolean = false
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
+ id
board(id: $boardId) {
+ id
hideBacklogList
lists(issueFilters: $filters) {
+ # We have ID in a deeply nested fragment
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
nodes {
...BoardListFragment
}
@@ -18,9 +22,13 @@ query BoardLists(
}
}
project(fullPath: $fullPath) @include(if: $isProject) {
+ id
board(id: $boardId) {
+ id
hideBacklogList
lists(issueFilters: $filters) {
+ # We have ID in a deeply nested fragment
+ # eslint-disable-next-line @graphql-eslint/require-id-when-available
nodes {
...BoardListFragment
}
diff --git a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql
new file mode 100644
index 00000000000..bae3220dfad
--- /dev/null
+++ b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql
@@ -0,0 +1,6 @@
+query BoardList($id: ID!, $filters: BoardIssueInput) {
+ boardList(id: $id, issueFilters: $filters) {
+ id
+ issuesCount
+ }
+}
diff --git a/app/assets/javascripts/boards/graphql/group_board.query.graphql b/app/assets/javascripts/boards/graphql/group_board.query.graphql
index 77c8e0378f0..8d87b83da96 100644
--- a/app/assets/javascripts/boards/graphql/group_board.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_board.query.graphql
@@ -2,6 +2,7 @@
query GroupBoard($fullPath: ID!, $boardId: ID!) {
workspace: group(fullPath: $fullPath) {
+ id
board(id: $boardId) {
...BoardScopeFragment
}
diff --git a/app/assets/javascripts/boards/graphql/group_board_members.query.graphql b/app/assets/javascripts/boards/graphql/group_board_members.query.graphql
index d3251c2aa12..aec674eb006 100644
--- a/app/assets/javascripts/boards/graphql/group_board_members.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_board_members.query.graphql
@@ -3,6 +3,7 @@
query GroupBoardMembers($fullPath: ID!, $search: String) {
workspace: group(fullPath: $fullPath) {
__typename
+ id
assignees: groupMembers(search: $search, relations: [DIRECT, DESCENDANTS, INHERITED]) {
__typename
nodes {
diff --git a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
index 73aa9137dec..0963b3fbfaa 100644
--- a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
@@ -1,5 +1,6 @@
query GroupBoardMilestones($fullPath: ID!, $searchTerm: String) {
group(fullPath: $fullPath) {
+ id
milestones(includeAncestors: true, searchTitle: $searchTerm) {
nodes {
id
diff --git a/app/assets/javascripts/boards/graphql/group_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_boards.query.graphql
index feafd6ae10d..0823c4f5a83 100644
--- a/app/assets/javascripts/boards/graphql/group_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_boards.query.graphql
@@ -2,6 +2,7 @@
query group_boards($fullPath: ID!) {
group(fullPath: $fullPath) {
+ id
boards {
edges {
node {
diff --git a/app/assets/javascripts/boards/graphql/group_projects.query.graphql b/app/assets/javascripts/boards/graphql/group_projects.query.graphql
index c5732bbaff3..0da14d0b872 100644
--- a/app/assets/javascripts/boards/graphql/group_projects.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_projects.query.graphql
@@ -2,6 +2,7 @@
query boardsGetGroupProjects($fullPath: ID!, $search: String, $after: String) {
group(fullPath: $fullPath) {
+ id
projects(search: $search, after: $after, first: 100, includeSubgroups: true) {
nodes {
id
diff --git a/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql
index 70eb1dfbf7e..c9c5d744371 100644
--- a/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql
@@ -1,13 +1,12 @@
+#import "~/graphql_shared/fragments/label.fragment.graphql"
+
mutation issueSetLabels($input: UpdateIssueInput!) {
- updateIssue(input: $input) {
- issue {
+ updateIssuableLabels: updateIssue(input: $input) {
+ issuable: issue {
id
labels {
nodes {
- id
- title
- color
- description
+ ...Label
}
}
}
diff --git a/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql
index bfb87758e17..c130a64cac4 100644
--- a/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql
@@ -1,6 +1,7 @@
mutation issueSetSubscription($input: IssueSetSubscriptionInput!) {
updateIssuableSubscription: issueSetSubscription(input: $input) {
issue {
+ id
subscribed
}
errors
diff --git a/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql
index 6ad12d982e0..147cf040a85 100644
--- a/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql
@@ -1,6 +1,7 @@
mutation issueSetTitle($input: UpdateIssueInput!) {
updateIssuableTitle: updateIssue(input: $input) {
issue {
+ id
title
}
errors
diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
index 9f93bc6d5bf..105f2931caa 100644
--- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
+++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/boards/graphql/issue.fragment.graphql"
-query BoardListEE(
+query BoardListsEE(
$fullPath: ID!
$boardId: ID!
$id: ID
@@ -11,7 +11,9 @@ query BoardListEE(
$first: Int
) {
group(fullPath: $fullPath) @include(if: $isGroup) {
+ id
board(id: $boardId) {
+ id
lists(id: $id, issueFilters: $filters) {
nodes {
id
@@ -33,7 +35,9 @@ query BoardListEE(
}
}
project(fullPath: $fullPath) @include(if: $isProject) {
+ id
board(id: $boardId) {
+ id
lists(id: $id, issueFilters: $filters) {
nodes {
id
diff --git a/app/assets/javascripts/boards/graphql/project_board.query.graphql b/app/assets/javascripts/boards/graphql/project_board.query.graphql
index 6e4cd6bed57..8246d615a6a 100644
--- a/app/assets/javascripts/boards/graphql/project_board.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_board.query.graphql
@@ -2,6 +2,7 @@
query ProjectBoard($fullPath: ID!, $boardId: ID!) {
workspace: project(fullPath: $fullPath) {
+ id
board(id: $boardId) {
...BoardScopeFragment
}
diff --git a/app/assets/javascripts/boards/graphql/project_board_members.query.graphql b/app/assets/javascripts/boards/graphql/project_board_members.query.graphql
index fc6cc6b832c..45bec5e574b 100644
--- a/app/assets/javascripts/boards/graphql/project_board_members.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_board_members.query.graphql
@@ -3,6 +3,7 @@
query ProjectBoardMembers($fullPath: ID!, $search: String) {
workspace: project(fullPath: $fullPath) {
__typename
+ id
assignees: projectMembers(search: $search) {
__typename
nodes {
diff --git a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
index 8dd4d256caa..e456823d78a 100644
--- a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
@@ -1,5 +1,6 @@
query ProjectBoardMilestones($fullPath: ID!, $searchTerm: String) {
project(fullPath: $fullPath) {
+ id
milestones(searchTitle: $searchTerm, includeAncestors: true) {
nodes {
id
diff --git a/app/assets/javascripts/boards/graphql/project_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_boards.query.graphql
index f98d25ba671..b8879bc260c 100644
--- a/app/assets/javascripts/boards/graphql/project_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_boards.query.graphql
@@ -2,6 +2,7 @@
query project_boards($fullPath: ID!) {
project(fullPath: $fullPath) {
+ id
boards {
edges {
node {
diff --git a/app/assets/javascripts/boards/graphql/project_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql
index 61c9ddded9b..4c952096d76 100644
--- a/app/assets/javascripts/boards/graphql/project_milestones.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql
@@ -5,6 +5,7 @@ query boardProjectMilestones(
$searchTitle: String
) {
project(fullPath: $fullPath) {
+ id
milestones(state: $state, includeAncestors: $includeAncestors, searchTitle: $searchTitle) {
edges {
node {
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 6fa8dd63245..ded3bfded86 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -110,7 +110,8 @@ export default () => {
});
if (gon?.features?.issueBoardsFilteredSearch) {
- initBoardsFilteredSearch(apolloProvider, isLoggedIn());
+ const { releasesFetchPath } = $boardApp.dataset;
+ initBoardsFilteredSearch(apolloProvider, isLoggedIn(), releasesFetchPath);
}
mountBoardApp($boardApp);
diff --git a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js
index 1ea74d5685c..a8ade58e316 100644
--- a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js
+++ b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js
@@ -4,7 +4,7 @@ import store from '~/boards/stores';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
-export default (apolloProvider, isSignedIn) => {
+export default (apolloProvider, isSignedIn, releasesFetchPath) => {
const el = document.getElementById('js-issue-board-filtered-search');
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
@@ -21,6 +21,7 @@ export default (apolloProvider, isSignedIn) => {
provide: {
initialFilterParams,
isSignedIn,
+ releasesFetchPath,
},
store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094
apolloProvider,
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 3a96e535cf7..1ebfcfc331b 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -16,30 +16,30 @@ import {
ListTypeTitles,
DraggableItemTypes,
} from 'ee_else_ce/boards/constants';
-import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
-import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { queryToObject } from '~/lib/utils/url_utility';
-import { s__ } from '~/locale';
import {
+ formatIssueInput,
formatBoardLists,
formatListIssues,
formatListsPageInfo,
formatIssue,
- formatIssueInput,
updateListPosition,
moveItemListHelper,
getMoveData,
FiltersInfo,
filterVariables,
-} from '../boards_util';
+} from 'ee_else_ce/boards/boards_util';
+import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql';
+import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
+import totalCountAndWeightQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { queryToObject } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
import { gqlClient } from '../graphql';
import boardLabelsQuery from '../graphql/board_labels.query.graphql';
import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql';
import groupProjectsQuery from '../graphql/group_projects.query.graphql';
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
-import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql';
@@ -373,7 +373,6 @@ export default {
commit(types.REQUEST_ITEMS_FOR_LIST, { listId, fetchNext });
const { fullPath, fullBoardId, boardType, filterParams } = state;
-
const variables = {
fullPath,
boardId: fullBoardId,
@@ -503,9 +502,10 @@ export default {
updateIssueOrder: async ({ commit, dispatch, state }, { moveData, mutationVariables = {} }) => {
try {
- const { itemId, fromListId, toListId, moveBeforeId, moveAfterId } = moveData;
+ const { itemId, fromListId, toListId, moveBeforeId, moveAfterId, itemNotInToList } = moveData;
const {
fullBoardId,
+ filterParams,
boardItems: {
[itemId]: { iid, referencePath },
},
@@ -524,6 +524,67 @@ export default {
// 'mutationVariables' allows EE code to pass in extra parameters.
...mutationVariables,
},
+ update(
+ cache,
+ {
+ data: {
+ issueMoveList: {
+ issue: { weight },
+ },
+ },
+ },
+ ) {
+ if (fromListId === toListId) return;
+
+ const updateFromList = () => {
+ const fromList = cache.readQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: fromListId, filters: filterParams },
+ });
+
+ const updatedFromList = {
+ boardList: {
+ __typename: 'BoardList',
+ id: fromList.boardList.id,
+ issuesCount: fromList.boardList.issuesCount - 1,
+ totalWeight: fromList.boardList.totalWeight - Number(weight),
+ },
+ };
+
+ cache.writeQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: fromListId, filters: filterParams },
+ data: updatedFromList,
+ });
+ };
+
+ const updateToList = () => {
+ if (!itemNotInToList) return;
+
+ const toList = cache.readQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: toListId, filters: filterParams },
+ });
+
+ const updatedToList = {
+ boardList: {
+ __typename: 'BoardList',
+ id: toList.boardList.id,
+ issuesCount: toList.boardList.issuesCount + 1,
+ totalWeight: toList.boardList.totalWeight + Number(weight),
+ },
+ };
+
+ cache.writeQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: toListId, filters: filterParams },
+ data: updatedToList,
+ });
+ };
+
+ updateFromList();
+ updateToList();
+ },
});
if (data?.issueMoveList?.errors.length || !data.issueMoveList) {
@@ -567,7 +628,7 @@ export default {
},
addListNewIssue: (
- { state: { boardConfig, boardType, fullPath }, dispatch, commit },
+ { state: { boardConfig, boardType, fullPath, filterParams }, dispatch, commit },
{ issueInput, list, placeholderId = `tmp-${new Date().getTime()}` },
) => {
const input = formatIssueInput(issueInput, boardConfig);
@@ -583,6 +644,27 @@ export default {
.mutate({
mutation: issueCreateMutation,
variables: { input },
+ update(cache) {
+ const fromList = cache.readQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: list.id, filters: filterParams },
+ });
+
+ const updatedList = {
+ boardList: {
+ __typename: 'BoardList',
+ id: fromList.boardList.id,
+ issuesCount: fromList.boardList.issuesCount + 1,
+ totalWeight: fromList.boardList.totalWeight,
+ },
+ };
+
+ cache.writeQuery({
+ query: totalCountAndWeightQuery,
+ variables: { id: list.id, filters: filterParams },
+ data: updatedList,
+ });
+ },
})
.then(({ data }) => {
if (data.createIssue.errors.length) {
@@ -610,33 +692,6 @@ export default {
setActiveIssueLabels: async ({ commit, getters }, input) => {
const { activeBoardItem } = getters;
- if (!gon.features?.labelsWidget) {
- const { data } = await gqlClient.mutate({
- mutation: issueSetLabelsMutation,
- variables: {
- input: {
- iid: input.iid || String(activeBoardItem.iid),
- labelIds: input.labelsId ?? undefined,
- addLabelIds: input.addLabelIds ?? [],
- removeLabelIds: input.removeLabelIds ?? [],
- projectPath: input.projectPath,
- },
- },
- });
-
- if (data.updateIssue?.errors?.length > 0) {
- throw new Error(data.updateIssue.errors);
- }
-
- commit(types.UPDATE_BOARD_ITEM_BY_ID, {
- itemId: data.updateIssue?.issue?.id || activeBoardItem.id,
- prop: 'labels',
- value: data.updateIssue?.issue?.labels.nodes,
- });
-
- return;
- }
-
let labels = input?.labels || [];
if (input.removeLabelIds) {
labels = activeBoardItem.labels.filter(