diff options
Diffstat (limited to 'app/assets/javascripts/boards/components')
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, }, { |