From 0ea3fcec397b69815975647f5e2aa5fe944a8486 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Jun 2022 11:10:13 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-1-stable-ee --- .../work_items/components/item_state.vue | 18 +- .../work_items/components/item_title.vue | 9 +- .../work_items/components/update_work_item.js | 23 ++ .../work_items/components/work_item_assignees.vue | 111 ++++++++++ .../components/work_item_description.vue | 234 +++++++++++++++++++++ .../work_items/components/work_item_detail.vue | 53 ++++- .../components/work_item_detail_modal.vue | 17 +- .../work_items/components/work_item_links/index.js | 37 ++++ .../components/work_item_links/work_item_links.vue | 165 +++++++++++++++ .../work_item_links/work_item_links_form.vue | 28 +++ .../work_items/components/work_item_state.vue | 46 ++-- .../work_items/components/work_item_title.vue | 50 +++-- .../work_items/components/work_item_weight.vue | 26 +++ app/assets/javascripts/work_items/constants.js | 18 ++ .../local_update_work_item.mutation.graphql | 9 + .../javascripts/work_items/graphql/provider.js | 84 +++++++- .../work_items/graphql/typedefs.graphql | 36 ++++ .../graphql/update_work_item_task.mutation.graphql | 8 + .../update_work_item_widgets.mutation.graphql | 10 + .../work_items/graphql/work_item.fragment.graphql | 7 + .../work_items/graphql/work_item.query.graphql | 16 ++ .../graphql/work_item_links.query.graphql | 28 +++ app/assets/javascripts/work_items/index.js | 4 +- .../work_items/pages/work_item_root.vue | 4 + 24 files changed, 992 insertions(+), 49 deletions(-) create mode 100644 app/assets/javascripts/work_items/components/update_work_item.js create mode 100644 app/assets/javascripts/work_items/components/work_item_assignees.vue create mode 100644 app/assets/javascripts/work_items/components/work_item_description.vue create mode 100644 app/assets/javascripts/work_items/components/work_item_links/index.js create mode 100644 app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue create mode 100644 app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue create mode 100644 app/assets/javascripts/work_items/components/work_item_weight.vue create mode 100644 app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql create mode 100644 app/assets/javascripts/work_items/graphql/typedefs.graphql create mode 100644 app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql create mode 100644 app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql create mode 100644 app/assets/javascripts/work_items/graphql/work_item_links.query.graphql (limited to 'app/assets/javascripts/work_items') diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue index 0b6c1a75bb2..69670d3471c 100644 --- a/app/assets/javascripts/work_items/components/item_state.vue +++ b/app/assets/javascripts/work_items/components/item_state.vue @@ -49,14 +49,28 @@ export default { + + diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue index 232510b108d..ce2fa158596 100644 --- a/app/assets/javascripts/work_items/components/item_title.vue +++ b/app/assets/javascripts/work_items/components/item_title.vue @@ -40,18 +40,18 @@ export default { diff --git a/app/assets/javascripts/work_items/components/update_work_item.js b/app/assets/javascripts/work_items/components/update_work_item.js new file mode 100644 index 00000000000..fc395fa5be3 --- /dev/null +++ b/app/assets/javascripts/work_items/components/update_work_item.js @@ -0,0 +1,23 @@ +import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; +import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql'; + +export function getUpdateWorkItemMutation({ input, workItemParentId }) { + let mutation = updateWorkItemMutation; + + const variables = { + input, + }; + + if (workItemParentId) { + mutation = updateWorkItemTaskMutation; + variables.input = { + id: workItemParentId, + taskData: input, + }; + } + + return { + mutation, + variables, + }; +} diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue new file mode 100644 index 00000000000..4d1c171772e --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -0,0 +1,111 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue new file mode 100644 index 00000000000..5a85fcdd7ac --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -0,0 +1,234 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 4222ffe42fe..5272df2d53f 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -1,27 +1,45 @@ @@ -83,27 +113,40 @@ export default { diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue index 172a40a6e56..d1c8022ac57 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue @@ -37,7 +37,7 @@ export default { default: null, }, }, - emits: ['workItemDeleted', 'workItemUpdated', 'close'], + emits: ['workItemDeleted', 'close'], data() { return { error: undefined, @@ -98,15 +98,24 @@ export default { @@ -114,7 +123,7 @@ export default { diff --git a/app/assets/javascripts/work_items/components/work_item_links/index.js b/app/assets/javascripts/work_items/components/work_item_links/index.js new file mode 100644 index 00000000000..320a4a213e3 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_links/index.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import WorkItemLinks from './work_item_links.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default function initWorkItemLinks() { + if (!window.gon.features.workItemsHierarchy) { + return; + } + + const workItemLinksRoot = document.querySelector('.js-work-item-links-root'); + + if (!workItemLinksRoot) { + return; + } + // eslint-disable-next-line no-new + new Vue({ + el: workItemLinksRoot, + name: 'WorkItemLinksRoot', + apolloProvider, + components: { + workItemLinks: WorkItemLinks, + }, + render: (createElement) => + createElement('work-item-links', { + props: { + issuableId: parseInt(workItemLinksRoot.dataset.issuableId, 10), + }, + }), + }); +} diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue new file mode 100644 index 00000000000..bdfff100333 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue @@ -0,0 +1,165 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue new file mode 100644 index 00000000000..22728f58026 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue @@ -0,0 +1,28 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_state.vue b/app/assets/javascripts/work_items/components/work_item_state.vue index 51db4c804eb..87f4a8822b1 100644 --- a/app/assets/javascripts/work_items/components/work_item_state.vue +++ b/app/assets/javascripts/work_items/components/work_item_state.vue @@ -7,8 +7,9 @@ import { STATE_CLOSED, STATE_EVENT_CLOSE, STATE_EVENT_REOPEN, + TRACKING_CATEGORY_SHOW, } from '../constants'; -import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; +import { getUpdateWorkItemMutation } from './update_work_item'; import ItemState from './item_state.vue'; export default { @@ -21,6 +22,11 @@ export default { type: Object, required: true, }, + workItemParentId: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -33,14 +39,14 @@ export default { }, tracking() { return { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'item_state', property: `type_${this.workItemType}`, }; }, }, methods: { - async updateWorkItemState(newState) { + updateWorkItemState(newState) { const stateEventMap = { [STATE_OPEN]: STATE_EVENT_REOPEN, [STATE_CLOSED]: STATE_EVENT_CLOSE, @@ -48,35 +54,39 @@ export default { const stateEvent = stateEventMap[newState]; - await this.updateWorkItem(stateEvent); + this.updateWorkItem(stateEvent); }, + async updateWorkItem(updatedState) { if (!updatedState) { return; } + const input = { + id: this.workItem.id, + stateEvent: updatedState, + }; + this.updateInProgress = true; try { this.track('updated_state'); - const { - data: { workItemUpdate }, - } = await this.$apollo.mutate({ - mutation: updateWorkItemMutation, - variables: { - input: { - id: this.workItem.id, - stateEvent: updatedState, - }, - }, + const { mutation, variables } = getUpdateWorkItemMutation({ + workItemParentId: this.workItemParentId, + input, }); - if (workItemUpdate?.errors?.length) { - throw new Error(workItemUpdate.errors[0]); - } + const { data } = await this.$apollo.mutate({ + mutation, + variables, + }); - this.$emit('updated'); + const errors = data.workItemUpdate?.errors; + + if (errors?.length) { + throw new Error(errors[0]); + } } catch (error) { this.$emit('error', i18n.updateError); Sentry.captureException(error); diff --git a/app/assets/javascripts/work_items/components/work_item_title.vue b/app/assets/javascripts/work_items/components/work_item_title.vue index d2e6d3c0bbf..b4c13037038 100644 --- a/app/assets/javascripts/work_items/components/work_item_title.vue +++ b/app/assets/javascripts/work_items/components/work_item_title.vue @@ -1,7 +1,8 @@ + + diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index e914500108f..2df4978a319 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -6,9 +6,27 @@ export const STATE_CLOSED = 'CLOSED'; export const STATE_EVENT_REOPEN = 'REOPEN'; export const STATE_EVENT_CLOSE = 'CLOSE'; +export const TRACKING_CATEGORY_SHOW = 'workItems:show'; + export const i18n = { fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'), updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'), }; export const DEFAULT_MODAL_TYPE = 'Task'; + +export const WIDGET_TYPE_ASSIGNEE = 'ASSIGNEES'; +export const WIDGET_TYPE_DESCRIPTION = 'DESCRIPTION'; +export const WIDGET_TYPE_WEIGHT = 'WEIGHT'; +export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY'; + +export const WIDGET_TYPE_TASK_ICON = 'task-done'; + +export const WIDGET_ICONS = { + TASK: 'task-done', +}; + +export const WORK_ITEM_STATUS_TEXT = { + CLOSED: s__('WorkItem|Closed'), + OPEN: s__('WorkItem|Open'), +}; diff --git a/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql new file mode 100644 index 00000000000..0d31ecef6f8 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/local_update_work_item.mutation.graphql @@ -0,0 +1,9 @@ +#import "./work_item.fragment.graphql" + +mutation localUpdateWorkItem($input: LocalWorkItemAssigneesInput) { + localUpdateWorkItem(input: $input) @client { + workItem { + ...WorkItem + } + } +} diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js index 3c2955ce1e2..09d929faae2 100644 --- a/app/assets/javascripts/work_items/graphql/provider.js +++ b/app/assets/javascripts/work_items/graphql/provider.js @@ -1,11 +1,93 @@ +import produce from 'immer'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { WIDGET_TYPE_ASSIGNEE } from '../constants'; +import typeDefs from './typedefs.graphql'; +import workItemQuery from './work_item.query.graphql'; + +export const temporaryConfig = { + typeDefs, + cacheConfig: { + possibleTypes: { + LocalWorkItemWidget: ['LocalWorkItemAssignees'], + }, + typePolicies: { + WorkItem: { + fields: { + mockWidgets: { + read(widgets) { + return ( + widgets || [ + { + __typename: 'LocalWorkItemAssignees', + type: 'ASSIGNEES', + nodes: [ + { + __typename: 'UserCore', + id: 'gid://gitlab/User/1', + avatarUrl: '', + webUrl: '', + // eslint-disable-next-line @gitlab/require-i18n-strings + name: 'John Doe', + username: 'doe_I', + }, + { + __typename: 'UserCore', + id: 'gid://gitlab/User/2', + avatarUrl: '', + webUrl: '', + // eslint-disable-next-line @gitlab/require-i18n-strings + name: 'Marcus Rutherford', + username: 'ruthfull', + }, + ], + }, + { + __typename: 'LocalWorkItemWeight', + type: 'WEIGHT', + weight: 0, + }, + ] + ); + }, + }, + }, + }, + }, + }, +}; + +export const resolvers = { + Mutation: { + localUpdateWorkItem(_, { input }, { cache }) { + const sourceData = cache.readQuery({ + query: workItemQuery, + variables: { id: input.id }, + }); + + const data = produce(sourceData, (draftData) => { + const assigneesWidget = draftData.workItem.mockWidgets.find( + (widget) => widget.type === WIDGET_TYPE_ASSIGNEE, + ); + assigneesWidget.nodes = assigneesWidget.nodes.filter((assignee) => + input.assigneeIds.includes(assignee.id), + ); + }); + + cache.writeQuery({ + query: workItemQuery, + variables: { id: input.id }, + data, + }); + }, + }, +}; export function createApolloProvider() { Vue.use(VueApollo); - const defaultClient = createDefaultClient(); + const defaultClient = createDefaultClient(resolvers, temporaryConfig); return new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql new file mode 100644 index 00000000000..bfe2f0fe0ce --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql @@ -0,0 +1,36 @@ +enum LocalWidgetType { + ASSIGNEES + WEIGHT +} + +interface LocalWorkItemWidget { + type: LocalWidgetType! +} + +type LocalWorkItemAssignees implements LocalWorkItemWidget { + type: LocalWidgetType! + nodes: [UserCore] +} + +type LocalWorkItemWeight implements LocalWorkItemWidget { + type: LocalWidgetType! + weight: Int +} + +extend type WorkItem { + mockWidgets: [LocalWorkItemWidget] +} + +type LocalWorkItemAssigneesInput { + id: WorkItemID! + assigneeIds: [ID!] +} + +type LocalWorkItemPayload { + workItem: WorkItem! + errors: [String!] +} + +extend type Mutation { + localUpdateWorkItem(input: LocalWorkItemAssigneesInput!): LocalWorkItemPayload +} diff --git a/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql new file mode 100644 index 00000000000..470de060ee3 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/update_work_item_task.mutation.graphql @@ -0,0 +1,8 @@ +mutation workItemUpdateTask($input: WorkItemUpdateTaskInput!) { + workItemUpdate: workItemUpdateTask(input: $input) { + workItem { + id + descriptionHtml + } + } +} diff --git a/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql new file mode 100644 index 00000000000..148b340b439 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/update_work_item_widgets.mutation.graphql @@ -0,0 +1,10 @@ +#import "./work_item.fragment.graphql" + +mutation workItemUpdateWidgets($input: WorkItemUpdateWidgetsInput!) { + workItemUpdateWidgets(input: $input) { + workItem { + ...WorkItem + } + errors + } +} diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql index e25fd102699..04701f6899e 100644 --- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql @@ -11,4 +11,11 @@ fragment WorkItem on WorkItem { deleteWorkItem updateWorkItem } + widgets { + ... on WorkItemWidgetDescription { + type + description + descriptionHtml + } + } } diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql index 3b46fed97ec..30bc61f5c59 100644 --- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql @@ -3,5 +3,21 @@ query workItem($id: WorkItemID!) { workItem(id: $id) { ...WorkItem + mockWidgets @client { + ... on LocalWorkItemAssignees { + type + nodes { + id + avatarUrl + name + username + webUrl + } + } + ... on LocalWorkItemWeight { + type + weight + } + } } } diff --git a/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql new file mode 100644 index 00000000000..c2496f53cc8 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/work_item_links.query.graphql @@ -0,0 +1,28 @@ +query workItemQuery($id: WorkItemID!) { + workItem(id: $id) { + id + workItemType { + id + } + title + widgets { + type + ... on WorkItemWidgetHierarchy { + type + parent { + id + } + children { + nodes { + id + workItemType { + id + } + title + state + } + } + } + } + } +} diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js index e39b0d6a353..33e28831b54 100644 --- a/app/assets/javascripts/work_items/index.js +++ b/app/assets/javascripts/work_items/index.js @@ -1,11 +1,12 @@ import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import App from './components/app.vue'; import { createRouter } from './router'; import { createApolloProvider } from './graphql/provider'; export const initWorkItemsRoot = () => { const el = document.querySelector('#js-work-items'); - const { fullPath, issuesListPath } = el.dataset; + const { fullPath, hasIssueWeightsFeature, issuesListPath } = el.dataset; return new Vue({ el, @@ -13,6 +14,7 @@ export const initWorkItemsRoot = () => { apolloProvider: createApolloProvider(), provide: { fullPath, + hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), issuesListPath, }, render(createElement) { diff --git a/app/assets/javascripts/work_items/pages/work_item_root.vue b/app/assets/javascripts/work_items/pages/work_item_root.vue index 6dc3dc3b3c9..e9840889bdb 100644 --- a/app/assets/javascripts/work_items/pages/work_item_root.vue +++ b/app/assets/javascripts/work_items/pages/work_item_root.vue @@ -4,6 +4,7 @@ import { TYPE_WORK_ITEM } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { visitUrl } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; +import ZenMode from '~/zen_mode'; import WorkItemDetail from '../components/work_item_detail.vue'; import deleteWorkItemMutation from '../graphql/delete_work_item.mutation.graphql'; @@ -29,6 +30,9 @@ export default { return convertToGraphQLId(TYPE_WORK_ITEM, this.id); }, }, + mounted() { + this.ZenMode = new ZenMode(); + }, methods: { deleteWorkItem() { this.$apollo -- cgit v1.2.3