summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/boards/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/boards/components')
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue18
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue32
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue48
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_new_item.vue13
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue43
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue54
-rw-r--r--app/assets/javascripts/boards/components/issue_board_filtered_search.vue1
10 files changed, 116 insertions, 103 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index ea80496c3f5..aee61a5b2a5 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -11,12 +11,10 @@ import { sortBy } from 'lodash';
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';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { ListType } from '../constants';
-import eventHub from '../eventhub';
import BoardBlockedIcon from './board_blocked_icon.vue';
import IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
@@ -176,18 +174,10 @@ export default {
)
);
},
- filterByLabel(label) {
- if (!this.updateFilters) return;
+ labelTarget(label) {
const filterPath = window.location.search ? `${window.location.search}&` : '?';
- const filter = `label_name[]=${encodeURIComponent(label.title)}`;
-
- if (!filterPath.includes(filter)) {
- updateHistory({
- url: `${filterPath}${filter}`,
- });
- this.performSearch();
- eventHub.$emit('updateTokens');
- }
+ const value = encodeURIComponent(label.title);
+ return `${filterPath}label_name[]=${value}`;
},
showScopedLabel(label) {
return this.scopedLabelsAvailable && isScopedLabel(label);
@@ -242,7 +232,7 @@ export default {
:description="label.description"
size="sm"
:scoped="showScopedLabel(label)"
- @click="filterByLabel(label)"
+ :target="labelTarget(label)"
/>
</template>
</div>
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index 156029b62b0..0320b4d925e 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -184,29 +184,15 @@ export default {
:issuable-type="issuableType"
data-testid="sidebar-milestones"
/>
- <template v-if="!glFeatures.iterationCadences">
- <sidebar-dropdown-widget
- v-if="iterationFeatureAvailable && !isIncidentSidebar"
- :iid="activeBoardItem.iid"
- issuable-attribute="iteration"
- :workspace-path="projectPathForActiveIssue"
- :attr-workspace-path="groupPathForActiveIssue"
- :issuable-type="issuableType"
- class="gl-mt-5"
- data-testid="iteration-edit"
- />
- </template>
- <template v-else>
- <iteration-sidebar-dropdown-widget
- v-if="iterationFeatureAvailable && !isIncidentSidebar"
- :iid="activeBoardItem.iid"
- :workspace-path="projectPathForActiveIssue"
- :attr-workspace-path="groupPathForActiveIssue"
- :issuable-type="issuableType"
- class="gl-mt-5"
- data-testid="iteration-edit"
- />
- </template>
+ <iteration-sidebar-dropdown-widget
+ v-if="iterationFeatureAvailable && !isIncidentSidebar"
+ :iid="activeBoardItem.iid"
+ :workspace-path="projectPathForActiveIssue"
+ :attr-workspace-path="groupPathForActiveIssue"
+ :issuable-type="issuableType"
+ class="gl-mt-5"
+ data-testid="iteration-edit"
+ />
</div>
<board-sidebar-time-tracker />
<sidebar-date-widget
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
index 2599d1c80b8..45192b5304a 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -1,5 +1,5 @@
<script>
-import { pickBy, isEmpty } from 'lodash';
+import { pickBy, isEmpty, mapValues } from 'lodash';
import { mapActions } from 'vuex';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
@@ -251,22 +251,36 @@ export default {
);
}
- return {
- ...notParams,
- author_username: authorUsername,
- 'label_name[]': labelName,
- assignee_username: assigneeUsername,
- assignee_id: assigneeId,
- milestone_title: milestoneTitle,
- iteration_id: iterationId,
- search,
- types,
- weight,
- epic_id: isGid(epicId) ? getIdFromGraphQLId(epicId) : epicId,
- my_reaction_emoji: myReactionEmoji,
- release_tag: releaseTag,
- confidential,
- };
+ return mapValues(
+ {
+ ...notParams,
+ author_username: authorUsername,
+ 'label_name[]': labelName,
+ assignee_username: assigneeUsername,
+ assignee_id: assigneeId,
+ milestone_title: milestoneTitle,
+ iteration_id: iterationId,
+ search,
+ types,
+ weight,
+ epic_id: isGid(epicId) ? getIdFromGraphQLId(epicId) : epicId,
+ my_reaction_emoji: myReactionEmoji,
+ release_tag: releaseTag,
+ confidential,
+ },
+ (value) => {
+ if (value || value === false) {
+ // note: need to check array for labels.
+ if (Array.isArray(value)) {
+ return value.map((valueItem) => encodeURIComponent(valueItem));
+ }
+
+ return encodeURIComponent(value);
+ }
+
+ return value;
+ },
+ );
},
},
created() {
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 6ad57fd8985..cc048e2af1a 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -98,9 +98,6 @@ export default {
return this.$options.i18n[this.currentPage].btnText;
},
buttonKind() {
- if (this.isNewForm) {
- return 'success';
- }
if (this.isDeleteForm) {
return 'danger';
}
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index e4c3c3206a8..1024be61359 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -60,6 +60,9 @@ export default {
filters: this.filterParams,
};
},
+ skip() {
+ return this.isEpicBoard;
+ },
},
},
computed: {
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 84c9191975e..8db366e4995 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -25,7 +25,7 @@ export default {
},
computed: {
...mapState(['selectedProject', 'fullPath']),
- ...mapGetters(['isGroupBoard']),
+ ...mapGetters(['isGroupBoard', 'getBoardItemsByList']),
formEventPrefix() {
return toggleFormEventPrefix.issue;
},
@@ -42,6 +42,7 @@ export default {
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
const milestone = getMilestone(this.list);
+ const firstItemId = this.getBoardItemsByList(this.list.id)[0]?.id;
return this.addListNewIssue({
list: this.list,
@@ -51,6 +52,7 @@ export default {
assigneeIds: assignees?.map((a) => a?.id),
milestoneId: milestone?.id,
projectPath: this.projectPath,
+ moveAfterId: firstItemId,
},
}).then(() => {
this.cancel();
diff --git a/app/assets/javascripts/boards/components/board_new_item.vue b/app/assets/javascripts/boards/components/board_new_item.vue
index 44574de17d7..600917683cd 100644
--- a/app/assets/javascripts/boards/components/board_new_item.vue
+++ b/app/assets/javascripts/boards/components/board_new_item.vue
@@ -43,6 +43,12 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${this.list.id}-title`;
},
+ isIssueTitleEmpty() {
+ return this.title.trim() === '';
+ },
+ isCreatingIssueDisabled() {
+ return this.isIssueTitleEmpty || this.disableSubmit;
+ },
},
methods: {
handleFormCancel() {
@@ -54,7 +60,7 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.$emit('form-submit', {
- title,
+ title: title.trim(),
list,
});
},
@@ -69,7 +75,7 @@ export default {
<label :for="inputFieldId" class="gl-font-weight-bold">{{ __('Title') }}</label>
<gl-form-input
:id="inputFieldId"
- v-model.trim="title"
+ v-model="title"
:autofocus="true"
autocomplete="off"
type="text"
@@ -78,7 +84,8 @@ export default {
<slot></slot>
<div class="gl-clearfix gl-mt-4">
<gl-button
- :disabled="!title || disableSubmit"
+ data-testid="create-button"
+ :disabled="isCreatingIssueDisabled"
class="gl-float-left js-no-auto-disable"
variant="confirm"
type="submit"
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 6b7c08d05a5..24071c6f0b4 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlDrawer, GlLabel } from '@gitlab/ui';
+import { GlButton, GlDrawer, GlLabel, GlModal, GlModalDirective } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import { LIST, ListType, ListTypeTitles } from '~/boards/constants';
@@ -11,8 +11,14 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
listSettingsText: __('List settings'),
+ i18n: {
+ modalAction: __('Remove list'),
+ modalCopy: __('Are you sure you want to remove this list?'),
+ modalCancel: __('Cancel'),
+ },
components: {
GlButton,
+ GlModal,
GlDrawer,
GlLabel,
MountingPortal,
@@ -21,6 +27,9 @@ export default {
BoardSettingsListTypes: () =>
import('ee_component/boards/components/board_settings_list_types.vue'),
},
+ directives: {
+ GlModal: GlModalDirective,
+ },
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
inject: ['canAdminList', 'scopedLabelsAvailable'],
inheritAttrs: false,
@@ -29,6 +38,7 @@ export default {
ListType,
};
},
+ modalId: 'board-settings-sidebar-modal',
computed: {
...mapGetters(['isSidebarOpen', 'isEpicBoard']),
...mapState(['activeId', 'sidebarType', 'boardLists']),
@@ -59,16 +69,16 @@ export default {
},
methods: {
...mapActions(['unsetActiveId', 'removeList']),
+ handleModalPrimary() {
+ this.deleteBoard();
+ },
showScopedLabels(label) {
return this.scopedLabelsAvailable && isScopedLabel(label);
},
deleteBoard() {
- // eslint-disable-next-line no-alert
- if (window.confirm(__('Are you sure you want to remove this list?'))) {
- this.track('click_button', { label: 'remove_list' });
- this.removeList(this.activeId);
- this.unsetActiveId();
- }
+ this.track('click_button', { label: 'remove_list' });
+ this.removeList(this.activeId);
+ this.unsetActiveId();
},
},
};
@@ -92,11 +102,10 @@ export default {
<template #header>
<div v-if="canAdminList && activeList.id" class="gl-mt-3">
<gl-button
+ v-gl-modal="$options.modalId"
variant="danger"
category="secondary"
size="small"
- data-testid="remove-list"
- @click.stop="deleteBoard"
>{{ __('Remove list') }}
</gl-button>
</div>
@@ -122,5 +131,21 @@ export default {
/>
</template>
</gl-drawer>
+ <gl-modal
+ :modal-id="$options.modalId"
+ :title="$options.i18n.modalAction"
+ size="sm"
+ :action-primary="{
+ text: $options.i18n.modalAction,
+ attributes: [{ variant: 'danger' }],
+ }"
+ :action-secondary="{
+ text: $options.i18n.modalCancel,
+ attributes: [{ variant: 'default' }],
+ }"
+ @primary="handleModalPrimary"
+ >
+ <p>{{ $options.i18n.modalCopy }}</p>
+ </gl-modal>
</mounting-portal>
</template>
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 69343cd78d8..6dbb1ea0050 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -14,8 +14,6 @@ import { mapActions, 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';
-import httpStatusCodes from '~/lib/utils/http_status';
import { s__ } from '~/locale';
import eventHub from '../eventhub';
@@ -23,6 +21,8 @@ import groupBoardsQuery from '../graphql/group_boards.query.graphql';
import projectBoardsQuery from '../graphql/project_boards.query.graphql';
import groupBoardQuery from '../graphql/group_board.query.graphql';
import projectBoardQuery from '../graphql/project_board.query.graphql';
+import groupRecentBoardsQuery from '../graphql/group_recent_boards.query.graphql';
+import projectRecentBoardsQuery from '../graphql/project_recent_boards.query.graphql';
const MIN_BOARDS_TO_VIEW_RECENT = 10;
@@ -40,7 +40,7 @@ export default {
directives: {
GlModalDirective,
},
- inject: ['fullPath', 'recentBoardsEndpoint'],
+ inject: ['fullPath'],
props: {
throttleDuration: {
type: Number,
@@ -158,6 +158,10 @@ export default {
this.scrollFadeInitialized = false;
this.$nextTick(this.setScrollFade);
},
+ recentBoards() {
+ this.scrollFadeInitialized = false;
+ this.$nextTick(this.setScrollFade);
+ },
},
created() {
eventHub.$on('showBoardModal', this.showPage);
@@ -173,11 +177,11 @@ export default {
cancel() {
this.showPage('');
},
- boardUpdate(data) {
+ boardUpdate(data, boardType) {
if (!data?.[this.parentType]) {
return [];
}
- return data[this.parentType].boards.edges.map(({ node }) => ({
+ return data[this.parentType][boardType].edges.map(({ node }) => ({
id: getIdFromGraphQLId(node.id),
name: node.name,
}));
@@ -185,6 +189,9 @@ export default {
boardQuery() {
return this.isGroupBoard ? groupBoardsQuery : projectBoardsQuery;
},
+ recentBoardsQuery() {
+ return this.isGroupBoard ? groupRecentBoardsQuery : projectRecentBoardsQuery;
+ },
loadBoards(toggleDropdown = true) {
if (toggleDropdown && this.boards.length > 0) {
return;
@@ -196,39 +203,20 @@ export default {
},
query: this.boardQuery,
loadingKey: 'loadingBoards',
- update: this.boardUpdate,
+ update: (data) => this.boardUpdate(data, 'boards'),
});
this.loadRecentBoards();
},
loadRecentBoards() {
- this.loadingRecentBoards = true;
- // Follow up to fetch recent boards using GraphQL
- // https://gitlab.com/gitlab-org/gitlab/-/issues/300985
- axios
- .get(this.recentBoardsEndpoint)
- .then((res) => {
- this.recentBoards = res.data;
- })
- .catch((err) => {
- /**
- * If user is unauthorized we'd still want to resolve the
- * request to display all boards.
- */
- if (err?.response?.status === httpStatusCodes.UNAUTHORIZED) {
- this.recentBoards = []; // recent boards are empty
- return;
- }
- throw err;
- })
- .then(() => this.$nextTick()) // Wait for boards list in DOM
- .then(() => {
- this.setScrollFade();
- })
- .catch(() => {})
- .finally(() => {
- this.loadingRecentBoards = false;
- });
+ this.$apollo.addSmartQuery('recentBoards', {
+ variables() {
+ return { fullPath: this.fullPath };
+ },
+ query: this.recentBoardsQuery,
+ loadingKey: 'loadingRecentBoards',
+ update: (data) => this.boardUpdate(data, 'recentIssueBoards'),
+ });
},
isScrolledUp() {
const { content } = this.$refs;
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 7fc87f9f672..6bfdbb674a2 100644
--- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue
@@ -157,6 +157,7 @@ export default {
symbol: '%',
token: MilestoneToken,
unique: true,
+ shouldSkipSort: true,
fetchMilestones: this.fetchMilestones,
},
{