Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-09 12:08:40 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-09 12:08:40 +0300
commit73add99b1f4ce720f1fe00e828fb6991f27af6fb (patch)
tree450d3139cb74b6cea31142d10bd45787db1e2df5 /app/assets/javascripts/design_management
parent9f182a88ebe19371a3b7e38c92effb7526985171 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/design_management')
-rw-r--r--app/assets/javascripts/design_management/components/design_sidebar.vue14
-rw-r--r--app/assets/javascripts/design_management/components/design_todo_button.vue145
-rw-r--r--app/assets/javascripts/design_management/graphql.js36
-rw-r--r--app/assets/javascripts/design_management/graphql/mutations/create_design_todo.mutation.graphql15
-rw-r--r--app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql1
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue7
-rw-r--r--app/assets/javascripts/design_management/utils/cache_update.js33
-rw-r--r--app/assets/javascripts/design_management/utils/design_management_utils.js21
-rw-r--r--app/assets/javascripts/design_management/utils/error_messages.js8
9 files changed, 276 insertions, 4 deletions
diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue
index 29932bc4d26..9cfd2ea43a9 100644
--- a/app/assets/javascripts/design_management/components/design_sidebar.vue
+++ b/app/assets/javascripts/design_management/components/design_sidebar.vue
@@ -8,7 +8,7 @@ import { extractDiscussions, extractParticipants } from '../utils/design_managem
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
import DesignDiscussion from './design_notes/design_discussion.vue';
import Participants from '~/sidebar/components/participants/participants.vue';
-import TodoButton from '~/vue_shared/components/todo_button.vue';
+import DesignTodoButton from './design_todo_button.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
@@ -18,7 +18,7 @@ export default {
GlCollapse,
GlButton,
GlPopover,
- TodoButton,
+ DesignTodoButton,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -41,6 +41,14 @@ export default {
discussionWithOpenForm: '',
};
},
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ issueIid: {
+ default: '',
+ },
+ },
computed: {
discussions() {
return extractDiscussions(this.design.discussions);
@@ -119,7 +127,7 @@ export default {
class="gl-py-4 gl-mb-4 gl-display-flex gl-justify-content-space-between gl-align-items-center gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
<span>{{ __('To-Do') }}</span>
- <todo-button issuable-type="design" :issuable-id="design.iid" />
+ <design-todo-button :design="design" @error="$emit('todoError', $event)" />
</div>
<h2 class="gl-font-weight-bold gl-mt-0">
{{ issue.title }}
diff --git a/app/assets/javascripts/design_management/components/design_todo_button.vue b/app/assets/javascripts/design_management/components/design_todo_button.vue
new file mode 100644
index 00000000000..3e6d51fb90d
--- /dev/null
+++ b/app/assets/javascripts/design_management/components/design_todo_button.vue
@@ -0,0 +1,145 @@
+<script>
+import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
+import getDesignQuery from '../graphql/queries/get_design.query.graphql';
+import createDesignTodoMutation from '../graphql/mutations/create_design_todo.mutation.graphql';
+import TodoButton from '~/vue_shared/components/todo_button.vue';
+import allVersionsMixin from '../mixins/all_versions';
+import { updateStoreAfterDeleteDesignTodo } from '../utils/cache_update';
+import { findIssueId } from '../utils/design_management_utils';
+import { CREATE_DESIGN_TODO_ERROR, DELETE_DESIGN_TODO_ERROR } from '../utils/error_messages';
+
+export default {
+ components: {
+ TodoButton,
+ },
+ mixins: [allVersionsMixin],
+ props: {
+ design: {
+ type: Object,
+ required: true,
+ },
+ },
+ inject: {
+ projectPath: {
+ default: '',
+ },
+ issueIid: {
+ default: '',
+ },
+ },
+ data() {
+ return {
+ todoLoading: false,
+ };
+ },
+ computed: {
+ designVariables() {
+ return {
+ fullPath: this.projectPath,
+ iid: this.issueIid,
+ filenames: [this.$route.params.id],
+ atVersion: this.designsVersion,
+ };
+ },
+ designTodoVariables() {
+ return {
+ projectPath: this.projectPath,
+ issueId: findIssueId(this.design.issue.id),
+ issueIid: this.issueIid,
+ filenames: [this.$route.params.id],
+ atVersion: this.designsVersion,
+ };
+ },
+ pendingTodo() {
+ // TODO data structure pending BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555#note_405732940
+ return this.design.currentUserTodos?.nodes[0];
+ },
+ hasPendingTodo() {
+ return Boolean(this.pendingTodo);
+ },
+ },
+ methods: {
+ createTodo() {
+ this.todoLoading = true;
+ return this.$apollo
+ .mutate({
+ mutation: createDesignTodoMutation,
+ variables: this.designTodoVariables,
+ update: (store, { data: { createDesignTodo } }) => {
+ // because this is a @client mutation,
+ // we control what is in errors, and therefore
+ // we are certain that there is at most 1 item in the array
+ const createDesignTodoError = (createDesignTodo.errors || [])[0];
+ if (createDesignTodoError) {
+ this.$emit('error', Error(createDesignTodoError.message));
+ }
+ },
+ })
+ .catch(err => {
+ this.$emit('error', Error(CREATE_DESIGN_TODO_ERROR));
+ throw err;
+ })
+ .finally(() => {
+ this.todoLoading = false;
+ });
+ },
+ deleteTodo() {
+ if (!this.hasPendingTodo) return Promise.reject();
+
+ const { id } = this.pendingTodo;
+ const { designVariables } = this;
+
+ this.todoLoading = true;
+ return this.$apollo
+ .mutate({
+ mutation: todoMarkDoneMutation,
+ variables: {
+ id,
+ },
+ update(
+ store,
+ {
+ data: { todoMarkDone },
+ },
+ ) {
+ const todoMarkDoneFirstError = (todoMarkDone.errors || [])[0];
+ if (todoMarkDoneFirstError) {
+ this.$emit('error', Error(todoMarkDoneFirstError));
+ } else {
+ updateStoreAfterDeleteDesignTodo(
+ store,
+ todoMarkDone,
+ getDesignQuery,
+ designVariables,
+ );
+ }
+ },
+ })
+ .catch(err => {
+ this.$emit('error', Error(DELETE_DESIGN_TODO_ERROR));
+ throw err;
+ })
+ .finally(() => {
+ this.todoLoading = false;
+ });
+ },
+ toggleTodo() {
+ if (this.hasPendingTodo) {
+ return this.deleteTodo();
+ }
+
+ return this.createTodo();
+ },
+ },
+};
+</script>
+
+<template>
+ <todo-button
+ issuable-type="design"
+ :issuable-id="design.iid"
+ :is-todo="hasPendingTodo"
+ :loading="todoLoading"
+ @click.stop.prevent="toggleTodo"
+ />
+</template>
diff --git a/app/assets/javascripts/design_management/graphql.js b/app/assets/javascripts/design_management/graphql.js
index 1dcb8deb249..0a17fef4cad 100644
--- a/app/assets/javascripts/design_management/graphql.js
+++ b/app/assets/javascripts/design_management/graphql.js
@@ -3,9 +3,14 @@ import VueApollo from 'vue-apollo';
import { uniqueId } from 'lodash';
import produce from 'immer';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
+import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql';
import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql';
+import getDesignQuery from './graphql/queries/get_design.query.graphql';
import typeDefs from './graphql/typedefs.graphql';
+import { extractTodoIdFromDeletePath, createPendingTodo } from './utils/design_management_utils';
+import { CREATE_DESIGN_TODO_EXISTS_ERROR } from './utils/error_messages';
+import { addPendingTodoToStore } from './utils/cache_update';
Vue.use(VueApollo);
@@ -25,6 +30,37 @@ const resolvers = {
cache.writeQuery({ query: activeDiscussionQuery, data });
},
+ createDesignTodo: (_, { projectPath, issueId, issueIid, filenames, atVersion }, { cache }) => {
+ return axios
+ .post(`/${projectPath}/todos`, {
+ issue_id: issueId,
+ issuable_id: issueIid,
+ issuable_type: 'design',
+ })
+ .then(({ data }) => {
+ const { delete_path } = data;
+ const todoId = extractTodoIdFromDeletePath(delete_path);
+ if (!todoId) {
+ return {
+ errors: [
+ {
+ message: CREATE_DESIGN_TODO_EXISTS_ERROR,
+ },
+ ],
+ };
+ }
+
+ const pendingTodo = createPendingTodo(todoId);
+ addPendingTodoToStore(cache, pendingTodo, getDesignQuery, {
+ fullPath: projectPath,
+ iid: issueIid,
+ filenames,
+ atVersion,
+ });
+
+ return pendingTodo;
+ });
+ },
},
};
diff --git a/app/assets/javascripts/design_management/graphql/mutations/create_design_todo.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/create_design_todo.mutation.graphql
new file mode 100644
index 00000000000..2fb0b28b1e0
--- /dev/null
+++ b/app/assets/javascripts/design_management/graphql/mutations/create_design_todo.mutation.graphql
@@ -0,0 +1,15 @@
+mutation createDesignTodo(
+ $projectPath: String!
+ $issueId: String!
+ $issueIid: String!
+ $filenames: [String]!
+ $atVersion: String
+) {
+ createDesignTodo(
+ projectPath: $projectPath
+ issueId: $issueId
+ issueIid: $issueIid
+ filenames: $filenames
+ atVersion: $atVersion
+ ) @client
+}
diff --git a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
index ab987dda525..96869a404b1 100644
--- a/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
+++ b/app/assets/javascripts/design_management/graphql/queries/get_design.query.graphql
@@ -10,6 +10,7 @@ query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [Stri
nodes {
...DesignItem
issue {
+ id
title
webPath
webUrl
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index 93fb9f37b72..8a9911f55a3 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -33,6 +33,7 @@ import {
DESIGN_NOT_FOUND_ERROR,
DESIGN_VERSION_NOT_EXIST_ERROR,
UPDATE_NOTE_ERROR,
+ TOGGLE_TODO_ERROR,
designDeletionError,
} from '../../utils/error_messages';
import { trackDesignDetailView } from '../../utils/tracking';
@@ -226,7 +227,7 @@ export default {
},
onError(message, e) {
this.errorMessage = message;
- throw e;
+ if (e) throw e;
},
onCreateImageDiffNoteError(e) {
this.onError(ADD_IMAGE_DIFF_NOTE_ERROR, e);
@@ -246,6 +247,9 @@ export default {
onResolveDiscussionError(e) {
this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
},
+ onTodoError(e) {
+ this.onError(e?.message || TOGGLE_TODO_ERROR, e);
+ },
openCommentForm(annotationCoordinates) {
this.annotationCoordinates = annotationCoordinates;
if (this.$refs.newDiscussionForm) {
@@ -349,6 +353,7 @@ export default {
@updateNoteError="onUpdateNoteError"
@resolveDiscussionError="onResolveDiscussionError"
@toggleResolvedComments="toggleResolvedComments"
+ @todoError="onTodoError"
>
<template #replyForm>
<apollo-mutation
diff --git a/app/assets/javascripts/design_management/utils/cache_update.js b/app/assets/javascripts/design_management/utils/cache_update.js
index dce33298efb..2ffbae8afe0 100644
--- a/app/assets/javascripts/design_management/utils/cache_update.js
+++ b/app/assets/javascripts/design_management/utils/cache_update.js
@@ -7,6 +7,7 @@ import { extractCurrentDiscussion, extractDesign, extractDesigns } from './desig
import {
ADD_IMAGE_DIFF_NOTE_ERROR,
UPDATE_IMAGE_DIFF_NOTE_ERROR,
+ DELETE_DESIGN_TODO_ERROR,
designDeletionError,
} from './error_messages';
@@ -188,6 +189,30 @@ const moveDesignInStore = (store, designManagementMove, query) => {
});
};
+export const addPendingTodoToStore = (store, pendingTodo, query, queryVariables) => {
+ const data = store.readQuery({
+ query,
+ variables: queryVariables,
+ });
+
+ // TODO produce new version of data that includes the new pendingTodo.
+ // This is only possible after BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555
+
+ store.writeQuery({ query, variables: queryVariables, data });
+};
+
+export const deletePendingTodoFromStore = (store, pendingTodo, query, queryVariables) => {
+ const data = store.readQuery({
+ query,
+ variables: queryVariables,
+ });
+
+ // TODO produce new version of data without the pendingTodo.
+ // This is only possible after BE MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40555
+
+ store.writeQuery({ query, variables: queryVariables, data });
+};
+
const onError = (data, message) => {
createFlash(message);
throw new Error(data.errors);
@@ -243,3 +268,11 @@ export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
moveDesignInStore(store, data, query);
}
};
+
+export const updateStoreAfterDeleteDesignTodo = (store, data, query, queryVariables) => {
+ if (hasErrors(data)) {
+ onError(data, DELETE_DESIGN_TODO_ERROR);
+ } else {
+ deletePendingTodoFromStore(store, data, query, queryVariables);
+ }
+};
diff --git a/app/assets/javascripts/design_management/utils/design_management_utils.js b/app/assets/javascripts/design_management/utils/design_management_utils.js
index a5514597fcf..2a6c8b249a5 100644
--- a/app/assets/javascripts/design_management/utils/design_management_utils.js
+++ b/app/assets/javascripts/design_management/utils/design_management_utils.js
@@ -30,6 +30,8 @@ export const findVersionId = id => (id.match('::Version/(.+$)') || [])[1];
export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
+export const findIssueId = id => (id.match('Issue/(.+$)') || [])[1];
+
export const extractDesigns = data => data.project.issue.designCollection.designs.nodes;
export const extractDesign = data => (extractDesigns(data) || [])[0];
@@ -146,3 +148,22 @@ const normalizeAuthor = author => ({
export const extractParticipants = users => users.map(node => normalizeAuthor(node));
export const getPageLayoutElement = () => document.querySelector('.layout-page');
+
+/**
+ * Extract the ID of the To-Do for a given 'delete' path
+ * Example of todoDeletePath: /delete/1234
+ * @param {String} todoDeletePath delete_path from REST API response
+ */
+export const extractTodoIdFromDeletePath = todoDeletePath =>
+ (todoDeletePath.match('todos/([0-9]+$)') || [])[1];
+
+const createTodoGid = todoId => {
+ return `gid://gitlab/Todo/${todoId}`;
+};
+
+export const createPendingTodo = todoId => {
+ return {
+ __typename: 'Todo', // eslint-disable-line @gitlab/require-i18n-strings
+ id: createTodoGid(todoId),
+ };
+};
diff --git a/app/assets/javascripts/design_management/utils/error_messages.js b/app/assets/javascripts/design_management/utils/error_messages.js
index c815b11737d..bd21d711462 100644
--- a/app/assets/javascripts/design_management/utils/error_messages.js
+++ b/app/assets/javascripts/design_management/utils/error_messages.js
@@ -44,6 +44,14 @@ export const MOVE_DESIGN_ERROR = __(
'Something went wrong when reordering designs. Please try again',
);
+export const CREATE_DESIGN_TODO_ERROR = __('Failed to create To-Do for the design.');
+
+export const CREATE_DESIGN_TODO_EXISTS_ERROR = __('There is already a To-Do for this design.');
+
+export const DELETE_DESIGN_TODO_ERROR = __('Failed to remove To-Do for the design.');
+
+export const TOGGLE_TODO_ERROR = __('Failed to toggle To-Do for the design.');
+
const MAX_SKIPPED_FILES_LISTINGS = 5;
const oneDesignSkippedMessage = filename =>