diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/work_items | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/work_items')
12 files changed, 324 insertions, 35 deletions
diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue new file mode 100644 index 00000000000..5e9e50a94f0 --- /dev/null +++ b/app/assets/javascripts/work_items/components/item_title.vue @@ -0,0 +1,71 @@ +<script> +import { escape } from 'lodash'; +import { __ } from '~/locale'; + +export default { + props: { + initialTitle: { + type: String, + required: false, + default: '', + }, + placeholder: { + type: String, + required: false, + default: __('Add a title...'), + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + title: this.initialTitle, + }; + }, + methods: { + getSanitizedTitle(inputEl) { + const { innerText } = inputEl; + return escape(innerText); + }, + handleBlur({ target }) { + this.$emit('title-changed', this.getSanitizedTitle(target)); + }, + handleInput({ target }) { + this.$emit('title-input', this.getSanitizedTitle(target)); + }, + handleSubmit() { + this.$refs.titleEl.blur(); + }, + }, +}; +</script> + +<template> + <h2 + class="gl-font-weight-normal gl-sm-font-weight-bold gl-my-5 gl-display-inline-block" + :class="{ 'gl-cursor-not-allowed': disabled }" + data-testid="title" + aria-labelledby="item-title" + > + <span + id="item-title" + ref="titleEl" + role="textbox" + :aria-label="__('Title')" + :data-placeholder="placeholder" + :contenteditable="!disabled" + class="gl-pseudo-placeholder" + @blur="handleBlur" + @keyup="handleInput" + @keydown.enter.exact="handleSubmit" + @keydown.ctrl.u.prevent + @keydown.meta.u.prevent + @keydown.ctrl.b.prevent + @keydown.meta.b.prevent + >{{ title }}</span + > + </h2> +</template> diff --git a/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql new file mode 100644 index 00000000000..2f302dae7d7 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/create_work_item.mutation.graphql @@ -0,0 +1,18 @@ +#import './widget.fragment.graphql' + +mutation createWorkItem($input: LocalCreateWorkItemInput) { + localCreateWorkItem(input: $input) @client { + workItem { + id + type + widgets { + nodes { + ...WidgetBase + ... on LocalTitleWidget { + contentText + } + } + } + } + } +} diff --git a/app/assets/javascripts/work_items/graphql/fragmentTypes.json b/app/assets/javascripts/work_items/graphql/fragmentTypes.json index c048ac34ac0..3b837e84ee9 100644 --- a/app/assets/javascripts/work_items/graphql/fragmentTypes.json +++ b/app/assets/javascripts/work_items/graphql/fragmentTypes.json @@ -1 +1 @@ -{"__schema":{"types":[{"kind":"INTERFACE","name":"WorkItemWidget","possibleTypes":[{"name":"TitleWidget"}]}]}} +{"__schema":{"types":[{"kind":"INTERFACE","name":"LocalWorkItemWidget","possibleTypes":[{"name":"LocalTitleWidget"}]}]}} diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js index 083735336ce..fb536a425c0 100644 --- a/app/assets/javascripts/work_items/graphql/provider.js +++ b/app/assets/javascripts/work_items/graphql/provider.js @@ -4,6 +4,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; import workItemQuery from './work_item.query.graphql'; import introspectionQueryResultData from './fragmentTypes.json'; +import { resolvers } from './resolvers'; import typeDefs from './typedefs.graphql'; const fragmentMatcher = new IntrospectionFragmentMatcher({ @@ -13,15 +14,12 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({ export function createApolloProvider() { Vue.use(VueApollo); - const defaultClient = createDefaultClient( - {}, - { - cacheConfig: { - fragmentMatcher, - }, - typeDefs, + const defaultClient = createDefaultClient(resolvers, { + cacheConfig: { + fragmentMatcher, }, - ); + typeDefs, + }); defaultClient.cache.writeQuery({ query: workItemQuery, @@ -30,14 +28,14 @@ export function createApolloProvider() { }, data: { workItem: { - __typename: 'WorkItem', + __typename: 'LocalWorkItem', id: '1', type: 'FEATURE', widgets: { - __typename: 'WorkItemWidgetConnection', + __typename: 'LocalWorkItemWidgetConnection', nodes: [ { - __typename: 'TitleWidget', + __typename: 'LocalTitleWidget', type: 'TITLE', enabled: true, // eslint-disable-next-line @gitlab/require-i18n-strings diff --git a/app/assets/javascripts/work_items/graphql/resolvers.js b/app/assets/javascripts/work_items/graphql/resolvers.js index e69de29bb2d..63d5234d083 100644 --- a/app/assets/javascripts/work_items/graphql/resolvers.js +++ b/app/assets/javascripts/work_items/graphql/resolvers.js @@ -0,0 +1,58 @@ +import { uuids } from '~/lib/utils/uuids'; +import workItemQuery from './work_item.query.graphql'; + +export const resolvers = { + Mutation: { + localCreateWorkItem(_, { input }, { cache }) { + const id = uuids()[0]; + const workItem = { + __typename: 'LocalWorkItem', + type: 'FEATURE', + id, + widgets: { + __typename: 'LocalWorkItemWidgetConnection', + nodes: [ + { + __typename: 'LocalTitleWidget', + type: 'TITLE', + enabled: true, + contentText: input.title, + }, + ], + }, + }; + + cache.writeQuery({ query: workItemQuery, variables: { id }, data: { workItem } }); + + return { + __typename: 'LocalCreateWorkItemPayload', + workItem, + }; + }, + + localUpdateWorkItem(_, { input }, { cache }) { + const workItemTitle = { + __typename: 'LocalTitleWidget', + type: 'TITLE', + enabled: true, + contentText: input.title, + }; + const workItem = { + __typename: 'LocalWorkItem', + type: 'FEATURE', + id: input.id, + widgets: { + __typename: 'LocalWorkItemWidgetConnection', + nodes: [workItemTitle], + }, + }; + + cache.writeQuery({ query: workItemQuery, variables: { id: input.id }, data: { workItem } }); + + return { + __typename: 'LocalUpdateWorkItemPayload', + workItem, + }; + }, + }, +}; diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql index 4a6e4aeed60..177eea00322 100644 --- a/app/assets/javascripts/work_items/graphql/typedefs.graphql +++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql @@ -1,38 +1,60 @@ -enum WorkItemType { +enum LocalWorkItemType { FEATURE } -enum WidgetType { +enum LocalWidgetType { TITLE } -interface WorkItemWidget { - type: WidgetType! +interface LocalWorkItemWidget { + type: LocalWidgetType! } # Replicating Relay connection type for client schema -type WorkItemWidgetEdge { +type LocalWorkItemWidgetEdge { cursor: String! - node: WorkItemWidget + node: LocalWorkItemWidget } -type WorkItemWidgetConnection { - edges: [WorkItemWidgetEdge] - nodes: [WorkItemWidget] +type LocalWorkItemWidgetConnection { + edges: [LocalWorkItemWidgetEdge] + nodes: [LocalWorkItemWidget] pageInfo: PageInfo! } -type TitleWidget implements WorkItemWidget { - type: WidgetType! +type LocalTitleWidget implements LocalWorkItemWidget { + type: LocalWidgetType! contentText: String! } -type WorkItem { +type LocalWorkItem { id: ID! - type: WorkItemType! - widgets: [WorkItemWidgetConnection] + type: LocalWorkItemType! + widgets: [LocalWorkItemWidgetConnection] +} + +input LocalCreateWorkItemInput { + title: String! +} + +input LocalUpdateWorkItemInput { + id: ID! + title: String +} + +type LocalCreateWorkItemPayload { + workItem: LocalWorkItem! +} + +type LocalUpdateWorkItemPayload { + workItem: LocalWorkItem! } extend type Query { - workItem(id: ID!): WorkItem! + workItem(id: ID!): LocalWorkItem! +} + +extend type Mutation { + localCreateWorkItem(input: LocalCreateWorkItemInput!): LocalCreateWorkItemPayload! + localUpdateWorkItem(input: LocalUpdateWorkItemInput!): LocalUpdateWorkItemPayload! } diff --git a/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql b/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql new file mode 100644 index 00000000000..f0563f099b2 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/update_work_item.mutation.graphql @@ -0,0 +1,18 @@ +#import './widget.fragment.graphql' + +mutation updateWorkItem($input: LocalUpdateWorkItemInput) { + localUpdateWorkItem(input: $input) @client { + workItem { + id + type + widgets { + nodes { + ...WidgetBase + ... on LocalTitleWidget { + contentText + } + } + } + } + } +} diff --git a/app/assets/javascripts/work_items/graphql/widget.fragment.graphql b/app/assets/javascripts/work_items/graphql/widget.fragment.graphql index d7608c26052..154367dc0d8 100644 --- a/app/assets/javascripts/work_items/graphql/widget.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/widget.fragment.graphql @@ -1,3 +1,3 @@ -fragment WidgetBase on WorkItemWidget { +fragment WidgetBase on LocalWorkItemWidget { type } 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 549e4f8c65a..9f173f7c302 100644 --- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql @@ -7,7 +7,7 @@ query WorkItem($id: ID!) { widgets { nodes { ...WidgetBase - ... on TitleWidget { + ... on LocalTitleWidget { contentText } } diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue new file mode 100644 index 00000000000..12bad5606d4 --- /dev/null +++ b/app/assets/javascripts/work_items/pages/create_work_item.vue @@ -0,0 +1,71 @@ +<script> +import { GlButton, GlAlert } from '@gitlab/ui'; +import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql'; + +import ItemTitle from '../components/item_title.vue'; + +export default { + components: { + GlButton, + GlAlert, + ItemTitle, + }, + data() { + return { + title: '', + error: false, + }; + }, + methods: { + async createWorkItem() { + try { + const response = await this.$apollo.mutate({ + mutation: createWorkItemMutation, + variables: { + input: { + title: this.title, + }, + }, + }); + + const { + data: { + localCreateWorkItem: { + workItem: { id }, + }, + }, + } = response; + this.$router.push({ name: 'workItem', params: { id } }); + } catch { + this.error = true; + } + }, + handleTitleInput(title) { + this.title = title; + }, + }, +}; +</script> + +<template> + <form @submit.prevent="createWorkItem"> + <gl-alert v-if="error" variant="danger" @dismiss="error = false">{{ + __('Something went wrong when creating a work item. Please try again') + }}</gl-alert> + <item-title data-testid="title-input" @title-input="handleTitleInput" /> + <div class="gl-bg-gray-10 gl-py-5 gl-px-6"> + <gl-button + variant="confirm" + :disabled="title.length === 0" + class="gl-mr-3" + data-testid="create-button" + type="submit" + > + {{ __('Create') }} + </gl-button> + <gl-button type="button" data-testid="cancel-button" @click="$router.go(-1)"> + {{ __('Cancel') }} + </gl-button> + </div> + </form> +</template> 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 493ee0aba01..479274baf3a 100644 --- a/app/assets/javascripts/work_items/pages/work_item_root.vue +++ b/app/assets/javascripts/work_items/pages/work_item_root.vue @@ -1,8 +1,16 @@ <script> +import { GlAlert } from '@gitlab/ui'; import workItemQuery from '../graphql/work_item.query.graphql'; +import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; import { widgetTypes } from '../constants'; +import ItemTitle from '../components/item_title.vue'; + export default { + components: { + ItemTitle, + GlAlert, + }, props: { id: { type: String, @@ -12,6 +20,7 @@ export default { data() { return { workItem: null, + error: false, }; }, apollo: { @@ -29,20 +38,39 @@ export default { return this.workItem?.widgets?.nodes?.find((widget) => widget.type === widgetTypes.title); }, }, + methods: { + async updateWorkItem(title) { + try { + await this.$apollo.mutate({ + mutation: updateWorkItemMutation, + variables: { + input: { + id: this.id, + title, + }, + }, + }); + } catch { + this.error = true; + } + }, + }, }; </script> <template> <section> + <gl-alert v-if="error" variant="danger" @dismiss="error = false">{{ + __('Something went wrong while updating work item. Please try again') + }}</gl-alert> <!-- Title widget placeholder --> <div> - <h2 + <item-title v-if="titleWidgetData" - class="gl-font-weight-normal gl-sm-font-weight-bold gl-my-5" + :initial-title="titleWidgetData.contentText" data-testid="title" - > - {{ titleWidgetData.contentText }} - </h2> + @title-changed="updateWorkItem" + /> </div> </section> </template> diff --git a/app/assets/javascripts/work_items/router/routes.js b/app/assets/javascripts/work_items/router/routes.js index a3cf44ad4ca..95772bbd026 100644 --- a/app/assets/javascripts/work_items/router/routes.js +++ b/app/assets/javascripts/work_items/router/routes.js @@ -1,7 +1,12 @@ export const routes = [ { + path: '/new', + name: 'createWorkItem', + component: () => import('../pages/create_work_item.vue'), + }, + { path: '/:id', - name: 'work_item', + name: 'workItem', component: () => import('../pages/work_item_root.vue'), props: true, }, |