summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/boards/components
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/boards/components
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
downloadgitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/boards/components')
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column.vue143
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column_form.vue131
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column_trigger.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue83
-rw-r--r--app/assets/javascripts/boards/components/board_card_deprecated.vue61
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue (renamed from app/assets/javascripts/boards/components/issue_card_inner.vue)84
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout.vue98
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout_deprecated.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue21
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue31
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue75
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue91
-rw-r--r--app/assets/javascripts/boards/components/board_list_deprecated.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue51
-rw-r--r--app/assets/javascripts/boards/components/board_list_header_deprecated.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue_deprecated.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js6
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue43
-rw-r--r--app/assets/javascripts/boards/components/boards_selector_deprecated.vue7
-rw-r--r--app/assets/javascripts/boards/components/config_toggle.vue64
-rw-r--r--app/assets/javascripts/boards/components/filtered_search.vue54
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue6
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue6
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue2
-rw-r--r--app/assets/javascripts/boards/components/item_count.vue (renamed from app/assets/javascripts/boards/components/issue_count.vue)10
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue6
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue11
-rw-r--r--app/assets/javascripts/boards/components/project_select_deprecated.vue1
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue88
31 files changed, 790 insertions, 409 deletions
diff --git a/app/assets/javascripts/boards/components/board_add_new_column.vue b/app/assets/javascripts/boards/components/board_add_new_column.vue
new file mode 100644
index 00000000000..3c7c792b787
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_add_new_column.vue
@@ -0,0 +1,143 @@
+<script>
+import {
+ GlFormRadio,
+ GlFormRadioGroup,
+ GlLabel,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
+import { ListType } from '~/boards/constants';
+import boardsStore from '~/boards/stores/boards_store';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { isScopedLabel } from '~/lib/utils/common_utils';
+
+export default {
+ components: {
+ BoardAddNewColumnForm,
+ GlFormRadio,
+ GlFormRadioGroup,
+ GlLabel,
+ },
+ directives: {
+ GlTooltip,
+ },
+ inject: ['scopedLabelsAvailable'],
+ data() {
+ return {
+ selectedId: null,
+ };
+ },
+ computed: {
+ ...mapState(['labels', 'labelsLoading']),
+ ...mapGetters(['getListByLabelId', 'shouldUseGraphQL']),
+ selectedLabel() {
+ if (!this.selectedId) {
+ return null;
+ }
+ return this.labels.find(({ id }) => id === this.selectedId);
+ },
+ columnForSelected() {
+ return this.getListByLabelId(this.selectedId);
+ },
+ },
+ created() {
+ this.filterItems();
+ },
+ methods: {
+ ...mapActions(['createList', 'fetchLabels', 'highlightList', 'setAddColumnFormVisibility']),
+ highlight(listId) {
+ if (this.shouldUseGraphQL) {
+ this.highlightList(listId);
+ } else {
+ const list = boardsStore.state.lists.find(({ id }) => id === listId);
+ list.highlighted = true;
+ setTimeout(() => {
+ list.highlighted = false;
+ }, 2000);
+ }
+ },
+ addList() {
+ if (!this.selectedLabel) {
+ return;
+ }
+
+ this.setAddColumnFormVisibility(false);
+
+ if (this.columnForSelected) {
+ const listId = this.columnForSelected.id;
+ this.highlight(listId);
+ return;
+ }
+
+ if (this.shouldUseGraphQL) {
+ this.createList({ labelId: this.selectedId });
+ } else {
+ const listObj = {
+ labelId: getIdFromGraphQLId(this.selectedId),
+ title: this.selectedLabel.title,
+ position: boardsStore.state.lists.length - 2,
+ list_type: ListType.label,
+ label: this.selectedLabel,
+ };
+
+ boardsStore.new(listObj);
+ }
+ },
+
+ filterItems(searchTerm) {
+ this.fetchLabels(searchTerm);
+ },
+
+ showScopedLabels(label) {
+ return this.scopedLabelsAvailable && isScopedLabel(label);
+ },
+ },
+};
+</script>
+
+<template>
+ <board-add-new-column-form
+ :loading="labelsLoading"
+ :form-description="__('A label list displays issues with the selected label.')"
+ :search-label="__('Select label')"
+ :search-placeholder="__('Search labels')"
+ :selected-id="selectedId"
+ @filter-items="filterItems"
+ @add-list="addList"
+ >
+ <template slot="selected">
+ <gl-label
+ v-if="selectedLabel"
+ v-gl-tooltip
+ :title="selectedLabel.title"
+ :description="selectedLabel.description"
+ :background-color="selectedLabel.color"
+ :scoped="showScopedLabels(selectedLabel)"
+ />
+ </template>
+
+ <template slot="items">
+ <gl-form-radio-group
+ v-if="labels.length > 0"
+ v-model="selectedId"
+ class="gl-overflow-y-auto gl-px-5 gl-pt-3"
+ >
+ <label
+ v-for="label in labels"
+ :key="label.id"
+ class="gl-display-flex gl-flex-align-items-center gl-mb-5 gl-font-weight-normal"
+ >
+ <gl-form-radio :value="label.id" class="gl-mb-0" />
+ <span
+ class="dropdown-label-box gl-top-0"
+ :style="{
+ backgroundColor: label.color,
+ }"
+ ></span>
+ <span>{{ label.title }}</span>
+ </label>
+ </gl-form-radio-group>
+ </template>
+ </board-add-new-column-form>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_form.vue b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
new file mode 100644
index 00000000000..d85343a5390
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_add_new_column_form.vue
@@ -0,0 +1,131 @@
+<script>
+import { GlButton, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
+import { mapActions } from 'vuex';
+import { __ } from '~/locale';
+
+export default {
+ i18n: {
+ add: __('Add to board'),
+ cancel: __('Cancel'),
+ newList: __('New list'),
+ noneSelected: __('None'),
+ noResults: __('No matching results'),
+ selected: __('Selected'),
+ },
+ components: {
+ GlButton,
+ GlFormGroup,
+ GlSearchBoxByType,
+ GlSkeletonLoader,
+ },
+ props: {
+ loading: {
+ type: Boolean,
+ required: true,
+ },
+ formDescription: {
+ type: String,
+ required: true,
+ },
+ searchLabel: {
+ type: String,
+ required: true,
+ },
+ searchPlaceholder: {
+ type: String,
+ required: true,
+ },
+ selectedId: {
+ type: [Number, String],
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ searchValue: '',
+ };
+ },
+ methods: {
+ ...mapActions(['setAddColumnFormVisibility']),
+ },
+};
+</script>
+
+<template>
+ <div
+ class="board-add-new-list board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal gl-flex-shrink-0"
+ data-testid="board-add-new-column"
+ data-qa-selector="board_add_new_list"
+ >
+ <div
+ class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base gl-bg-white"
+ >
+ <h3
+ class="gl-font-base gl-px-5 gl-py-5 gl-m-0 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
+ data-testid="board-add-column-form-title"
+ >
+ {{ $options.i18n.newList }}
+ </h3>
+
+ <div class="gl-display-flex gl-flex-direction-column gl-h-full gl-overflow-hidden">
+ <slot name="select-list-type">
+ <div class="gl-mb-5"></div>
+ </slot>
+
+ <p class="gl-px-5">{{ formDescription }}</p>
+
+ <div class="gl-px-5 gl-pb-4">
+ <label class="gl-mb-2">{{ $options.i18n.selected }}</label>
+ <slot name="selected">
+ <div class="gl-text-gray-500">{{ $options.i18n.noneSelected }}</div>
+ </slot>
+ </div>
+
+ <gl-form-group
+ class="gl-mx-5 gl-mb-3"
+ :label="searchLabel"
+ label-for="board-available-column-entities"
+ >
+ <gl-search-box-by-type
+ id="board-available-column-entities"
+ v-model="searchValue"
+ debounce="250"
+ :placeholder="searchPlaceholder"
+ @input="$emit('filter-items', $event)"
+ />
+ </gl-form-group>
+
+ <div v-if="loading" class="gl-px-5">
+ <gl-skeleton-loader :width="500" :height="172">
+ <rect width="480" height="20" x="10" y="15" rx="4" />
+ <rect width="380" height="20" x="10" y="50" rx="4" />
+ <rect width="430" height="20" x="10" y="85" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+
+ <slot v-else name="items">
+ <p class="gl-mx-5">{{ $options.i18n.noResults }}</p>
+ </slot>
+ </div>
+ <div
+ class="gl-display-flex gl-p-3 gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
+ >
+ <gl-button
+ data-testid="cancelAddNewColumn"
+ class="gl-ml-auto gl-mr-3"
+ @click="setAddColumnFormVisibility(false)"
+ >{{ $options.i18n.cancel }}</gl-button
+ >
+ <gl-button
+ data-testid="addNewColumnButton"
+ :disabled="!selectedId"
+ variant="confirm"
+ class="gl-mr-4"
+ @click="$emit('add-list')"
+ >{{ $options.i18n.add }}</gl-button
+ >
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
index 85fca589279..7c08e33be7e 100644
--- a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
+++ b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue
@@ -13,7 +13,7 @@ export default {
</script>
<template>
- <span class="gl-ml-3 gl-display-flex gl-align-items-center">
+ <span class="gl-ml-3 gl-display-flex gl-align-items-center" data-testid="boards-create-list">
<gl-button variant="confirm" @click="setAddColumnFormVisibility(true)"
>{{ __('Create list') }}
</gl-button>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index e6009343626..aacea0b970c 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -1,14 +1,11 @@
<script>
-import sidebarEventHub from '~/sidebar/event_hub';
-import eventHub from '../eventhub';
-import boardsStore from '../stores/boards_store';
-import BoardCardLayout from './board_card_layout.vue';
-import BoardCardLayoutDeprecated from './board_card_layout_deprecated.vue';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import BoardCardInner from './board_card_inner.vue';
export default {
- name: 'BoardsIssueCard',
+ name: 'BoardCard',
components: {
- BoardCardLayout: gon.features?.graphqlBoardLists ? BoardCardLayout : BoardCardLayoutDeprecated,
+ BoardCardInner,
},
props: {
list: {
@@ -16,34 +13,46 @@ export default {
default: () => ({}),
required: false,
},
- issue: {
+ item: {
type: Object,
default: () => ({}),
required: false,
},
+ disabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ index: {
+ type: Number,
+ default: 0,
+ required: false,
+ },
},
- methods: {
- // These are methods instead of computed's, because boardsStore is not reactive.
+ computed: {
+ ...mapState(['selectedBoardItems', 'activeId']),
+ ...mapGetters(['isSwimlanesOn']),
isActive() {
- return this.getActiveId() === this.issue.id;
+ return this.item.id === this.activeId;
},
- getActiveId() {
- return boardsStore.detail?.issue?.id;
+ multiSelectVisible() {
+ return (
+ !this.activeId &&
+ this.selectedBoardItems.findIndex((boardItem) => boardItem.id === this.item.id) > -1
+ );
},
- showIssue({ isMultiSelect }) {
- // If no issues are opened, close all sidebars first
- if (!this.getActiveId()) {
- sidebarEventHub.$emit('sidebar.closeAll');
- }
- if (this.isActive()) {
- eventHub.$emit('clearDetailIssue', isMultiSelect);
+ },
+ methods: {
+ ...mapActions(['toggleBoardItemMultiSelection', 'toggleBoardItem']),
+ toggleIssue(e) {
+ // Don't do anything if this happened on a no trigger element
+ if (e.target.classList.contains('js-no-trigger')) return;
- if (isMultiSelect) {
- eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- }
+ const isMultiSelect = e.ctrlKey || e.metaKey;
+ if (isMultiSelect) {
+ this.toggleBoardItemMultiSelection(this.item);
} else {
- eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- boardsStore.setListDetail(this.list);
+ this.toggleBoardItem({ boardItem: this.item });
}
},
},
@@ -51,12 +60,22 @@ export default {
</script>
<template>
- <board-card-layout
+ <li
data-qa-selector="board_card"
- :issue="issue"
- :list="list"
- :is-active="isActive()"
- v-bind="$attrs"
- @show="showIssue"
- />
+ :class="{
+ 'multi-select': multiSelectVisible,
+ 'user-can-drag': !disabled && item.id,
+ 'is-disabled': disabled || !item.id,
+ 'is-active': isActive,
+ }"
+ :index="index"
+ :data-item-id="item.id"
+ :data-item-iid="item.iid"
+ :data-item-path="item.referencePath"
+ data-testid="board_card"
+ class="board-card gl-p-5 gl-rounded-base"
+ @mouseup="toggleIssue($event)"
+ >
+ <board-card-inner :list="list" :item="item" :update-filters="true" />
+ </li>
</template>
diff --git a/app/assets/javascripts/boards/components/board_card_deprecated.vue b/app/assets/javascripts/boards/components/board_card_deprecated.vue
new file mode 100644
index 00000000000..e12a2836f67
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_card_deprecated.vue
@@ -0,0 +1,61 @@
+<script>
+// This component is being replaced in favor of './board_card.vue' for GraphQL boards
+import sidebarEventHub from '~/sidebar/event_hub';
+import eventHub from '../eventhub';
+import boardsStore from '../stores/boards_store';
+import BoardCardLayoutDeprecated from './board_card_layout_deprecated.vue';
+
+export default {
+ components: {
+ BoardCardLayout: BoardCardLayoutDeprecated,
+ },
+ props: {
+ list: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ issue: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ },
+ methods: {
+ // These are methods instead of computed's, because boardsStore is not reactive.
+ isActive() {
+ return this.getActiveId() === this.issue.id;
+ },
+ getActiveId() {
+ return boardsStore.detail?.issue?.id;
+ },
+ showIssue({ isMultiSelect }) {
+ // If no issues are opened, close all sidebars first
+ if (!this.getActiveId()) {
+ sidebarEventHub.$emit('sidebar.closeAll');
+ }
+ if (this.isActive()) {
+ eventHub.$emit('clearDetailIssue', isMultiSelect);
+
+ if (isMultiSelect) {
+ eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
+ }
+ } else {
+ eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
+ boardsStore.setListDetail(this.list);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <board-card-layout
+ data-qa-selector="board_card"
+ :issue="issue"
+ :list="list"
+ :is-active="isActive()"
+ v-bind="$attrs"
+ @show="showIssue"
+ />
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index e5ea30df767..d4d6b17a589 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -1,8 +1,8 @@
<script>
import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { sortBy } from 'lodash';
-import { mapActions, mapState } from 'vuex';
-import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
+import { mapActions, mapGetters, mapState } from 'vuex';
+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';
@@ -26,10 +26,10 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [issueCardInner],
- inject: ['groupId', 'rootPath', 'scopedLabelsAvailable'],
+ mixins: [boardCardInner],
+ inject: ['rootPath', 'scopedLabelsAvailable'],
props: {
- issue: {
+ item: {
type: Object,
required: true,
},
@@ -53,18 +53,19 @@ export default {
},
computed: {
...mapState(['isShowingLabels']),
+ ...mapGetters(['isEpicBoard']),
cappedAssignees() {
// e.g. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
// Otherwise render up to the limitBeforeCounter
- if (this.issue.assignees.length <= this.maxRender) {
- return this.issue.assignees.slice(0, this.maxRender);
+ if (this.item.assignees.length <= this.maxRender) {
+ return this.item.assignees.slice(0, this.maxRender);
}
- return this.issue.assignees.slice(0, this.limitBeforeCounter);
+ return this.item.assignees.slice(0, this.limitBeforeCounter);
},
numberOverLimit() {
- return this.issue.assignees.length - this.limitBeforeCounter;
+ return this.item.assignees.length - this.limitBeforeCounter;
},
assigneeCounterTooltip() {
const { numberOverLimit, maxCounter } = this;
@@ -79,31 +80,35 @@ export default {
return `+${this.numberOverLimit}`;
},
shouldRenderCounter() {
- if (this.issue.assignees.length <= this.maxRender) {
+ if (this.item.assignees.length <= this.maxRender) {
return false;
}
- return this.issue.assignees.length > this.numberOverLimit;
+ return this.item.assignees.length > this.numberOverLimit;
},
- issueId() {
- if (this.issue.iid) {
- return `#${this.issue.iid}`;
+ itemPrefix() {
+ return this.isEpicBoard ? '&' : '#';
+ },
+
+ itemId() {
+ if (this.item.iid) {
+ return `${this.itemPrefix}${this.item.iid}`;
}
return false;
},
showLabelFooter() {
- return this.isShowingLabels && this.issue.labels.find(this.showLabel);
+ return this.isShowingLabels && this.item.labels.find(this.showLabel);
},
- issueReferencePath() {
- const { referencePath, groupId } = this.issue;
- return !groupId ? referencePath.split('#')[0] : null;
+ itemReferencePath() {
+ const { referencePath } = this.item;
+ return referencePath.split(this.itemPrefix)[0];
},
orderedLabels() {
- return sortBy(this.issue.labels.filter(this.isNonListLabel), 'title');
+ return sortBy(this.item.labels.filter(this.isNonListLabel), 'title');
},
blockedLabel() {
- if (this.issue.blockedByCount) {
- return n__(`Blocked by %d issue`, `Blocked by %d issues`, this.issue.blockedByCount);
+ if (this.item.blockedByCount) {
+ return n__(`Blocked by %d issue`, `Blocked by %d issues`, this.item.blockedByCount);
}
return __('Blocked issue');
},
@@ -160,7 +165,7 @@ export default {
<div class="gl-display-flex" dir="auto">
<h4 class="board-card-title gl-mb-0 gl-mt-0">
<gl-icon
- v-if="issue.blocked"
+ v-if="item.blocked"
v-gl-tooltip
name="issue-block"
:title="blockedLabel"
@@ -169,7 +174,7 @@ export default {
data-testid="issue-blocked-icon"
/>
<gl-icon
- v-if="issue.confidential"
+ v-if="item.confidential"
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
@@ -177,11 +182,11 @@ export default {
:aria-label="__('Confidential')"
/>
<a
- :href="issue.path || issue.webUrl || ''"
- :title="issue.title"
+ :href="item.path || item.webUrl || ''"
+ :title="item.title"
class="js-no-trigger"
@mousemove.stop
- >{{ issue.title }}</a
+ >{{ item.title }}</a
>
</h4>
</div>
@@ -205,29 +210,30 @@ export default {
class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden js-board-card-number-container"
>
<span
- v-if="issue.referencePath"
+ v-if="item.referencePath"
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
+ :class="{ 'gl-font-base': isEpicBoard }"
>
<tooltip-on-truncate
- v-if="issueReferencePath"
- :title="issueReferencePath"
+ v-if="itemReferencePath"
+ :title="itemReferencePath"
placement="bottom"
- class="board-issue-path gl-text-truncate gl-font-weight-bold"
- >{{ issueReferencePath }}</tooltip-on-truncate
+ class="board-item-path gl-text-truncate gl-font-weight-bold"
+ >{{ itemReferencePath }}</tooltip-on-truncate
>
- #{{ issue.iid }}
+ {{ itemId }}
</span>
<span class="board-info-items gl-mt-3 gl-display-inline-block">
<issue-due-date
- v-if="issue.dueDate"
- :date="issue.dueDate"
- :closed="issue.closed || Boolean(issue.closedAt)"
+ v-if="item.dueDate"
+ :date="item.dueDate"
+ :closed="item.closed || Boolean(item.closedAt)"
/>
- <issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
+ <issue-time-estimate v-if="item.timeEstimate" :estimate="item.timeEstimate" />
<issue-card-weight
- v-if="validIssueWeight"
- :weight="issue.weight"
- @click="filterByWeight(issue.weight)"
+ v-if="validIssueWeight(item)"
+ :weight="item.weight"
+ @click="filterByWeight(item.weight)"
/>
</span>
</div>
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue
deleted file mode 100644
index 5e3c3702519..00000000000
--- a/app/assets/javascripts/boards/components/board_card_layout.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
-import { ISSUABLE } from '~/boards/constants';
-import IssueCardInner from './issue_card_inner.vue';
-
-export default {
- name: 'BoardCardLayout',
- components: {
- IssueCardInner,
- },
- props: {
- list: {
- type: Object,
- default: () => ({}),
- required: false,
- },
- issue: {
- type: Object,
- default: () => ({}),
- required: false,
- },
- disabled: {
- type: Boolean,
- default: false,
- required: false,
- },
- index: {
- type: Number,
- default: 0,
- required: false,
- },
- isActive: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- data() {
- return {
- showDetail: false,
- };
- },
- computed: {
- ...mapState(['selectedBoardItems']),
- ...mapGetters(['isSwimlanesOn']),
- multiSelectVisible() {
- return this.selectedBoardItems.findIndex((boardItem) => boardItem.id === this.issue.id) > -1;
- },
- },
- methods: {
- ...mapActions(['setActiveId', 'toggleBoardItemMultiSelection']),
- mouseDown() {
- this.showDetail = true;
- },
- mouseMove() {
- this.showDetail = false;
- },
- showIssue(e) {
- // Don't do anything if this happened on a no trigger element
- if (e.target.classList.contains('js-no-trigger')) return;
-
- const isMultiSelect = e.ctrlKey || e.metaKey;
-
- if (!isMultiSelect) {
- this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE });
- } else {
- this.toggleBoardItemMultiSelection(this.issue);
- }
-
- if (this.showDetail || isMultiSelect) {
- this.showDetail = false;
- }
- },
- },
-};
-</script>
-
-<template>
- <li
- :class="{
- 'multi-select': multiSelectVisible,
- 'user-can-drag': !disabled && issue.id,
- 'is-disabled': disabled || !issue.id,
- 'is-active': isActive,
- }"
- :index="index"
- :data-issue-id="issue.id"
- :data-issue-iid="issue.iid"
- :data-issue-path="issue.referencePath"
- data-testid="board_card"
- class="board-card gl-p-5 gl-rounded-base"
- @mousedown="mouseDown"
- @mousemove="mouseMove"
- @mouseup="showIssue($event)"
- >
- <issue-card-inner :list="list" :issue="issue" :update-filters="true" />
- </li>
-</template>
diff --git a/app/assets/javascripts/boards/components/board_card_layout_deprecated.vue b/app/assets/javascripts/boards/components/board_card_layout_deprecated.vue
index f9a726134a3..3381e4c3a7d 100644
--- a/app/assets/javascripts/boards/components/board_card_layout_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_card_layout_deprecated.vue
@@ -3,13 +3,12 @@ import { mapActions, mapGetters } from 'vuex';
import { ISSUABLE } from '~/boards/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import boardsStore from '../stores/boards_store';
-import IssueCardInner from './issue_card_inner.vue';
import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue';
export default {
name: 'BoardCardLayout',
components: {
- IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated,
+ IssueCardInner: IssueCardInnerDeprecated,
},
mixins: [glFeatureFlagMixin()],
props: {
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 41b9ee795eb..c9e667d526c 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -32,12 +32,12 @@ export default {
},
computed: {
...mapState(['filterParams', 'highlightedLists']),
- ...mapGetters(['getIssuesByList']),
+ ...mapGetters(['getBoardItemsByList']),
highlighted() {
return this.highlightedLists.includes(this.list.id);
},
- listIssues() {
- return this.getIssuesByList(this.list.id);
+ listItems() {
+ return this.getBoardItemsByList(this.list.id);
},
isListDraggable() {
return isListDraggable(this.list);
@@ -46,11 +46,20 @@ export default {
watch: {
filterParams: {
handler() {
- this.fetchIssuesForList({ listId: this.list.id });
+ if (this.list.id) {
+ this.fetchItemsForList({ listId: this.list.id });
+ }
},
deep: true,
immediate: true,
},
+ 'list.id': {
+ handler(id) {
+ if (id) {
+ this.fetchItemsForList({ listId: this.list.id });
+ }
+ },
+ },
highlighted: {
handler(highlighted) {
if (highlighted) {
@@ -63,7 +72,7 @@ export default {
},
},
methods: {
- ...mapActions(['fetchIssuesForList']),
+ ...mapActions(['fetchItemsForList']),
},
};
</script>
@@ -87,7 +96,7 @@ export default {
<board-list
ref="board-list"
:disabled="disabled"
- :issues="listIssues"
+ :board-items="listItems"
:list="list"
:can-admin-list="canAdminList"
/>
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 9b10e7d7db5..e9c4237d759 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -3,6 +3,7 @@ import { GlAlert } from '@gitlab/ui';
import { sortBy } from 'lodash';
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
+import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { sortableEnd, sortableStart } from '~/boards/mixins/sortable_default_options';
import defaultSortableConfig from '~/sortable/sortable_config';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -11,7 +12,11 @@ import BoardColumnDeprecated from './board_column_deprecated.vue';
export default {
components: {
- BoardColumn: gon.features?.graphqlBoardLists ? BoardColumn : BoardColumnDeprecated,
+ BoardAddNewColumn,
+ BoardColumn:
+ gon.features?.graphqlBoardLists || gon.features?.epicBoards
+ ? BoardColumn
+ : BoardColumnDeprecated,
BoardContentSidebar: () => import('ee_component/boards/components/board_content_sidebar.vue'),
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
@@ -33,15 +38,18 @@ export default {
},
},
computed: {
- ...mapState(['boardLists', 'error']),
- ...mapGetters(['isSwimlanesOn']),
+ ...mapState(['boardLists', 'error', 'addColumnForm']),
+ ...mapGetters(['isSwimlanesOn', 'isEpicBoard']),
+ addColumnFormVisible() {
+ return this.addColumnForm?.visible;
+ },
boardListsToUse() {
- return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn
+ return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard
? sortBy([...Object.values(this.boardLists)], 'position')
: this.lists;
},
canDragColumns() {
- return this.glFeatures.graphqlBoardLists && this.canAdminList;
+ return !this.isEpicBoard && this.glFeatures.graphqlBoardLists && this.canAdminList;
},
boardColumnWrapper() {
return this.canDragColumns ? Draggable : 'div';
@@ -62,12 +70,17 @@ export default {
},
methods: {
...mapActions(['moveList']),
+ afterFormEnters() {
+ const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list;
+ el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
+ },
handleDragOnStart() {
sortableStart();
},
handleDragOnEnd(params) {
sortableEnd();
+ if (this.isEpicBoard) return;
const { item, newIndex, oldIndex, to } = params;
@@ -100,13 +113,17 @@ export default {
@end="handleDragOnEnd"
>
<board-column
- v-for="list in boardListsToUse"
- :key="list.id"
+ v-for="(list, index) in boardListsToUse"
+ :key="index"
ref="board"
:can-admin-list="canAdminList"
:list="list"
:disabled="disabled"
/>
+
+ <transition name="slide" @after-enter="afterFormEnters">
+ <board-add-new-column v-if="addColumnFormVisible" />
+ </transition>
</component>
<epics-swimlanes
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index f65f00bcccc..d8504dcfb0f 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,5 +1,6 @@
<script>
import { GlModal } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
import { deprecatedCreateFlash as Flash } from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { getParameterByName } from '~/lib/utils/common_utils';
@@ -106,6 +107,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isEpicBoard', 'isGroupBoard', 'isProjectBoard']),
isNewForm() {
return this.currentPage === formType.new;
},
@@ -161,42 +163,49 @@ export default {
currentMutation() {
return this.board.id ? updateBoardMutation : createBoardMutation;
},
- mutationVariables() {
+ baseMutationVariables() {
const { board } = this;
- /* eslint-disable @gitlab/require-i18n-strings */
- let baseMutationVariables = {
+ const variables = {
name: board.name,
hideBacklogList: board.hide_backlog_list,
hideClosedList: board.hide_closed_list,
};
- if (this.scopedIssueBoardFeatureEnabled) {
- baseMutationVariables = {
- ...baseMutationVariables,
- weight: board.weight,
- assigneeId: board.assignee?.id ? convertToGraphQLId('User', board.assignee.id) : null,
- milestoneId:
- board.milestone?.id || board.milestone?.id === 0
- ? convertToGraphQLId('Milestone', board.milestone.id)
- : null,
- labelIds: board.labels.map(fullLabelId),
- iterationId: board.iteration_id
- ? convertToGraphQLId('Iteration', board.iteration_id)
- : null,
- };
- }
- /* eslint-enable @gitlab/require-i18n-strings */
return board.id
? {
- ...baseMutationVariables,
+ ...variables,
id: fullBoardId(board.id),
}
: {
- ...baseMutationVariables,
- projectPath: this.projectId ? this.fullPath : null,
- groupPath: this.groupId ? this.fullPath : null,
+ ...variables,
+ projectPath: this.isProjectBoard ? this.fullPath : undefined,
+ groupPath: this.isGroupBoard ? this.fullPath : undefined,
};
},
+ boardScopeMutationVariables() {
+ /* eslint-disable @gitlab/require-i18n-strings */
+ return {
+ weight: this.board.weight,
+ assigneeId: this.board.assignee?.id
+ ? convertToGraphQLId('User', this.board.assignee.id)
+ : null,
+ milestoneId:
+ this.board.milestone?.id || this.board.milestone?.id === 0
+ ? convertToGraphQLId('Milestone', this.board.milestone.id)
+ : null,
+ labelIds: this.board.labels.map(fullLabelId),
+ iterationId: this.board.iteration_id
+ ? convertToGraphQLId('Iteration', this.board.iteration_id)
+ : null,
+ };
+ /* eslint-enable @gitlab/require-i18n-strings */
+ },
+ mutationVariables() {
+ return {
+ ...this.baseMutationVariables,
+ ...(this.scopedIssueBoardFeatureEnabled ? this.boardScopeMutationVariables : {}),
+ };
+ },
},
mounted() {
this.resetFormState();
@@ -208,6 +217,16 @@ export default {
setIteration(iterationId) {
this.board.iteration_id = iterationId;
},
+ boardCreateResponse(data) {
+ return data.createBoard.board.webPath;
+ },
+ boardUpdateResponse(data) {
+ const path = data.updateBoard.board.webPath;
+ const param = getParameterByName('group_by')
+ ? `?group_by=${getParameterByName('group_by')}`
+ : '';
+ return `${path}${param}`;
+ },
async createOrUpdateBoard() {
const response = await this.$apollo.mutate({
mutation: this.currentMutation,
@@ -215,14 +234,10 @@ export default {
});
if (!this.board.id) {
- return response.data.createBoard.board.webPath;
+ return this.boardCreateResponse(response.data);
}
- const path = response.data.updateBoard.board.webPath;
- const param = getParameterByName('group_by')
- ? `?group_by=${getParameterByName('group_by')}`
- : '';
- return `${path}${param}`;
+ return this.boardUpdateResponse(response.data);
},
async submit() {
if (this.board.name.length === 0) return;
@@ -309,7 +324,7 @@ export default {
/>
<board-scope
- v-if="scopedIssueBoardFeatureEnabled"
+ v-if="scopedIssueBoardFeatureEnabled && !isEpicBoard"
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 7495b1163be..ae8434be312 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -1,7 +1,7 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import Draggable from 'vuedraggable';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_options';
import { sprintf, __ } from '~/locale';
import defaultSortableConfig from '~/sortable/sortable_config';
@@ -12,9 +12,10 @@ import BoardNewIssue from './board_new_issue.vue';
export default {
name: 'BoardList',
i18n: {
- loadingIssues: __('Loading issues'),
- loadingMoreissues: __('Loading more issues'),
+ loading: __('Loading'),
+ loadingMoreboardItems: __('Loading more'),
showingAllIssues: __('Showing all issues'),
+ showingAllEpics: __('Showing all epics'),
},
components: {
BoardCard,
@@ -30,7 +31,7 @@ export default {
type: Object,
required: true,
},
- issues: {
+ boardItems: {
type: Array,
required: true,
},
@@ -49,14 +50,19 @@ export default {
},
computed: {
...mapState(['pageInfoByListId', 'listsFlags']),
+ ...mapGetters(['isEpicBoard']),
+ listItemsCount() {
+ return this.isEpicBoard ? this.list.epicsCount : this.list.issuesCount;
+ },
paginatedIssueText() {
- return sprintf(__('Showing %{pageSize} of %{total} issues'), {
- pageSize: this.issues.length,
- total: this.list.issuesCount,
+ return sprintf(__('Showing %{pageSize} of %{total} %{issuableType}'), {
+ pageSize: this.boardItems.length,
+ total: this.listItemsCount,
+ issuableType: this.isEpicBoard ? 'epics' : 'issues',
});
},
- issuesSizeExceedsMax() {
- return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount;
+ boardItemsSizeExceedsMax() {
+ return this.list.maxIssueCount > 0 && this.listItemsCount > this.list.maxIssueCount;
},
hasNextPage() {
return this.pageInfoByListId[this.list.id].hasNextPage;
@@ -71,8 +77,13 @@ export default {
// When list is draggable, the reference to the list needs to be accessed differently
return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
},
- showingAllIssues() {
- return this.issues.length === this.list.issuesCount;
+ showingAllItems() {
+ return this.boardItems.length === this.listItemsCount;
+ },
+ showingAllItemsText() {
+ return this.isEpicBoard
+ ? this.$options.i18n.showingAllEpics
+ : this.$options.i18n.showingAllIssues;
},
treeRootWrapper() {
return this.canAdminList ? Draggable : 'ul';
@@ -85,14 +96,14 @@ export default {
tag: 'ul',
'ghost-class': 'board-card-drag-active',
'data-list-id': this.list.id,
- value: this.issues,
+ value: this.boardItems,
};
return this.canAdminList ? options : {};
},
},
watch: {
- issues() {
+ boardItems() {
this.$nextTick(() => {
this.showCount = this.scrollHeight() > Math.ceil(this.listHeight());
});
@@ -112,7 +123,7 @@ export default {
this.listRef.removeEventListener('scroll', this.onScroll);
},
methods: {
- ...mapActions(['fetchIssuesForList', 'moveIssue']),
+ ...mapActions(['fetchItemsForList', 'moveItem']),
listHeight() {
return this.listRef.getBoundingClientRect().height;
},
@@ -126,7 +137,7 @@ export default {
this.listRef.scrollTop = 0;
},
loadNextPage() {
- this.fetchIssuesForList({ listId: this.list.id, fetchNext: true });
+ this.fetchItemsForList({ listId: this.list.id, fetchNext: true });
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
@@ -148,40 +159,40 @@ export default {
handleDragOnEnd(params) {
sortableEnd();
const { newIndex, oldIndex, from, to, item } = params;
- const { issueId, issueIid, issuePath } = item.dataset;
+ const { itemId, itemIid, itemPath } = item.dataset;
const { children } = to;
let moveBeforeId;
let moveAfterId;
- const getIssueId = (el) => Number(el.dataset.issueId);
+ const getItemId = (el) => Number(el.dataset.itemId);
- // If issue is being moved within the same list
+ // If item is being moved within the same list
if (from === to) {
if (newIndex > oldIndex && children.length > 1) {
- // If issue is being moved down we look for the issue that ends up before
- moveBeforeId = getIssueId(children[newIndex]);
+ // If item is being moved down we look for the item that ends up before
+ moveBeforeId = getItemId(children[newIndex]);
} else if (newIndex < oldIndex && children.length > 1) {
- // If issue is being moved up we look for the issue that ends up after
- moveAfterId = getIssueId(children[newIndex]);
+ // If item is being moved up we look for the item that ends up after
+ moveAfterId = getItemId(children[newIndex]);
} else {
- // If issue remains in the same list at the same position we do nothing
+ // If item remains in the same list at the same position we do nothing
return;
}
} else {
- // We look for the issue that ends up before the moved issue if it exists
+ // We look for the item that ends up before the moved item if it exists
if (children[newIndex - 1]) {
- moveBeforeId = getIssueId(children[newIndex - 1]);
+ moveBeforeId = getItemId(children[newIndex - 1]);
}
- // We look for the issue that ends up after the moved issue if it exists
+ // We look for the item that ends up after the moved item if it exists
if (children[newIndex]) {
- moveAfterId = getIssueId(children[newIndex]);
+ moveAfterId = getItemId(children[newIndex]);
}
}
- this.moveIssue({
- issueId,
- issueIid,
- issuePath,
+ this.moveItem({
+ itemId,
+ itemIid,
+ itemPath,
fromListId: from.dataset.listId,
toListId: to.dataset.listId,
moveBeforeId,
@@ -201,7 +212,7 @@ export default {
<div
v-if="loading"
class="gl-mt-4 gl-text-center"
- :aria-label="$options.i18n.loadingIssues"
+ :aria-label="$options.i18n.loading"
data-testid="board_list_loading"
>
<gl-loading-icon />
@@ -214,24 +225,28 @@ export default {
v-bind="treeRootOptions"
:data-board="list.id"
:data-board-type="list.listType"
- :class="{ 'bg-danger-100': issuesSizeExceedsMax }"
+ :class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-card
- v-for="(issue, index) in issues"
+ v-for="(item, index) in boardItems"
ref="issue"
- :key="issue.id"
+ :key="item.id"
:index="index"
:list="list"
- :issue="issue"
+ :item="item"
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
- <gl-loading-icon v-if="loadingMore" :label="$options.i18n.loadingMoreissues" />
- <span v-if="showingAllIssues">{{ $options.i18n.showingAllIssues }}</span>
+ <gl-loading-icon
+ v-if="loadingMore"
+ :label="$options.i18n.loadingMoreboardItems"
+ data-testid="count-loading-icon"
+ />
+ <span v-if="showingAllItems">{{ showingAllItemsText }}</span>
<span v-else>{{ paginatedIssueText }}</span>
</li>
</component>
diff --git a/app/assets/javascripts/boards/components/board_list_deprecated.vue b/app/assets/javascripts/boards/components/board_list_deprecated.vue
index 9b4961d362d..d59fbcc1b31 100644
--- a/app/assets/javascripts/boards/components/board_list_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_list_deprecated.vue
@@ -11,7 +11,7 @@ import {
sortableEnd,
} from '../mixins/sortable_default_options';
import boardsStore from '../stores/boards_store';
-import boardCard from './board_card.vue';
+import boardCard from './board_card_deprecated.vue';
import boardNewIssue from './board_new_issue_deprecated.vue';
// This component is being replaced in favor of './board_list.vue' for GraphQL boards
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index a933370427c..6ccaec4a633 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -8,16 +8,16 @@ import {
GlSprintf,
GlTooltipDirective,
} from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { isListDraggable } from '~/boards/boards_util';
-import { isScopedLabel } from '~/lib/utils/common_utils';
+import { isScopedLabel, parseBoolean } from '~/lib/utils/common_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { n__, s__, __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
import AccessorUtilities from '../../lib/utils/accessor';
import { inactiveId, LIST, ListType } from '../constants';
import eventHub from '../eventhub';
-import IssueCount from './issue_count.vue';
+import ItemCount from './item_count.vue';
export default {
i18n: {
@@ -33,7 +33,7 @@ export default {
GlTooltip,
GlIcon,
GlSprintf,
- IssueCount,
+ ItemCount,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -70,6 +70,7 @@ export default {
},
computed: {
...mapState(['activeId']),
+ ...mapGetters(['isEpicBoard']),
isLoggedIn() {
return Boolean(this.currentUserId);
},
@@ -97,11 +98,14 @@ export default {
showListDetails() {
return !this.list.collapsed || !this.isSwimlanesHeader;
},
- issuesCount() {
+ itemsCount() {
return this.list.issuesCount;
},
- issuesTooltipLabel() {
- return n__(`%d issue`, `%d issues`, this.issuesCount);
+ countIcon() {
+ return 'issues';
+ },
+ itemsTooltipLabel() {
+ return n__(`%d issue`, `%d issues`, this.itemsCount);
},
chevronTooltip() {
return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse;
@@ -110,7 +114,7 @@ export default {
return this.list.collapsed ? 'chevron-down' : 'chevron-right';
},
isNewIssueShown() {
- return this.listType === ListType.backlog || this.showListHeaderButton;
+ return (this.listType === ListType.backlog || this.showListHeaderButton) && !this.isEpicBoard;
},
isSettingsShown() {
return (
@@ -131,8 +135,14 @@ export default {
return !this.disabled && isListDraggable(this.list);
},
},
+ created() {
+ const localCollapsed = parseBoolean(localStorage.getItem(`${this.uniqueKey}.collapsed`));
+ if ((!this.isLoggedIn || this.isEpicBoard) && localCollapsed) {
+ this.toggleListCollapsed({ listId: this.list.id, collapsed: true });
+ }
+ },
methods: {
- ...mapActions(['updateList', 'setActiveId']),
+ ...mapActions(['updateList', 'setActiveId', 'toggleListCollapsed']),
openSidebarSettings() {
if (this.activeId === inactiveId) {
sidebarEventHub.$emit('sidebar.closeAll');
@@ -148,10 +158,10 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
- // eslint-disable-next-line vue/no-mutating-props
- this.list.collapsed = !this.list.collapsed;
+ const collapsed = !this.list.collapsed;
+ this.toggleListCollapsed({ listId: this.list.id, collapsed });
- if (!this.isLoggedIn) {
+ if (!this.isLoggedIn || this.isEpicBoard) {
this.addToLocalStorage();
} else {
this.updateListFunction();
@@ -163,7 +173,7 @@ export default {
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
- localStorage.setItem(`${this.uniqueKey}.expanded`, !this.list.collapsed);
+ localStorage.setItem(`${this.uniqueKey}.collapsed`, this.list.collapsed);
}
},
updateListFunction() {
@@ -203,6 +213,7 @@ export default {
class="board-title-caret no-drag gl-cursor-pointer"
category="tertiary"
size="small"
+ data-testid="board-title-caret"
@click="toggleExpanded"
/>
<!-- EE start -->
@@ -301,11 +312,11 @@ export default {
<div v-if="list.maxIssueCount !== 0">
<gl-sprintf :message="__('%{issuesSize} with a limit of %{maxIssueCount}')">
- <template #issuesSize>{{ issuesTooltipLabel }}</template>
+ <template #issuesSize>{{ itemsTooltipLabel }}</template>
<template #maxIssueCount>{{ list.maxIssueCount }}</template>
</gl-sprintf>
</div>
- <div v-else>• {{ issuesTooltipLabel }}</div>
+ <div v-else>• {{ itemsTooltipLabel }}</div>
<div v-if="weightFeatureAvailable">
<gl-sprintf :message="__('%{totalWeight} total weight')">
@@ -323,13 +334,13 @@ export default {
}"
>
<span class="gl-display-inline-flex">
- <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
- <span ref="issueCount" class="issue-count-badge-count">
- <gl-icon class="gl-mr-2" name="issues" />
- <issue-count :issues-size="issuesCount" :max-issue-count="list.maxIssueCount" />
+ <gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" />
+ <span ref="itemCount" class="issue-count-badge-count">
+ <gl-icon class="gl-mr-2" :name="countIcon" />
+ <item-count :items-size="itemsCount" :max-issue-count="list.maxIssueCount" />
</span>
<!-- EE start -->
- <template v-if="weightFeatureAvailable">
+ <template v-if="weightFeatureAvailable && !isEpicBoard">
<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" />
diff --git a/app/assets/javascripts/boards/components/board_list_header_deprecated.vue b/app/assets/javascripts/boards/components/board_list_header_deprecated.vue
index ff043d3aa01..429ffd4cd06 100644
--- a/app/assets/javascripts/boards/components/board_list_header_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_list_header_deprecated.vue
@@ -17,7 +17,7 @@ import AccessorUtilities from '../../lib/utils/accessor';
import { inactiveId, LIST, ListType } from '../constants';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
-import IssueCount from './issue_count.vue';
+import IssueCount from './item_count.vue';
// This component is being replaced in favor of './board_list_header.vue' for GraphQL boards
@@ -308,7 +308,7 @@ export default {
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
- <issue-count :issues-size="issuesCount" :max-issue-count="list.maxIssueCount" />
+ <issue-count :items-size="issuesCount" :max-issue-count="list.maxIssueCount" />
</span>
<!-- The following is only true in EE. -->
<template v-if="weightFeatureAvailable">
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 1df154688c8..a81c28733cd 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -32,8 +32,9 @@ export default {
},
computed: {
...mapState(['selectedProject']),
+ ...mapGetters(['isGroupBoard']),
disabled() {
- if (this.groupId) {
+ if (this.isGroupBoard) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
@@ -98,7 +99,7 @@ export default {
name="issue_title"
autocomplete="off"
/>
- <project-select v-if="groupId" :group-id="groupId" :list="list" />
+ <project-select v-if="isGroupBoard" :group-id="groupId" :list="list" />
<div class="clearfix gl-mt-3">
<gl-button
ref="submitButton"
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 eff87ff110e..16f23dfff0e 100644
--- a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -31,8 +32,9 @@ export default {
};
},
computed: {
+ ...mapGetters(['isGroupBoard']),
disabled() {
- if (this.groupId) {
+ if (this.isGroupBoard) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
@@ -110,7 +112,7 @@ export default {
name="issue_title"
autocomplete="off"
/>
- <project-select v-if="groupId" :group-id="groupId" :list="list" />
+ <project-select v-if="isGroupBoard" :group-id="groupId" :list="list" />
<div class="clearfix gl-mt-3">
<gl-button
ref="submitButton"
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 6d5a13be3ac..55bc91cbcff 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -20,7 +20,6 @@ import Subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue'
import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
import eventHub from '~/sidebar/event_hub';
import boardsStore from '../stores/boards_store';
-import RemoveBtn from './sidebar/remove_issue.vue';
export default Vue.extend({
components: {
@@ -29,7 +28,6 @@ export default Vue.extend({
GlLabel,
SidebarEpicsSelect: () =>
import('ee_component/sidebar/components/sidebar_item_epics_select.vue'),
- RemoveBtn,
Subscriptions,
TimeTracker,
SidebarAssigneesWidget,
@@ -107,8 +105,8 @@ export default Vue.extend({
closeSidebar() {
this.detail.issue = {};
},
- setAssignees(data) {
- boardsStore.detail.issue.setAssignees(data.issueSetAssignees.issue.assignees.nodes);
+ setAssignees(assignees) {
+ boardsStore.detail.issue.setAssignees(assignees);
},
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 2a064aaa885..5124467136e 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -9,6 +9,9 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import { throttle } from 'lodash';
+import { mapGetters, mapState } from 'vuex';
+
+import BoardForm from 'ee_else_ce/boards/components/board_form.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils';
@@ -18,8 +21,6 @@ import eventHub from '../eventhub';
import groupQuery from '../graphql/group_boards.query.graphql';
import projectQuery from '../graphql/project_boards.query.graphql';
-import BoardForm from './board_form.vue';
-
const MIN_BOARDS_TO_VIEW_RECENT = 10;
export default {
@@ -109,8 +110,10 @@ export default {
};
},
computed: {
+ ...mapState(['boardType']),
+ ...mapGetters(['isGroupBoard']),
parentType() {
- return this.groupId ? 'group' : 'project';
+ return this.boardType;
},
loading() {
return this.loadingRecentBoards || Boolean(this.loadingBoards);
@@ -123,6 +126,9 @@ export default {
board() {
return this.currentBoard;
},
+ showCreate() {
+ return this.multipleIssueBoardsAvailable;
+ },
showDelete() {
return this.boards.length > 1;
},
@@ -158,6 +164,18 @@ export default {
cancel() {
this.showPage('');
},
+ boardUpdate(data) {
+ if (!data?.[this.parentType]) {
+ return [];
+ }
+ return data[this.parentType].boards.edges.map(({ node }) => ({
+ id: getIdFromGraphQLId(node.id),
+ name: node.name,
+ }));
+ },
+ boardQuery() {
+ return this.isGroupBoard ? groupQuery : projectQuery;
+ },
loadBoards(toggleDropdown = true) {
if (toggleDropdown && this.boards.length > 0) {
return;
@@ -167,21 +185,14 @@ export default {
variables() {
return { fullPath: this.fullPath };
},
- query() {
- return this.groupId ? groupQuery : projectQuery;
- },
+ query: this.boardQuery,
loadingKey: 'loadingBoards',
- update(data) {
- if (!data?.[this.parentType]) {
- return [];
- }
- return data[this.parentType].boards.edges.map(({ node }) => ({
- id: getIdFromGraphQLId(node.id),
- name: node.name,
- }));
- },
+ update: this.boardUpdate,
});
+ this.loadRecentBoards();
+ },
+ loadRecentBoards() {
this.loadingRecentBoards = true;
// Follow up to fetch recent boards using GraphQL
// https://gitlab.com/gitlab-org/gitlab/-/issues/300985
@@ -322,7 +333,7 @@ export default {
<gl-dropdown-divider />
<gl-dropdown-item
- v-if="multipleIssueBoardsAvailable"
+ v-if="showCreate"
v-gl-modal-directive="'board-config-modal'"
data-qa-selector="create_new_board_button"
@click.prevent="showPage('new')"
diff --git a/app/assets/javascripts/boards/components/boards_selector_deprecated.vue b/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
index 33ad46a0d29..85c7b27336b 100644
--- a/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
+++ b/app/assets/javascripts/boards/components/boards_selector_deprecated.vue
@@ -9,6 +9,7 @@ import {
GlModalDirective,
} from '@gitlab/ui';
import { throttle } from 'lodash';
+import { mapGetters, mapState } from 'vuex';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -108,8 +109,10 @@ export default {
};
},
computed: {
+ ...mapState(['boardType']),
+ ...mapGetters(['isGroupBoard']),
parentType() {
- return this.groupId ? 'group' : 'project';
+ return this.boardType;
},
loading() {
return this.loadingRecentBoards || Boolean(this.loadingBoards);
@@ -167,7 +170,7 @@ export default {
return { fullPath: this.state.endpoints.fullPath };
},
query() {
- return this.groupId ? groupQuery : projectQuery;
+ return this.isGroupBoard ? groupQuery : projectQuery;
},
loadingKey: 'loadingBoards',
update(data) {
diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue
new file mode 100644
index 00000000000..7ec99e51f5b
--- /dev/null
+++ b/app/assets/javascripts/boards/components/config_toggle.vue
@@ -0,0 +1,64 @@
+<script>
+import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
+import { formType } from '~/boards/constants';
+import eventHub from '~/boards/eventhub';
+import { s__, __ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModalDirective,
+ },
+ props: {
+ boardsStore: {
+ type: Object,
+ required: true,
+ },
+ canAdminList: {
+ type: Boolean,
+ required: true,
+ },
+ hasScope: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ state: this.boardsStore.state,
+ };
+ },
+ computed: {
+ buttonText() {
+ return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope');
+ },
+ tooltipTitle() {
+ return this.hasScope ? __("This board's scope is reduced") : '';
+ },
+ },
+ methods: {
+ showPage() {
+ eventHub.$emit('showBoardModal', formType.edit);
+ return this.boardsStore.showPage(formType.edit);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-ml-3 gl-display-flex gl-align-items-center">
+ <gl-button
+ v-gl-modal-directive="'board-config-modal'"
+ v-gl-tooltip
+ :title="tooltipTitle"
+ :class="{ 'dot-highlight': hasScope }"
+ data-qa-selector="boards_config_button"
+ @click.prevent="showPage"
+ >
+ {{ buttonText }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/filtered_search.vue b/app/assets/javascripts/boards/components/filtered_search.vue
new file mode 100644
index 00000000000..8505ea39a6b
--- /dev/null
+++ b/app/assets/javascripts/boards/components/filtered_search.vue
@@ -0,0 +1,54 @@
+<script>
+import { mapActions } from 'vuex';
+import { historyPushState } from '~/lib/utils/common_utils';
+import { setUrlParams } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+
+export default {
+ i18n: {
+ search: __('Search'),
+ },
+ components: { FilteredSearch },
+ props: {
+ search: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ initialSearch() {
+ return [{ type: 'filtered-search-term', value: { data: this.search } }];
+ },
+ },
+ methods: {
+ ...mapActions(['performSearch']),
+ handleSearch(filters) {
+ let itemValue = '';
+ const [item] = filters;
+
+ if (filters.length === 0) {
+ itemValue = '';
+ } else {
+ itemValue = item?.value?.data;
+ }
+
+ historyPushState(setUrlParams({ search: itemValue }, window.location.href));
+
+ this.performSearch();
+ },
+ },
+};
+</script>
+
+<template>
+ <filtered-search
+ class="gl-w-full"
+ namespace=""
+ :tokens="[]"
+ :search-input-placeholder="$options.i18n.search"
+ :initial-filter-value="initialSearch"
+ @onFilter="handleSearch"
+ />
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
index 069cc2cda22..2652fac1818 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
@@ -2,7 +2,7 @@
import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { sortBy } from 'lodash';
import { mapState } from 'vuex';
-import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
+import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { sprintf, __, n__ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
@@ -24,7 +24,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [issueCardInner],
+ mixins: [boardCardInner],
inject: ['groupId', 'rootPath'],
props: {
issue: {
@@ -207,7 +207,7 @@ export default {
/>
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
<issue-card-weight
- v-if="validIssueWeight"
+ v-if="validIssueWeight(issue)"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
/>
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 7e3f36c8a17..73ec008c2b6 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -86,7 +86,11 @@ export default {
<template>
<span>
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
- <gl-icon :class="{ 'text-danger': isPastDue }" class="board-card-info-icon" name="calendar" />
+ <gl-icon
+ :class="{ 'text-danger': isPastDue }"
+ class="board-card-info-icon gl-mr-2"
+ name="calendar"
+ />
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue
index 42d187b9b40..1ab7deebfaf 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -37,7 +37,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
- <gl-icon name="hourglass" class="board-card-info-icon" />
+ <gl-icon name="hourglass" class="board-card-info-icon gl-mr-2" />
<time class="board-card-info-text">{{ timeEstimate }}</time>
</span>
<gl-tooltip
diff --git a/app/assets/javascripts/boards/components/issue_count.vue b/app/assets/javascripts/boards/components/item_count.vue
index d55f7151d7e..9b1ff254766 100644
--- a/app/assets/javascripts/boards/components/issue_count.vue
+++ b/app/assets/javascripts/boards/components/item_count.vue
@@ -7,7 +7,7 @@ export default {
required: false,
default: 0,
},
- issuesSize: {
+ itemsSize: {
type: Number,
required: false,
default: 0,
@@ -18,16 +18,16 @@ export default {
return this.maxIssueCount !== 0;
},
issuesExceedMax() {
- return this.isMaxLimitSet && this.issuesSize > this.maxIssueCount;
+ return this.isMaxLimitSet && this.itemsSize > this.maxIssueCount;
},
},
};
</script>
<template>
- <div class="issue-count text-nowrap">
- <span class="js-issue-size" :class="{ 'text-danger': issuesExceedMax }">
- {{ issuesSize }}
+ <div class="item-count text-nowrap">
+ <span :class="{ 'text-danger': issuesExceedMax }" data-testid="board-items-count">
+ {{ itemsSize }}
</span>
<span v-if="isMaxLimitSet" class="js-max-issue-size">
{{ maxIssueCount }}
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index bf69f8140d5..e66cae0ce18 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -2,11 +2,11 @@
import { GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import ModalStore from '../../stores/modal_store';
-import IssueCardInner from '../issue_card_inner.vue';
+import BoardCardInner from '../board_card_inner.vue';
export default {
components: {
- IssueCardInner,
+ BoardCardInner,
GlIcon,
},
props: {
@@ -126,7 +126,7 @@ export default {
class="board-card position-relative p-3 rounded"
@click="toggleIssue($event, issue)"
>
- <issue-card-inner :issue="issue" />
+ <board-card-inner :item="issue" />
<gl-icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index cfc1752a828..77b6af77652 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -7,7 +7,7 @@ import {
GlIntersectionObserver,
GlLoadingIcon,
} from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import { ListType } from '../constants';
@@ -49,7 +49,8 @@ export default {
};
},
computed: {
- ...mapState(['groupProjects', 'groupProjectsFlags']),
+ ...mapState(['groupProjectsFlags']),
+ ...mapGetters(['activeGroupProjects']),
selectedProjectName() {
return this.selectedProject.name || this.$options.i18n.dropdownText;
},
@@ -65,7 +66,7 @@ export default {
};
},
isFetchResultEmpty() {
- return this.groupProjects.length === 0;
+ return this.activeGroupProjects.length === 0;
},
hasNextPage() {
return this.groupProjectsFlags.pageInfo?.hasNextPage;
@@ -84,7 +85,7 @@ export default {
methods: {
...mapActions(['fetchGroupProjects', 'setSelectedProject']),
selectProject(projectId) {
- this.selectedProject = this.groupProjects.find((project) => project.id === projectId);
+ this.selectedProject = this.activeGroupProjects.find((project) => project.id === projectId);
this.setSelectedProject(this.selectedProject);
},
loadMoreProjects() {
@@ -113,7 +114,7 @@ export default {
:placeholder="$options.i18n.searchPlaceholder"
/>
<gl-dropdown-item
- v-for="project in groupProjects"
+ v-for="project in activeGroupProjects"
v-show="!groupProjectsFlags.isLoading"
:key="project.id"
:name="project.name"
diff --git a/app/assets/javascripts/boards/components/project_select_deprecated.vue b/app/assets/javascripts/boards/components/project_select_deprecated.vue
index 5605e9945ea..afe161d9c54 100644
--- a/app/assets/javascripts/boards/components/project_select_deprecated.vue
+++ b/app/assets/javascripts/boards/components/project_select_deprecated.vue
@@ -25,6 +25,7 @@ export default {
with_shared: false,
include_subgroups: true,
order_by: 'similarity',
+ archived: false,
},
components: {
GlLoadingIcon,
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
index aa4fdcf9a94..f01c8e8fa20 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
@@ -64,6 +64,8 @@ export default {
v-if="!activeIssue.emailsDisabled"
:value="activeIssue.subscribed"
:is-loading="loading"
+ :label="$options.i18n.header.title"
+ label-position="hidden"
data-testid="notification-subscribe-toggle"
@change="handleToggleSubscription"
/>
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
deleted file mode 100644
index 8d65f3240c8..00000000000
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import axios from '~/lib/utils/axios_utils';
-import { deprecatedCreateFlash as Flash } from '../../../flash';
-import { __ } from '../../../locale';
-import boardsStore from '../../stores/boards_store';
-
-export default {
- components: {
- GlButton,
- },
- props: {
- issue: {
- type: Object,
- required: true,
- },
- list: {
- type: Object,
- required: true,
- },
- },
- computed: {
- updateUrl() {
- return this.issue.path;
- },
- },
- methods: {
- removeIssue() {
- const { issue } = this;
- const lists = issue.getLists();
- const req = this.buildPatchRequest(issue, lists);
-
- const data = {
- issue: this.seedPatchRequest(issue, req),
- };
-
- if (data.issue.label_ids.length === 0) {
- data.issue.label_ids = [''];
- }
-
- // Post the remove data
- axios.patch(this.updateUrl, data).catch(() => {
- Flash(__('Failed to remove issue from board, please try again.'));
-
- lists.forEach((list) => {
- list.addIssue(issue);
- });
- });
-
- // Remove from the frontend store
- lists.forEach((list) => {
- list.removeIssue(issue);
- });
-
- boardsStore.clearDetailIssue();
- },
- /**
- * Build the default patch request.
- */
- buildPatchRequest(issue, lists) {
- const listLabelIds = lists.map((list) => list.label.id);
-
- const labelIds = issue.labels
- .map((label) => label.id)
- .filter((id) => !listLabelIds.includes(id));
-
- return {
- label_ids: labelIds,
- };
- },
- /**
- * Seed the given patch request.
- *
- * (This is overridden in EE)
- */
- seedPatchRequest(issue, req) {
- return req;
- },
- },
-};
-</script>
-<template>
- <div class="block list">
- <gl-button variant="default" category="secondary" block="block" @click="removeIssue">
- {{ __('Remove from board') }}
- </gl-button>
- </div>
-</template>