diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index f7693dd7102b8ef551b368d9e5de9dd2d1d0777d..9372618fb2a62c2b6509dced423a77dbd885db9b 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -6,8 +6,12 @@ import { GlDisclosureDropdownGroup, GlFilteredSearchToken, GlTooltipDirective, + GlDrawer, + GlLink, } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; + +import produce from 'immer'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import { isEmpty } from 'lodash'; import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_statistics.vue'; @@ -63,6 +67,9 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; +import WorkItemDetail from '~/work_items/components/work_item_detail.vue'; +import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql'; +import { WORK_ITEM_TYPE_ENUM_OBJECTIVE } from '~/work_items/constants'; import { CREATED_DESC, defaultTypeTokenOptions, @@ -98,6 +105,7 @@ import { getSortKey, getSortOptions, isSortKey, + mapWorkItemWidgetsToIssueFields, } from '../utils'; import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin'; import EmptyStateWithAnyIssues from './empty_state_with_any_issues.vue'; @@ -131,12 +139,15 @@ export default { EmptyStateWithoutAnyIssues, GlButton, GlButtonGroup, + GlDrawer, IssuableByEmail, IssuableList, IssueCardStatistics, IssueCardTimeInfo, NewResourceDropdown, LocalStorageSync, + WorkItemDetail, + GlLink, }, directives: { GlTooltip: GlTooltipDirective, @@ -218,6 +229,7 @@ export default { }, ], }, + activeIssuable: null, }; }, apollo: { @@ -230,6 +242,7 @@ export default { return data[this.namespace]?.issues.nodes ?? []; }, fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + nextFetchPolicy: fetchPolicies.CACHE_FIRST, // We need this for handling loading state when using frontend cache // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106004#note_1217325202 for details notifyOnNetworkStatusChange: true, @@ -536,6 +549,12 @@ export default { isGridView() { return this.viewType === ISSUES_GRID_VIEW_KEY; }, + isIssuableSelected() { + return !isEmpty(this.activeIssuable); + }, + issuesDrawerEnabled() { + return this.glFeatures?.issuesListDrawer; + }, }, watch: { $route(newValue, oldValue) { @@ -805,12 +824,96 @@ export default { // The default view is list view this.viewType = ISSUES_LIST_VIEW_KEY; }, + handleSelectIssuable(issuable) { + this.activeIssuable = issuable; + }, + updateIssuablesCache(workItem) { + const client = this.$apollo.provider.clients.defaultClient; + const issuesList = client.readQuery({ + query: getIssuesQuery, + variables: this.queryVariables, + }); + + const activeIssuable = issuesList.project.issues.nodes.find( + (issue) => issue.iid === workItem.iid, + ); + + // when we change issuable state, it's moved to a different tab + // to ensure that we show 20 items of the first page, we need to refetch issuables + if (!activeIssuable.state.includes(workItem.state.toLowerCase())) { + this.refetchIssuables(); + return; + } + + // handle all other widgets + const data = mapWorkItemWidgetsToIssueFields(issuesList, workItem); + + client.writeQuery({ query: getIssuesQuery, variables: this.queryVariables, data }); + }, + promoteToObjective(workItemIid) { + const { cache } = this.$apollo.provider.clients.defaultClient; + + cache.updateQuery({ query: getIssuesQuery, variables: this.queryVariables }, (issuesList) => + produce(issuesList, (draftData) => { + const activeItem = draftData.project.issues.nodes.find( + (issue) => issue.iid === workItemIid, + ); + + activeItem.type = WORK_ITEM_TYPE_ENUM_OBJECTIVE; + }), + ); + }, + refetchIssuables() { + this.$apollo.queries.issues.refetch(); + this.$apollo.queries.issuesCounts.refetch(); + }, + deleteIssuable({ workItemId }) { + this.$apollo + .mutate({ + mutation: deleteWorkItemMutation, + variables: { input: { id: workItemId } }, + }) + .then(({ data }) => { + if (data.workItemDelete.errors?.length) { + throw new Error(data.workItemDelete.errors[0]); + } + this.activeIssuable = null; + this.refetchIssuables(); + }) + .catch((error) => { + this.issuesError = this.$options.i18n.deleteError; + Sentry.captureException(error); + }); + }, }, };