diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-21 18:10:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-21 18:10:23 +0300 |
commit | 75196424b189d70aff3d88a95117adb33c2b1263 (patch) | |
tree | ed55cbd4e0974bf7062ad0d794eaec32dfe5dfdb /app/assets/javascripts/design_management | |
parent | eac99f198f2834788c38108bcee3a2c567fba9e0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/design_management')
8 files changed, 156 insertions, 21 deletions
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue index 3091c6703b4..7083c5cd0b7 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue @@ -1,16 +1,20 @@ <script> import { GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui'; import { ApolloMutation } from 'vue-apollo'; +import * as Sentry from '@sentry/browser'; import { createAlert } from '~/flash'; -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import { updateGlobalTodoCount } from '~/sidebar/utils'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue'; import { isLoggedIn } from '~/lib/utils/common_utils'; -import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants'; +import { TYPENAME_NOTE, TYPENAME_DISCUSSION } from '~/graphql_shared/constants'; +import { ACTIVE_DISCUSSION_SOURCE_TYPES, DELETE_NOTE_ERROR_MSG } from '../../constants'; import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql'; import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql'; +import destroyNoteMutation from '../../graphql/mutations/destroy_note.mutation.graphql'; import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql'; import getDesignQuery from '../../graphql/queries/get_design.query.graphql'; import allVersionsMixin from '../../mixins/all_versions'; @@ -23,6 +27,13 @@ import DesignReplyForm from './design_reply_form.vue'; import ToggleRepliesWidget from './toggle_replies_widget.vue'; export default { + i18n: { + deleteNote: { + confirmationText: __('Are you sure you want to delete this comment?'), + primaryModalBtnText: __('Delete comment'), + errorText: DELETE_NOTE_ERROR_MSG, + }, + }, components: { ApolloMutation, DesignNote, @@ -100,6 +111,7 @@ export default { discussionComment: '', isFormRendered: false, activeDiscussion: {}, + noteToDelete: null, isResolving: false, shouldChangeResolvedStatus: false, areRepliesCollapsed: this.discussion.resolved, @@ -219,13 +231,65 @@ export default { const { source } = activeDiscussion; return ALLOWED_ACTIVE_DISCUSSION_SOURCES.includes(source) && this.isDiscussionActive; }, + async showDeleteNoteConfirmationModal(note) { + const isLast = note?.discussion?.notes?.nodes.length === 1; + this.noteToDelete = { ...note, isLast }; + + const confirmed = await confirmAction(this.$options.i18n.deleteNote.confirmationText, { + primaryBtnVariant: 'danger', + primaryBtnText: this.$options.i18n.deleteNote.primaryModalBtnText, + }); + + if (confirmed) { + await this.deleteNote(); + } + }, + async deleteNote() { + const { id, discussion, isLast } = this.noteToDelete; + try { + await this.$apollo.mutate({ + mutation: destroyNoteMutation, + variables: { + input: { + id, + }, + }, + update: (cache, { data }) => { + const { errors } = data.destroyNote; + + if (errors?.length) { + this.$emit('delete-note-error', errors[0]); + } + + const objectToIdentify = isLast + ? { __typename: TYPENAME_DISCUSSION, id: discussion?.id } + : { __typename: TYPENAME_NOTE, id }; + + cache.modify({ + id: cache.identify(objectToIdentify), + fields: (_, { DELETE }) => DELETE, + }); + }, + optimisticResponse: { + destroyNote: { + note: null, + errors: [], + __typename: 'DestroyNotePayload', + }, + }, + }); + } catch (error) { + this.$emit('delete-note-error', this.$options.i18n.deleteNote.errorText); + Sentry.captureException(error); + } + }, }, createNoteMutation, }; </script> <template> - <div class="design-discussion-wrapper"> + <div class="design-discussion-wrapper" @click="$emit('update-active-discussion')"> <design-note-pin :is-resolved="discussion.resolved" :label="discussion.index" /> <ul class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none" @@ -237,6 +301,7 @@ export default { :is-resolving="isResolving" :noteable-id="noteableId" :class="{ 'gl-bg-blue-50': isDiscussionActive }" + @delete-note="showDeleteNoteConfirmationModal($event)" @error="$emit('update-note-error', $event)" > <template v-if="isLoggedIn && discussion.resolvable" #resolve-discussion> @@ -280,6 +345,7 @@ export default { :is-resolving="isResolving" :noteable-id="noteableId" :class="{ 'gl-bg-blue-50': isDiscussionActive }" + @delete-note="showDeleteNoteConfirmationModal($event)" @error="$emit('update-note-error', $event)" /> <li diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue index af4bf7eb14d..c1207ad527e 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue @@ -1,5 +1,13 @@ <script> -import { GlAvatar, GlAvatarLink, GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui'; +import { + GlAvatar, + GlAvatarLink, + GlButton, + GlDropdown, + GlDropdownItem, + GlLink, + GlTooltipDirective, +} from '@gitlab/ui'; import { ApolloMutation } from 'vue-apollo'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -14,6 +22,8 @@ import DesignReplyForm from './design_reply_form.vue'; export default { i18n: { editCommentLabel: __('Edit comment'), + moreActionsLabel: __('More actions'), + deleteCommentText: __('Delete comment'), }, components: { ApolloMutation, @@ -21,6 +31,8 @@ export default { GlAvatar, GlAvatarLink, GlButton, + GlDropdown, + GlDropdownItem, GlLink, TimeAgoTooltip, TimelineEntryItem, @@ -48,6 +60,7 @@ export default { return { noteText: this.note.body, isEditing: false, + isError: true, }; }, computed: { @@ -70,7 +83,13 @@ export default { }; }, isEditButtonVisible() { - return !this.isEditing && this.note.userPermissions.adminNote; + return !this.isEditing && this.adminPermissions; + }, + isMoreActionsButtonVisible() { + return !this.isEditing && this.adminPermissions; + }, + adminPermissions() { + return this.note.userPermissions.adminNote; }, }, methods: { @@ -132,6 +151,30 @@ export default { size="small" @click="isEditing = true" /> + <gl-dropdown + v-if="isMoreActionsButtonVisible" + v-gl-tooltip.hover + class="gl-display-none gl-sm-display-inline-flex! gl-ml-3" + icon="ellipsis_v" + category="tertiary" + data-qa-selector="design_discussion_actions_ellipsis_dropdown" + data-testid="more-actions-dropdown" + :text="$options.i18n.moreActionsLabel" + text-sr-only + :title="$options.i18n.moreActionsLabel" + :aria-label="$options.i18n.moreActionsLabel" + no-caret + left + > + <gl-dropdown-item + variant="danger" + data-qa-selector="delete_design_note_button" + data-testid="delete-note-button" + @click="$emit('delete-note', note)" + > + {{ $options.i18n.deleteCommentText }} + </gl-dropdown-item> + </gl-dropdown> </div> </div> <template v-if="!isEditing"> diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue index 24cc93f5eaf..c34d5cea0c2 100644 --- a/app/assets/javascripts/design_management/components/design_sidebar.vue +++ b/app/assets/javascripts/design_management/components/design_sidebar.vue @@ -57,7 +57,6 @@ export default { }, data() { return { - isResolvedDiscussionsExpanded: this.resolvedDiscussionsExpanded, discussionWithOpenForm: '', isLoggedIn: isLoggedIn(), }; @@ -87,13 +86,13 @@ export default { unresolvedDiscussions() { return this.discussions.filter((discussion) => !discussion.resolved); }, - }, - watch: { - resolvedDiscussionsExpanded(resolvedDiscussionsExpanded) { - this.isResolvedDiscussionsExpanded = resolvedDiscussionsExpanded; - }, - isResolvedDiscussionsExpanded() { - this.$emit('toggleResolvedComments'); + isResolvedDiscussionsExpanded: { + get() { + return this.resolvedDiscussionsExpanded; + }, + set(isExpanded) { + this.$emit('toggleResolvedComments', isExpanded); + }, }, }, mounted() { @@ -129,7 +128,7 @@ export default { </script> <template> - <div class="image-notes gl-pt-0" @click="handleSidebarClick"> + <div class="image-notes gl-pt-0" @click.self="handleSidebarClick"> <div 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" > @@ -179,8 +178,9 @@ export default { data-testid="unresolved-discussion" @create-note-error="$emit('onDesignDiscussionError', $event)" @update-note-error="$emit('updateNoteError', $event)" + @delete-note-error="$emit('deleteNoteError', $event)" @resolve-discussion-error="$emit('resolveDiscussionError', $event)" - @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" + @update-active-discussion="updateActiveDiscussion(discussion.notes[0].id)" @open-form="updateDiscussionWithOpenForm" /> <gl-accordion v-if="hasResolvedDiscussions" :header-level="3" class="gl-mb-5"> @@ -202,9 +202,10 @@ export default { :discussion-with-open-form="discussionWithOpenForm" data-testid="resolved-discussion" @error="$emit('onDesignDiscussionError', $event)" - @updateNoteError="$emit('updateNoteError', $event)" + @update-note-error="$emit('updateNoteError', $event)" + @delete-note-error="$emit('deleteNoteError', $event)" @open-form="updateDiscussionWithOpenForm" - @click.native.stop="updateActiveDiscussion(discussion.notes[0].id)" + @update-active-discussion="updateActiveDiscussion(discussion.notes[0].id)" /> </gl-accordion-item> </gl-accordion> diff --git a/app/assets/javascripts/design_management/components/toolbar/index.vue b/app/assets/javascripts/design_management/components/toolbar/index.vue index 6d571365306..cd76b6c1885 100644 --- a/app/assets/javascripts/design_management/components/toolbar/index.vue +++ b/app/assets/javascripts/design_management/components/toolbar/index.vue @@ -60,7 +60,8 @@ export default { }, image: { type: String, - required: true, + required: false, + default: '', }, isLoading: { type: Boolean, diff --git a/app/assets/javascripts/design_management/constants.js b/app/assets/javascripts/design_management/constants.js index afe621ac3c5..6720245b5f1 100644 --- a/app/assets/javascripts/design_management/constants.js +++ b/app/assets/javascripts/design_management/constants.js @@ -1,3 +1,4 @@ +import { __ } from '~/locale'; // WARNING: replace this with something // more sensical as per https://gitlab.com/gitlab-org/gitlab/issues/118611 export const VALID_DESIGN_FILE_MIMETYPE = { @@ -14,3 +15,7 @@ export const ACTIVE_DISCUSSION_SOURCE_TYPES = { export const DESIGN_DETAIL_LAYOUT_CLASSLIST = ['design-detail-layout', 'overflow-hidden', 'm-0']; export const MAXIMUM_FILE_UPLOAD_LIMIT = 10; + +export const DELETE_NOTE_ERROR_MSG = __( + 'Something went wrong when deleting a comment. Please try again.', +); diff --git a/app/assets/javascripts/design_management/graphql/mutations/destroy_note.mutation.graphql b/app/assets/javascripts/design_management/graphql/mutations/destroy_note.mutation.graphql new file mode 100644 index 00000000000..58fb05e2140 --- /dev/null +++ b/app/assets/javascripts/design_management/graphql/mutations/destroy_note.mutation.graphql @@ -0,0 +1,8 @@ +mutation destroyNote($input: DestroyNoteInput!) { + destroyNote(input: $input) { + errors + note { + id + } + } +} diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index f448e2f9e3d..3a1c8ae43c5 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -41,6 +41,7 @@ import { DESIGN_VERSION_NOT_EXIST_ERROR, UPDATE_NOTE_ERROR, TOGGLE_TODO_ERROR, + DELETE_NOTE_ERROR, designDeletionError, } from '../../utils/error_messages'; import { trackDesignDetailView, servicePingDesignDetailView } from '../../utils/tracking'; @@ -263,6 +264,9 @@ export default { onUpdateNoteError(e) { this.onError(UPDATE_NOTE_ERROR, e); }, + onDeleteNoteError(e) { + this.onError(DELETE_NOTE_ERROR, e); + }, onDesignDiscussionError(e) { this.onError(ADD_DISCUSSION_COMMENT_ERROR, e); }, @@ -324,8 +328,8 @@ export default { const diffNoteGid = noteId ? toDiffNoteGid(noteId) : undefined; return this.updateActiveDiscussion(diffNoteGid, ACTIVE_DISCUSSION_SOURCE_TYPES.url); }, - toggleResolvedComments() { - this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded; + toggleResolvedComments(newValue) { + this.resolvedDiscussionsExpanded = newValue; }, setMaxScale(event) { this.maxScale = 1 / event; @@ -397,6 +401,7 @@ export default { @onDesignDiscussionError="onDesignDiscussionError" @onCreateImageDiffNoteError="onCreateImageDiffNoteError" @updateNoteError="onUpdateNoteError" + @deleteNoteError="onDeleteNoteError" @resolveDiscussionError="onResolveDiscussionError" @toggleResolvedComments="toggleResolvedComments" @todoError="onTodoError" diff --git a/app/assets/javascripts/design_management/utils/error_messages.js b/app/assets/javascripts/design_management/utils/error_messages.js index 42f752efc9e..1ed054abe22 100644 --- a/app/assets/javascripts/design_management/utils/error_messages.js +++ b/app/assets/javascripts/design_management/utils/error_messages.js @@ -13,7 +13,13 @@ export const UPDATE_IMAGE_DIFF_NOTE_ERROR = s__( 'DesignManagement|Could not update discussion. Please try again.', ); -export const UPDATE_NOTE_ERROR = s__('DesignManagement|Could not update note. Please try again.'); +export const UPDATE_NOTE_ERROR = s__( + 'DesignManagement|Could not update comment. Please try again.', +); + +export const DELETE_NOTE_ERROR = s__( + 'DesignManagement|Could not delete comment. Please try again.', +); export const UPLOAD_DESIGN_ERROR = s__( 'DesignManagement|Error uploading a new design. Please try again.', |