Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/boards/components')
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue18
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue77
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue30
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue48
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue109
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue_deprecated.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_new_item.vue95
-rw-r--r--app/assets/javascripts/boards/components/issue_board_filtered_search.vue57
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_editable_item.vue5
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue21
13 files changed, 344 insertions, 130 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 05b64ddc773..5658a34e9a6 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -65,7 +65,7 @@ export default {
},
computed: {
...mapState(['isShowingLabels', 'issuableType', 'allowSubEpics']),
- ...mapGetters(['isEpicBoard']),
+ ...mapGetters(['isEpicBoard', 'isProjectBoard']),
cappedAssignees() {
// e.g. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
@@ -144,6 +144,9 @@ export default {
totalProgress() {
return Math.round((this.item.descendantWeightSum.closedIssues / this.totalWeight) * 100);
},
+ showReferencePath() {
+ return !this.isProjectBoard && this.itemReferencePath;
+ },
},
methods: {
...mapActions(['performSearch', 'setError']),
@@ -247,7 +250,7 @@ export default {
:class="{ 'gl-font-base': isEpicBoard }"
>
<tooltip-on-truncate
- v-if="itemReferencePath"
+ v-if="showReferencePath"
:title="itemReferencePath"
placement="bottom"
class="board-item-path gl-text-truncate gl-font-weight-bold"
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 69abf886ad7..bcf5b12b209 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -79,7 +79,7 @@ export default {
'is-collapsed': list.collapsed,
'board-type-assignee': list.listType === 'assignee',
}"
- :data-id="list.id"
+ :data-list-id="list.id"
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable"
data-qa-selector="board_list"
>
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 53b071aaed1..4df6ff75249 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -6,10 +6,12 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { DraggableItemTypes } from '../constants';
import BoardColumn from './board_column.vue';
import BoardColumnDeprecated from './board_column_deprecated.vue';
export default {
+ draggableItemTypes: DraggableItemTypes,
components: {
BoardAddNewColumn,
BoardColumn,
@@ -76,19 +78,6 @@ export default {
const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
},
- handleDragOnEnd(params) {
- const { item, newIndex, oldIndex, to } = params;
-
- const listId = item.dataset.id;
- const replacedListId = to.children[newIndex].dataset.id;
-
- this.moveList({
- listId,
- replacedListId,
- newIndex,
- adjustmentValue: newIndex < oldIndex ? 1 : -1,
- });
- },
},
};
</script>
@@ -104,7 +93,7 @@ export default {
ref="list"
v-bind="draggableOptions"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
- @end="handleDragOnEnd"
+ @end="moveList"
>
<component
:is="boardColumnComponent"
@@ -112,6 +101,7 @@ export default {
:key="index"
ref="board"
:list="list"
+ :data-draggable-item-type="$options.draggableItemTypes.list"
:disabled="disabled"
/>
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index e014b82d362..7a936e75676 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -87,6 +87,7 @@ export default {
v-bind="$attrs"
:open="isSidebarOpen"
class="boards-sidebar gl-absolute"
+ variant="sidebar"
@close="handleClose"
>
<template #title>
@@ -159,7 +160,7 @@ export default {
:issuable-type="issuableType"
data-testid="sidebar-due-date"
/>
- <board-sidebar-labels-select class="labels" />
+ <board-sidebar-labels-select 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 cfd6b21fa66..7f242dea644 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -27,7 +27,15 @@ export default {
},
computed: {
urlParams() {
- const { authorUsername, labelName, assigneeUsername, search } = this.filterParams;
+ const {
+ authorUsername,
+ labelName,
+ assigneeUsername,
+ search,
+ milestoneTitle,
+ types,
+ weight,
+ } = this.filterParams;
let notParams = {};
if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
@@ -36,6 +44,9 @@ export default {
'not[label_name][]': this.filterParams.not.labelName,
'not[author_username]': this.filterParams.not.authorUsername,
'not[assignee_username]': this.filterParams.not.assigneeUsername,
+ 'not[types]': this.filterParams.not.types,
+ 'not[milestone_title]': this.filterParams.not.milestoneTitle,
+ 'not[weight]': this.filterParams.not.weight,
},
undefined,
);
@@ -46,7 +57,10 @@ export default {
author_username: authorUsername,
'label_name[]': labelName,
assignee_username: assigneeUsername,
+ milestone_title: milestoneTitle,
search,
+ types,
+ weight,
};
},
},
@@ -64,7 +78,15 @@ export default {
this.performSearch();
},
getFilteredSearchValue() {
- const { authorUsername, labelName, assigneeUsername, search } = this.filterParams;
+ const {
+ authorUsername,
+ labelName,
+ assigneeUsername,
+ search,
+ milestoneTitle,
+ types,
+ weight,
+ } = this.filterParams;
const filteredSearchValue = [];
if (authorUsername) {
@@ -81,6 +103,13 @@ export default {
});
}
+ if (types) {
+ filteredSearchValue.push({
+ type: 'types',
+ value: { data: types, operator: '=' },
+ });
+ }
+
if (labelName?.length) {
filteredSearchValue.push(
...labelName.map((label) => ({
@@ -90,6 +119,20 @@ export default {
);
}
+ if (milestoneTitle) {
+ filteredSearchValue.push({
+ type: 'milestone_title',
+ value: { data: milestoneTitle, operator: '=' },
+ });
+ }
+
+ if (weight) {
+ filteredSearchValue.push({
+ type: 'weight',
+ value: { data: weight, operator: '=' },
+ });
+ }
+
if (this.filterParams['not[authorUsername]']) {
filteredSearchValue.push({
type: 'author_username',
@@ -97,6 +140,20 @@ export default {
});
}
+ if (this.filterParams['not[milestoneTitle]']) {
+ filteredSearchValue.push({
+ type: 'milestone_title',
+ value: { data: this.filterParams['not[milestoneTitle]'], operator: '!=' },
+ });
+ }
+
+ if (this.filterParams['not[weight]']) {
+ filteredSearchValue.push({
+ type: 'weight',
+ value: { data: this.filterParams['not[weight]'], operator: '!=' },
+ });
+ }
+
if (this.filterParams['not[assigneeUsername]']) {
filteredSearchValue.push({
type: 'assignee_username',
@@ -113,6 +170,13 @@ export default {
);
}
+ if (this.filterParams['not[types]']) {
+ filteredSearchValue.push({
+ type: 'types',
+ value: { data: this.filterParams['not[types]'], operator: '!=' },
+ });
+ }
+
if (search) {
filteredSearchValue.push(search);
}
@@ -140,9 +204,18 @@ export default {
case 'assignee_username':
filterParams.assigneeUsername = filter.value.data;
break;
+ case 'types':
+ filterParams.types = filter.value.data;
+ break;
case 'label_name':
labels.push(filter.value.data);
break;
+ case 'milestone_title':
+ filterParams.milestoneTitle = filter.value.data;
+ break;
+ case 'weight':
+ filterParams.weight = filter.value.data;
+ break;
case 'filtered-search-term':
if (filter.value.data) plainText.push(filter.value.data);
break;
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 386ed6bd0a1..a89f71504a9 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -2,7 +2,7 @@
import { GlModal, GlAlert } from '@gitlab/ui';
import { mapGetters, mapActions, mapState } from 'vuex';
import ListLabel from '~/boards/models/label';
-import { TYPE_ITERATION, TYPE_MILESTONE, TYPE_USER } from '~/graphql_shared/constants';
+import { TYPE_ITERATION, TYPE_MILESTONE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { getParameterByName, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
@@ -18,10 +18,9 @@ const boardDefaults = {
id: false,
name: '',
labels: [],
- milestone_id: undefined,
+ milestone: {},
iteration_id: undefined,
assignee: {},
- assignee_id: undefined,
weight: null,
hide_backlog_list: false,
hide_closed_list: false,
@@ -190,13 +189,10 @@ export default {
issueBoardScopeMutationVariables() {
return {
weight: this.board.weight,
- assigneeId: this.board.assignee?.id
- ? convertToGraphQLId(TYPE_USER, this.board.assignee.id)
+ assigneeId: this.board.assignee?.id || null,
+ milestoneId: this.board.milestone?.id
+ ? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id)
: null,
- milestoneId:
- this.board.milestone?.id || this.board.milestone?.id === 0
- ? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id)
- : null,
iterationId: this.board.iteration_id
? convertToGraphQLId(TYPE_ITERATION, this.board.iteration_id)
: null,
@@ -306,6 +302,19 @@ export default {
}
});
},
+ setAssignee(assigneeId) {
+ this.$set(this.board, 'assignee', {
+ id: assigneeId,
+ });
+ },
+ setMilestone(milestoneId) {
+ this.$set(this.board, 'milestone', {
+ id: milestoneId,
+ });
+ },
+ setWeight(weight) {
+ this.$set(this.board, 'weight', weight);
+ },
},
};
</script>
@@ -373,6 +382,9 @@ export default {
:weights="weights"
@set-iteration="setIteration"
@set-board-labels="setBoardLabels"
+ @set-assignee="setAssignee"
+ @set-milestone="setMilestone"
+ @set-weight="setWeight"
/>
</form>
</gl-modal>
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 8dca6be853f..849492effab 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -6,12 +6,13 @@ import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_opt
import { sprintf, __ } from '~/locale';
import defaultSortableConfig from '~/sortable/sortable_config';
import Tracking from '~/tracking';
-import { toggleFormEventPrefix } from '../constants';
+import { toggleFormEventPrefix, DraggableItemTypes } from '../constants';
import eventHub from '../eventhub';
import BoardCard from './board_card.vue';
import BoardNewIssue from './board_new_issue.vue';
export default {
+ draggableItemTypes: DraggableItemTypes,
name: 'BoardList',
i18n: {
loading: __('Loading'),
@@ -27,11 +28,6 @@ export default {
GlIntersectionObserver,
},
mixins: [Tracking.mixin()],
- inject: {
- canAdminList: {
- default: false,
- },
- },
props: {
disabled: {
type: Boolean,
@@ -89,8 +85,8 @@ export default {
return !this.isEpicBoard && this.list.listType !== 'closed' && this.showIssueForm;
},
listRef() {
- // When list is draggable, the reference to the list needs to be accessed differently
- return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
+ // When list is draggable, the reference to the list needs to be accessed differently
+ return this.canMoveIssue ? this.$refs.list.$el : this.$refs.list;
},
showingAllItems() {
return this.boardItems.length === this.listItemsCount;
@@ -100,8 +96,11 @@ export default {
? this.$options.i18n.showingAllEpics
: this.$options.i18n.showingAllIssues;
},
+ canMoveIssue() {
+ return !this.disabled;
+ },
treeRootWrapper() {
- return this.canAdminList && !this.listsFlags[this.list.id]?.addItemToListInProgress
+ return this.canMoveIssue && !this.listsFlags[this.list.id]?.addItemToListInProgress
? Draggable
: 'ul';
},
@@ -116,7 +115,7 @@ export default {
value: this.boardItems,
};
- return this.canAdminList ? options : {};
+ return this.canMoveIssue ? options : {};
},
},
watch: {
@@ -172,15 +171,33 @@ export default {
this.loadNextPage();
}
},
- handleDragOnStart() {
+ handleDragOnStart({
+ item: {
+ dataset: { draggableItemType },
+ },
+ }) {
+ if (draggableItemType !== DraggableItemTypes.card) {
+ return;
+ }
+
sortableStart();
this.track('drag_card', { label: 'board' });
},
- handleDragOnEnd(params) {
+ handleDragOnEnd({
+ newIndex: originalNewIndex,
+ oldIndex,
+ from,
+ to,
+ item: {
+ dataset: { draggableItemType, itemId, itemIid, itemPath },
+ },
+ }) {
+ if (draggableItemType !== DraggableItemTypes.card) {
+ return;
+ }
+
sortableEnd();
- const { oldIndex, from, to, item } = params;
- let { newIndex } = params;
- const { itemId, itemIid, itemPath } = item.dataset;
+ let newIndex = originalNewIndex;
let { children } = to;
let moveBeforeId;
let moveAfterId;
@@ -267,6 +284,7 @@ export default {
:index="index"
:list="list"
:item="item"
+ :data-draggable-item-type="$options.draggableItemTypes.card"
:disabled="disabled"
/>
<gl-intersection-observer @appear="onReachingListBottom">
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index caeecb25227..84c9191975e 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,21 +1,19 @@
<script>
-import { GlButton } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import BoardNewIssueMixin from 'ee_else_ce/boards/mixins/board_new_issue';
-import { __ } from '~/locale';
+
import { toggleFormEventPrefix } from '../constants';
import eventHub from '../eventhub';
+
+import BoardNewItem from './board_new_item.vue';
import ProjectSelect from './project_select.vue';
export default {
name: 'BoardNewIssue',
- i18n: {
- cancel: __('Cancel'),
- },
components: {
+ BoardNewItem,
ProjectSelect,
- GlButton,
},
mixins: [BoardNewIssueMixin],
inject: ['groupId'],
@@ -25,106 +23,55 @@ export default {
required: true,
},
},
- data() {
- return {
- title: '',
- };
- },
computed: {
- ...mapState(['selectedProject']),
- ...mapGetters(['isGroupBoard', 'isEpicBoard']),
- /**
- * We've extended this component in EE where
- * submitButtonTitle returns a different string
- * hence this is kept as a computed prop.
- */
- submitButtonTitle() {
- return __('Create issue');
+ ...mapState(['selectedProject', 'fullPath']),
+ ...mapGetters(['isGroupBoard']),
+ formEventPrefix() {
+ return toggleFormEventPrefix.issue;
},
- disabled() {
- if (this.isGroupBoard) {
- return this.title === '' || !this.selectedProject.name;
- }
- return this.title === '';
+ disableSubmit() {
+ return this.isGroupBoard ? !this.selectedProject.name : false;
},
- inputFieldId() {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- return `${this.list.id}-title`;
+ projectPath() {
+ return this.isGroupBoard ? this.selectedProject.fullPath : this.fullPath;
},
},
- mounted() {
- this.$refs.input.focus();
- eventHub.$on('setSelectedProject', this.setSelectedProject);
- },
methods: {
...mapActions(['addListNewIssue']),
- submit() {
- const { title } = this;
+ submit({ title }) {
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
const milestone = getMilestone(this.list);
- eventHub.$emit(`scroll-board-list-${this.list.id}`);
-
return this.addListNewIssue({
+ list: this.list,
issueInput: {
title,
labelIds: labels?.map((l) => l.id),
assigneeIds: assignees?.map((a) => a?.id),
milestoneId: milestone?.id,
- projectPath: this.selectedProject.fullPath,
- ...this.extraIssueInput(),
+ projectPath: this.projectPath,
},
- list: this.list,
}).then(() => {
- this.reset();
+ this.cancel();
});
},
- reset() {
- this.title = '';
- eventHub.$emit(`${toggleFormEventPrefix.issue}${this.list.id}`);
+ cancel() {
+ eventHub.$emit(`${this.formEventPrefix}${this.list.id}`);
},
},
};
</script>
<template>
- <div class="board-new-issue-form">
- <div class="board-card position-relative p-3 rounded">
- <form ref="submitForm" @submit.prevent="submit">
- <label :for="inputFieldId" class="label-bold">{{ __('Title') }}</label>
- <input
- :id="inputFieldId"
- ref="input"
- v-model="title"
- class="form-control"
- type="text"
- name="issue_title"
- autocomplete="off"
- />
- <project-select v-if="isGroupBoard && !isEpicBoard" :group-id="groupId" :list="list" />
- <div class="clearfix gl-mt-3">
- <gl-button
- ref="submitButton"
- :disabled="disabled"
- class="float-left js-no-auto-disable"
- variant="confirm"
- category="primary"
- type="submit"
- >
- {{ submitButtonTitle }}
- </gl-button>
- <gl-button
- ref="cancelButton"
- class="float-right"
- type="button"
- variant="default"
- @click="reset"
- >
- {{ $options.i18n.cancel }}
- </gl-button>
- </div>
- </form>
- </div>
- </div>
+ <board-new-item
+ :list="list"
+ :form-event-prefix="formEventPrefix"
+ :submit-button-title="__('Create issue')"
+ :disable-submit="disableSubmit"
+ @form-submit="submit"
+ @form-cancel="cancel"
+ >
+ <project-select v-if="isGroupBoard" :group-id="groupId" :list="list" />
+ </board-new-item>
</template>
diff --git a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
index 1218941065f..a25b436b8de 100644
--- a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
@@ -11,7 +11,7 @@ import ProjectSelect from './project_select_deprecated.vue';
// This component is being replaced in favor of './board_new_issue.vue' for GraphQL boards
export default {
- name: 'BoardNewIssue',
+ name: 'BoardNewIssueDeprecated',
components: {
ProjectSelect,
GlButton,
diff --git a/app/assets/javascripts/boards/components/board_new_item.vue b/app/assets/javascripts/boards/components/board_new_item.vue
new file mode 100644
index 00000000000..44574de17d7
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_new_item.vue
@@ -0,0 +1,95 @@
+<script>
+import { GlForm, GlFormInput, GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+import eventHub from '../eventhub';
+
+export default {
+ i18n: {
+ cancel: __('Cancel'),
+ },
+ components: {
+ GlForm,
+ GlFormInput,
+ GlButton,
+ },
+ props: {
+ list: {
+ type: Object,
+ required: true,
+ },
+ formEventPrefix: {
+ type: String,
+ required: true,
+ },
+ disableSubmit: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ submitButtonTitle: {
+ type: String,
+ required: false,
+ default: __('Create issue'),
+ },
+ },
+ data() {
+ return {
+ title: '',
+ };
+ },
+ computed: {
+ inputFieldId() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `${this.list.id}-title`;
+ },
+ },
+ methods: {
+ handleFormCancel() {
+ this.title = '';
+ this.$emit('form-cancel');
+ },
+ handleFormSubmit() {
+ const { title, list } = this;
+
+ eventHub.$emit(`scroll-board-list-${this.list.id}`);
+ this.$emit('form-submit', {
+ title,
+ list,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="board-new-issue-form">
+ <div class="board-card position-relative gl-p-5 rounded">
+ <gl-form @submit.prevent="handleFormSubmit" @reset="handleFormCancel">
+ <label :for="inputFieldId" class="gl-font-weight-bold">{{ __('Title') }}</label>
+ <gl-form-input
+ :id="inputFieldId"
+ v-model.trim="title"
+ :autofocus="true"
+ autocomplete="off"
+ type="text"
+ name="issue_title"
+ />
+ <slot></slot>
+ <div class="gl-clearfix gl-mt-4">
+ <gl-button
+ :disabled="!title || disableSubmit"
+ class="gl-float-left js-no-auto-disable"
+ variant="confirm"
+ type="submit"
+ >
+ {{ submitButtonTitle }}
+ </gl-button>
+ <gl-button class="gl-float-right js-no-auto-disable" type="reset">
+ {{ $options.i18n.cancel }}
+ </gl-button>
+ </div>
+ </gl-form>
+ </div>
+ </div>
+</template>
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 d8dac17d326..5206db05410 100644
--- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
@@ -1,4 +1,6 @@
<script>
+import { GlFilteredSearchToken } from '@gitlab/ui';
+import { mapActions } from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import issueBoardFilters from '~/boards/issue_board_filters';
import { TYPE_USER } from '~/graphql_shared/constants';
@@ -6,13 +8,24 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_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';
export default {
+ types: {
+ ISSUE: 'ISSUE',
+ INCIDENT: 'INCIDENT',
+ },
i18n: {
search: __('Search'),
label: __('Label'),
author: __('Author'),
assignee: __('Assignee'),
+ type: __('Type'),
+ incident: __('Incident'),
+ issue: __('Issue'),
+ milestone: __('Milestone'),
+ weight: __('Weight'),
is: __('is'),
isNot: __('is not'),
},
@@ -29,7 +42,19 @@ export default {
},
computed: {
tokens() {
- const { label, is, isNot, author, assignee } = this.$options.i18n;
+ const {
+ label,
+ is,
+ isNot,
+ author,
+ assignee,
+ issue,
+ incident,
+ type,
+ milestone,
+ weight,
+ } = this.$options.i18n;
+ const { types } = this.$options;
const { fetchAuthors, fetchLabels } = issueBoardFilters(
this.$apollo,
this.fullPath,
@@ -77,10 +102,40 @@ export default {
fetchAuthors,
preloadedAuthors: this.preloadedAuthors(),
},
+ {
+ icon: 'issues',
+ title: type,
+ type: 'types',
+ operators: [{ value: '=', description: is }],
+ token: GlFilteredSearchToken,
+ unique: true,
+ options: [
+ { icon: 'issue-type-issue', value: types.ISSUE, title: issue },
+ { icon: 'issue-type-incident', value: types.INCIDENT, title: incident },
+ ],
+ },
+ {
+ type: 'milestone_title',
+ title: milestone,
+ icon: 'clock',
+ symbol: '%',
+ token: MilestoneToken,
+ unique: true,
+ defaultMilestones: [], // todo: https://gitlab.com/gitlab-org/gitlab/-/issues/337044#note_640010094
+ fetchMilestones: this.fetchMilestones,
+ },
+ {
+ type: 'weight',
+ title: weight,
+ icon: 'weight',
+ token: WeightToken,
+ unique: true,
+ },
];
},
},
methods: {
+ ...mapActions(['fetchMilestones']),
preloadedAuthors() {
return gon?.current_user_id
? [
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
index 84802650dad..e7696b8d31b 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
@@ -87,7 +87,7 @@ export default {
<div>
<header
v-show="showHeader"
- class="gl-display-flex gl-justify-content-space-between gl-align-items-flex-start gl-mb-3"
+ class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-mb-2"
>
<span class="gl-vertical-align-middle">
<slot name="title">
@@ -97,7 +97,8 @@ export default {
</span>
<gl-button
v-if="canUpdate"
- variant="link"
+ category="tertiary"
+ size="small"
class="gl-text-gray-900! gl-ml-5 js-sidebar-dropdown-toggle edit-link"
data-testid="edit-button"
@click="toggle"
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
index 29febd0fa51..e74463825c5 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
@@ -25,6 +25,8 @@ export default {
data() {
return {
loading: false,
+ oldIid: null,
+ isEditing: false,
};
},
computed: {
@@ -72,6 +74,15 @@ export default {
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) {
@@ -84,8 +95,14 @@ export default {
.filter((label) => !payload.find((selected) => selected.id === label.id))
.map((label) => label.id);
- const input = { addLabelIds, removeLabelIds, projectPath: this.projectPathForActiveIssue };
+ 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 {
@@ -115,6 +132,8 @@ export default {
:title="__('Labels')"
:loading="loading"
data-testid="sidebar-labels"
+ @open="isEditing = true"
+ @close="isEditing = false"
>
<template #collapsed>
<gl-label