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:
Diffstat (limited to 'app/assets/javascripts/notes')
-rw-r--r--app/assets/javascripts/notes/components/comment_field_layout.vue20
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue14
-rw-r--r--app/assets/javascripts/notes/components/comment_type_dropdown.vue97
-rw-r--r--app/assets/javascripts/notes/components/diff_discussion_header.vue8
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue90
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes.vue1
-rw-r--r--app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue2
-rw-r--r--app/assets/javascripts/notes/components/mr_discussion_filter.vue11
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue29
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue34
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue19
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue34
-rw-r--r--app/assets/javascripts/notes/components/toggle_replies_widget.vue2
-rw-r--r--app/assets/javascripts/notes/mixins/diff_line_note_form.js34
-rw-r--r--app/assets/javascripts/notes/utils.js14
16 files changed, 246 insertions, 164 deletions
diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue
index bde7d219e9f..cefcc1b0c98 100644
--- a/app/assets/javascripts/notes/components/comment_field_layout.vue
+++ b/app/assets/javascripts/notes/components/comment_field_layout.vue
@@ -66,9 +66,7 @@ export default {
};
</script>
<template>
- <div
- class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-lg gl-border-gray-100 gl-bg-white gl-overflow-hidden"
- >
+ <div class="comment-warning-wrapper">
<div
v-if="withAlertContainer"
class="error-alert"
@@ -76,7 +74,7 @@ export default {
></div>
<noteable-warning
v-if="hasWarning"
- class="gl-py-4 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100 gl-rounded-base gl-rounded-bottom-left-none gl-rounded-bottom-right-none"
+ class="gl-pt-4 gl-pb-5 gl-mb-n3 gl-rounded-lg gl-rounded-bottom-left-none gl-rounded-bottom-right-none"
:is-locked="isLocked"
:is-confidential="isConfidential"
:noteable-type="noteableType"
@@ -84,10 +82,20 @@ export default {
:confidential-noteable-docs-path="noteableData.confidential_issues_docs_path"
/>
<slot></slot>
- <attachments-warning v-if="showAttachmentWarning" />
+ <attachments-warning
+ v-if="showAttachmentWarning"
+ :class="{
+ 'gl-py-3': !showEmailParticipantsWarning,
+ 'gl-pt-4 gl-pb-3 gl-mt-n3': showEmailParticipantsWarning,
+ }"
+ />
<email-participants-warning
v-if="showEmailParticipantsWarning"
- class="gl-border-t-1 gl-border-t-solid gl-border-t-gray-100 gl-rounded-base gl-rounded-top-left-none! gl-rounded-top-right-none!"
+ class="gl-border-t-1 gl-rounded-lg gl-rounded-top-left-none! gl-rounded-top-right-none!"
+ :class="{
+ 'gl-pt-4 gl-pb-3 gl-mt-n3': !showAttachmentWarning,
+ 'gl-py-3 gl-mt-1': showAttachmentWarning,
+ }"
:emails="emailParticipants"
/>
</div>
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index cba0f960c00..c6d94a3b7b7 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -16,11 +16,12 @@ import { sprintf } from '~/locale';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
import * as constants from '../constants';
import eventHub from '../event_hub';
import { COMMENT_FORM } from '../i18n';
-import { getErrorMessages } from '../utils';
+import { createNoteErrorMessages } from '../utils';
import issuableStateMixin from '../mixins/issuable_state';
import CommentFieldLayout from './comment_field_layout.vue';
@@ -146,9 +147,6 @@ export default {
markdownDocsPath() {
return this.getNotesData.markdownDocsPath;
},
- quickActionsDocsPath() {
- return this.getNotesData.quickActionsDocsPath;
- },
markdownPreviewPath() {
return this.getNoteableData.preview_note_path;
},
@@ -219,7 +217,7 @@ export default {
'toggleIssueLocalState',
]),
handleSaveError({ data, status }) {
- this.errors = getErrorMessages(data, status);
+ this.errors = createNoteErrorMessages(data, status);
},
handleSaveDraft() {
this.handleSave({ isDraft: true });
@@ -258,6 +256,11 @@ export default {
this.isSubmitting = true;
+ trackSavedUsingEditor(
+ this.$refs.markdownEditor.isContentEditorActive,
+ `${this.noteableType}_${this.noteType}`,
+ );
+
this.saveNote(noteData)
.then(() => {
this.restartPolling();
@@ -366,7 +369,6 @@ export default {
:render-markdown-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:add-spacing-classes="false"
- :quick-actions-docs-path="quickActionsDocsPath"
:form-field-props="formFieldProps"
:autosave-key="autosaveKey"
:disabled="isSubmitting"
diff --git a/app/assets/javascripts/notes/components/comment_type_dropdown.vue b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
index 543be838920..2e4f925194f 100644
--- a/app/assets/javascripts/notes/components/comment_type_dropdown.vue
+++ b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
@@ -1,16 +1,20 @@
<script>
-import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
+import { GlButtonGroup, GlButton, GlCollapsibleListbox } from '@gitlab/ui';
-import { sprintf } from '~/locale';
+import { sprintf, __ } from '~/locale';
import { COMMENT_FORM } from '~/notes/i18n';
import * as constants from '../constants';
export default {
- i18n: COMMENT_FORM,
+ name: 'CommentTypeDropdown',
+ i18n: {
+ ...COMMENT_FORM,
+ toggleSrText: __('Comment type'),
+ },
components: {
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
+ GlButtonGroup,
+ GlButton,
+ GlCollapsibleListbox,
},
model: {
prop: 'noteType',
@@ -93,56 +97,63 @@ export default {
noteableDisplayName: this.noteableDisplayName,
});
},
+ dropdownItems() {
+ return [
+ {
+ text: this.dropdownCommentButtonTitle,
+ description: this.commentDescription,
+ value: constants.COMMENT,
+ },
+ {
+ text: this.dropdownStartThreadButtonTitle,
+ description: this.startDiscussionDescription,
+ value: constants.DISCUSSION,
+ qaSelector: 'discussion_menu_item',
+ },
+ ];
+ },
},
methods: {
handleClick() {
this.$emit('click');
},
- setNoteTypeToComment() {
- if (this.noteType !== constants.COMMENT) {
- this.$emit('change', constants.COMMENT);
- }
- },
- setNoteTypeToDiscussion() {
- if (this.noteType !== constants.DISCUSSION) {
- this.$emit('change', constants.DISCUSSION);
- }
+ setNoteType(value) {
+ this.$emit('change', value);
},
},
};
</script>
<template>
- <gl-dropdown
- split
- :text="commentButtonTitle"
- class="gl-mr-3 js-comment-button js-comment-submit-button comment-type-dropdown"
- category="primary"
- variant="confirm"
- :disabled="disabled"
- data-testid="comment-button"
- data-qa-selector="comment_button"
+ <!--TODO: Replace button-group workaround once `split` option for new dropdowns is implemented.-->
+ <!-- See issue at https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2263-->
+ <gl-button-group
+ class="js-comment-button js-comment-submit-button comment-type-dropdown gl-w-full gl-mb-3 gl-md-w-auto gl-md-mb-0"
:data-track-label="trackingLabel"
data-track-action="click_button"
- @click="$emit('click')"
+ data-testid="comment-button"
+ data-qa-selector="comment_button"
>
- <gl-dropdown-item
- is-check-item
- :is-checked="isNoteTypeComment"
- @click.stop.prevent="setNoteTypeToComment"
- >
- <strong>{{ dropdownCommentButtonTitle }}</strong>
- <p class="gl-m-0">{{ commentDescription }}</p>
- </gl-dropdown-item>
- <gl-dropdown-divider />
- <gl-dropdown-item
- is-check-item
- :is-checked="isNoteTypeDiscussion"
- data-qa-selector="discussion_menu_item"
- @click.stop.prevent="setNoteTypeToDiscussion"
+ <gl-button variant="confirm" :disabled="disabled" @click="handleClick">
+ {{ commentButtonTitle }}
+ </gl-button>
+ <gl-collapsible-listbox
+ class="split"
+ toggle-class="gl-rounded-top-left-none! gl-rounded-bottom-left-none! gl-pl-1!"
+ variant="confirm"
+ text-sr-only
+ :toggle-text="$options.i18n.toggleSrText"
+ :disabled="disabled"
+ :items="dropdownItems"
+ :selected="noteType"
+ @select="setNoteType"
>
- <strong>{{ dropdownStartThreadButtonTitle }}</strong>
- <p class="gl-m-0">{{ startDiscussionDescription }}</p>
- </gl-dropdown-item>
- </gl-dropdown>
+ <template #list-item="{ item }">
+ <div :data-qa-selector="item.qaSelector">
+ <strong>{{ item.text }}</strong>
+ <p class="gl-m-0">{{ item.description }}</p>
+ </div>
+ </template>
+ </gl-collapsible-listbox>
+ </gl-button-group>
</template>
diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue
index c53d3203327..e7b7ba7743e 100644
--- a/app/assets/javascripts/notes/components/diff_discussion_header.vue
+++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue
@@ -107,7 +107,13 @@ export default {
<template>
<div class="discussion-header gl-display-flex gl-align-items-center">
<div v-once class="timeline-avatar gl-align-self-start gl-flex-shrink-0 gl-flex-shrink">
- <gl-avatar-link v-if="author" :href="author.path">
+ <gl-avatar-link
+ v-if="author"
+ :href="author.path"
+ :data-user-id="author.id"
+ :data-username="author.username"
+ class="js-user-link"
+ >
<gl-avatar :src="author.avatar_url" :alt="author.name" :size="32" />
</gl-avatar-link>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index ba5ffc60917..cff1043c258 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,13 +1,6 @@
<script>
-import {
- GlTooltipDirective,
- GlButton,
- GlButtonGroup,
- GlDropdown,
- GlDropdownItem,
- GlIcon,
-} from '@gitlab/ui';
-import { mapGetters, mapActions } from 'vuex';
+import { GlButton, GlButtonGroup, GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui';
+import { mapActions, mapGetters } from 'vuex';
import { throttle } from 'lodash';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -18,11 +11,9 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
+ GlDisclosureDropdown,
GlButton,
GlButtonGroup,
- GlDropdown,
- GlDropdownItem,
- GlIcon,
},
mixins: [glFeatureFlagsMixin(), discussionNavigation],
props: {
@@ -56,6 +47,29 @@ export default {
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
+ threadOptions() {
+ const options = [
+ {
+ text: this.toggleThreadsLabel,
+ action: this.handleExpandDiscussions,
+ extraAttrs: {
+ 'data-testid': 'toggle-all-discussions-btn',
+ },
+ },
+ ];
+
+ if (this.resolveAllDiscussionsIssuePath && !this.allResolved) {
+ options.push({
+ text: __('Resolve all with new issue'),
+ href: this.resolveAllDiscussionsIssuePath,
+ extraAttrs: {
+ 'data-testid': 'resolve-all-with-issue-link',
+ },
+ });
+ }
+
+ return options;
+ },
},
methods: {
...mapActions(['setExpandDiscussions']),
@@ -86,32 +100,25 @@ export default {
>
<template v-if="allResolved">
{{ __('All threads resolved!') }}
- <gl-dropdown
- v-gl-tooltip:discussionCounter.hover.bottom
+ <gl-disclosure-dropdown
+ v-gl-tooltip:discussionCounter.hover.top
+ icon="ellipsis_v"
size="small"
category="tertiary"
- right
+ placement="right"
+ no-caret
:title="__('Thread options')"
:aria-label="__('Thread options')"
toggle-class="btn-icon"
class="gl-pt-0! gl-px-2 gl-h-full gl-ml-2"
- >
- <template #button-content>
- <gl-icon name="ellipsis_v" class="mr-0" />
- </template>
- <gl-dropdown-item
- data-testid="toggle-all-discussions-btn"
- @click="handleExpandDiscussions"
- >
- {{ toggleThreadsLabel }}
- </gl-dropdown-item>
- </gl-dropdown>
+ :items="threadOptions"
+ />
</template>
<template v-else>
{{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
<gl-button-group class="gl-ml-3">
<gl-button
- v-gl-tooltip:discussionCounter.hover.bottom
+ v-gl-tooltip:discussionCounter.hover.top
:title="__('Go to previous unresolved thread')"
:aria-label="__('Go to previous unresolved thread')"
class="discussion-previous-btn gl-rounded-base! gl-px-2!"
@@ -123,7 +130,7 @@ export default {
@click="jumpPrevious"
/>
<gl-button
- v-gl-tooltip:discussionCounter.hover.bottom
+ v-gl-tooltip:discussionCounter.hover.top
:title="__('Go to next unresolved thread')"
:aria-label="__('Go to next unresolved thread')"
class="discussion-next-btn gl-rounded-base! gl-px-2!"
@@ -134,32 +141,19 @@ export default {
category="tertiary"
@click="jumpNext"
/>
- <gl-dropdown
- v-gl-tooltip:discussionCounter.hover.bottom
+ <gl-disclosure-dropdown
+ v-gl-tooltip:discussionCounter.hover.top
+ icon="ellipsis_v"
size="small"
category="tertiary"
- right
+ placement="right"
+ no-caret
:title="__('Thread options')"
:aria-label="__('Thread options')"
toggle-class="btn-icon"
class="gl-pt-0! gl-px-2"
- >
- <template #button-content>
- <gl-icon name="ellipsis_v" class="mr-0" />
- </template>
- <gl-dropdown-item
- data-testid="toggle-all-discussions-btn"
- @click="handleExpandDiscussions"
- >
- {{ toggleThreadsLabel }}
- </gl-dropdown-item>
- <gl-dropdown-item
- v-if="resolveAllDiscussionsIssuePath && !allResolved"
- :href="resolveAllDiscussionsIssuePath"
- >
- {{ __('Resolve all with new issue') }}
- </gl-dropdown-item>
- </gl-dropdown>
+ :items="threadOptions"
+ />
</gl-button-group>
</template>
</div>
diff --git a/app/assets/javascripts/notes/components/discussion_notes.vue b/app/assets/javascripts/notes/components/discussion_notes.vue
index 9fb027fb955..080787884c8 100644
--- a/app/assets/javascripts/notes/components/discussion_notes.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes.vue
@@ -169,7 +169,6 @@ export default {
v-if="hasReplies"
:collapsed="!isExpanded"
:replies="replies"
- :class="{ 'discussion-toggle-replies': discussion.diff_discussion }"
@toggle="toggleDiscussion({ discussionId: discussion.id })"
/>
<template v-if="isExpanded">
diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
index 1dd07fe90d2..571928b972b 100644
--- a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
+++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue
@@ -21,7 +21,7 @@ export default {
'li',
{
class:
- 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix',
+ 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base gl-border-top-0',
},
[h('ul', { class: 'notes' }, children)],
);
diff --git a/app/assets/javascripts/notes/components/mr_discussion_filter.vue b/app/assets/javascripts/notes/components/mr_discussion_filter.vue
index 2338c9eef67..7ca0c4730a9 100644
--- a/app/assets/javascripts/notes/components/mr_discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/mr_discussion_filter.vue
@@ -62,6 +62,12 @@ export default {
this.updateMergeRequestFilters(filters);
this.selectedFilters = filters;
},
+ deselectAll() {
+ this.selectedFilters = [];
+ },
+ selectAll() {
+ this.selectedFilters = MR_FILTER_OPTIONS.map((f) => f.value);
+ },
},
MR_FILTER_OPTIONS,
};
@@ -84,9 +90,14 @@ export default {
<gl-collapsible-listbox
v-model="selectedFilters"
:items="$options.MR_FILTER_OPTIONS"
+ :header-text="__('Filter activity')"
+ :show-select-all-button-label="__('Select all')"
+ :reset-button-label="__('Deselect all')"
multiple
placement="right"
@hidden="applyFilters"
+ @reset="deselectAll"
+ @select-all="selectAll"
>
<template #toggle>
<gl-button class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!">
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 47e0ace1ea7..8d2d8095a44 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,7 +1,6 @@
<script>
import {
GlTooltipDirective,
- GlIcon,
GlButton,
GlDisclosureDropdown,
GlDisclosureDropdownItem,
@@ -30,15 +29,14 @@ export default {
},
name: 'NoteActions',
components: {
- GlIcon,
- ReplyButton,
- TimelineEventButton,
+ AbuseCategorySelector,
+ EmojiPicker: () => import('~/emoji/components/picker.vue'),
GlButton,
GlDisclosureDropdown,
GlDisclosureDropdownItem,
+ ReplyButton,
+ TimelineEventButton,
UserAccessRoleBadge,
- EmojiPicker: () => import('~/emoji/components/picker.vue'),
- AbuseCategorySelector,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -318,22 +316,12 @@ export default {
/>
<emoji-picker
v-if="canAwardEmoji"
+ v-gl-tooltip
+ :title="$options.i18n.addReactionLabel"
toggle-class="note-action-button note-emoji-button btn-icon btn-default-tertiary"
data-testid="note-emoji-button"
@click="setAwardEmoji"
- >
- <template #button-content>
- <gl-icon class="award-control-icon-neutral gl-button-icon gl-icon" name="slight-smile" />
- <gl-icon
- class="award-control-icon-positive gl-button-icon gl-icon gl-left-3!"
- name="smiley"
- />
- <gl-icon
- class="award-control-icon-super-positive gl-button-icon gl-icon gl-left-3!"
- name="smile"
- />
- </template>
- </emoji-picker>
+ />
<reply-button
v-if="showReply"
ref="replyButton"
@@ -365,7 +353,8 @@ export default {
<gl-disclosure-dropdown
v-gl-tooltip
:title="$options.i18n.moreActionsLabel"
- :aria-label="$options.i18n.moreActionsLabel"
+ :toggle-text="$options.i18n.moreActionsLabel"
+ text-sr-only
icon="ellipsis_v"
category="tertiary"
placement="right"
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index b4e5129ca0e..1c6be0cfd77 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -174,6 +174,7 @@ export default {
:note-id="note.id"
:line="line"
:note="note"
+ :diff-file="file"
:save-button-title="saveButtonTitle"
:help-page-path="helpPagePath"
:discussion="discussion"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index fe7967f1ed0..4e816038539 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -5,6 +5,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { trackSavedUsingEditor } from '~/vue_shared/components/markdown/tracking';
import eventHub from '../event_hub';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
@@ -192,9 +193,6 @@ export default {
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
},
- quickActionsDocsPath() {
- return this.getNotesDataByProp('quickActionsDocsPath');
- },
currentUserId() {
return this.getUserDataByProp('id');
},
@@ -223,6 +221,15 @@ export default {
enableContentEditor() {
return Boolean(this.glFeatures.contentEditorOnIssues);
},
+ codeSuggestionsConfig() {
+ return {
+ canSuggest: this.canSuggest,
+ line: this.line,
+ lines: this.lines,
+ showPopover: this.showSuggestPopover,
+ diffFile: this.diffFile,
+ };
+ },
},
watch: {
noteBody() {
@@ -290,6 +297,11 @@ export default {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
+ trackSavedUsingEditor(
+ this.$refs.markdownEditor.isContentEditorActive,
+ `${this.getNoteableData.noteableType}_note`,
+ );
+
this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
@@ -321,7 +333,15 @@ export default {
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
- this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
+ this.$emit(
+ 'handleFormUpdateAddToReview',
+ this.updatedNoteBody,
+ shouldResolve,
+ this.$refs.editNoteForm,
+ () => {
+ this.isSubmitting = false;
+ },
+ );
},
hasEmailParticipants() {
return this.getNoteableData.issue_email_participants?.length;
@@ -351,15 +371,11 @@ export default {
:value="updatedNoteBody"
:render-markdown-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
- :line="line"
- :lines="lines"
- :can-suggest="canSuggest"
+ :code-suggestions-config="codeSuggestionsConfig"
:add-spacing-classes="false"
:help-page-path="helpPagePath"
:note="discussionNote"
:form-field-props="formFieldProps"
- :show-suggest-popover="showSuggestPopover"
- :quick-actions-docs-path="quickActionsDocsPath"
:autosave-key="autosaveKey"
:autocomplete-data-sources="autocompleteDataSources"
:disabled="isSubmitting"
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 499581653ba..a5939e1023c 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -15,7 +15,7 @@ import { containsSensitiveToken, confirmSensitiveAction } from '~/lib/utils/secr
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
-import { getErrorMessages } from '../utils';
+import { createNoteErrorMessages } from '../utils';
import DiffDiscussionHeader from './diff_discussion_header.vue';
import DiffWithNote from './diff_with_note.vue';
import DiscussionActions from './discussion_actions.vue';
@@ -96,6 +96,18 @@ export default {
'showJumpToNextDiscussion',
'getUserData',
]),
+ diffFile() {
+ const diffFile = this.discussion.diff_file;
+ if (!diffFile) return null;
+
+ return {
+ ...diffFile,
+ view_path: window.location.href.replace(
+ /\/-\/merge_requests.*/,
+ `/-/blob/${diffFile.content_sha}/${diffFile.new_path}`,
+ ),
+ };
+ },
currentUser() {
return this.getUserData;
},
@@ -270,7 +282,7 @@ export default {
});
},
handleSaveError({ response }) {
- const errorMessage = getErrorMessages(response.data, response.status)[0];
+ const errorMessage = createNoteErrorMessages(response.data, response.status)[0];
createAlert({
message: errorMessage,
@@ -331,7 +343,7 @@ export default {
<li
v-else-if="canShowReplyActions && showReplies"
data-testid="reply-wrapper"
- class="discussion-reply-holder gl-border-t-0! clearfix"
+ class="discussion-reply-holder gl-border-t-0! gl-pb-5! clearfix"
:class="discussionHolderClass"
>
<discussion-actions
@@ -348,6 +360,7 @@ export default {
v-if="isReplying"
ref="noteForm"
:discussion="discussion"
+ :diff-file="diffFile"
:line="diffLine"
:save-button-title="saveButtonTitle"
:autosave-key="autosaveKey"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index dd135eaee3b..69c41af97ab 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -17,8 +17,7 @@ import { containsSensitiveToken, confirmSensitiveAction } from '~/lib/utils/secr
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
-import { renderMarkdown } from '../utils';
-import { UPDATE_COMMENT_FORM } from '../i18n';
+import { renderMarkdown, updateNoteErrorMessage } from '../utils';
import {
getStartLineNumber,
getEndLineNumber,
@@ -114,7 +113,6 @@ export default {
isResolving: false,
commentLineStart: {},
resolveAsThread: true,
- oldContent: this.note.note_html,
};
},
computed: {
@@ -212,7 +210,8 @@ export default {
return fileResolvedFromAvailableSource || null;
},
isMRDiffView() {
- return this.line && !this.isOverviewTab;
+ const isFileComment = this.note.position?.position_type === 'file';
+ return !this.isOverviewTab && (this.line || isFileComment);
},
},
created() {
@@ -295,7 +294,7 @@ export default {
updateSuccess() {
this.isEditing = false;
this.isRequesting = false;
- this.oldContent = this.note.note_html;
+ this.oldContent = null;
renderGFM(this.$refs.noteBody.$el);
this.$emit('updateSuccess');
},
@@ -317,7 +316,9 @@ export default {
noteText,
resolveDiscussion,
position,
+ flashContainer: this.$el,
callback: () => this.updateSuccess(),
+ errorCallback: () => callback(),
});
if (this.isDraft) return;
@@ -343,6 +344,7 @@ export default {
// https://gitlab.com/gitlab-org/gitlab/-/issues/298827
if (!isEmpty(position)) data.note.note.position = JSON.stringify(position);
this.isRequesting = true;
+ this.oldContent = this.note.note_html;
// eslint-disable-next-line vue/no-mutating-props
this.note.note_html = renderMarkdown(noteText);
@@ -369,14 +371,8 @@ export default {
});
},
handleUpdateError(e) {
- const serverErrorMessage = e?.response?.data?.errors;
-
- const alertMessage = serverErrorMessage
- ? sprintf(UPDATE_COMMENT_FORM.error, { reason: serverErrorMessage.toLowerCase() }, false)
- : UPDATE_COMMENT_FORM.defaultError;
-
createAlert({
- message: alertMessage,
+ message: updateNoteErrorMessage(e),
parent: this.$el,
});
},
@@ -442,7 +438,12 @@ export default {
</div>
<div v-if="isMRDiffView" class="timeline-avatar gl-float-left gl-pt-2">
- <gl-avatar-link :href="author.path">
+ <gl-avatar-link
+ :href="author.path"
+ :data-user-id="author.id"
+ :data-username="author.username"
+ class="js-user-link"
+ >
<gl-avatar
:src="author.avatar_url"
:entity-name="author.username"
@@ -455,7 +456,12 @@ export default {
</div>
<div v-else class="timeline-avatar gl-float-left">
- <gl-avatar-link :href="author.path">
+ <gl-avatar-link
+ :href="author.path"
+ :data-user-id="author.id"
+ :data-username="author.username"
+ class="js-user-link"
+ >
<gl-avatar
:src="author.avatar_url"
:entity-name="author.username"
diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
index b0f7a4a4732..a012b4411bc 100644
--- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue
+++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue
@@ -39,7 +39,7 @@ export default {
},
liClasses() {
return this.collapsed
- ? 'gl-text-gray-500 gl-rounded-bottom-left-base! gl-rounded-bottom-right-base! replies-widget-collapsed'
+ ? 'gl-text-gray-500 gl-rounded-bottom-left-base! gl-rounded-bottom-right-base!'
: 'gl-border-b';
},
buttonIcon() {
diff --git a/app/assets/javascripts/notes/mixins/diff_line_note_form.js b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
index 55a63212dc5..cb6f72538b9 100644
--- a/app/assets/javascripts/notes/mixins/diff_line_note_form.js
+++ b/app/assets/javascripts/notes/mixins/diff_line_note_form.js
@@ -7,8 +7,9 @@ import {
} from '~/diffs/constants';
import { createAlert } from '~/alert';
import { clearDraft } from '~/lib/utils/autosave';
-import { s__ } from '~/locale';
+import { sprintf } from '~/locale';
import { formatLineRange } from '~/notes/components/multiline_comment_utils';
+import { SAVING_THE_COMMENT_FAILED, SOMETHING_WENT_WRONG } from '~/diffs/i18n';
export default {
computed: {
@@ -24,7 +25,7 @@ export default {
methods: {
...mapActions('diffs', ['cancelCommentForm', 'toggleFileCommentForm']),
...mapActions('batchComments', ['addDraftToReview', 'saveDraft', 'insertDraftIntoDrafts']),
- addReplyToReview(noteText, isResolving) {
+ addReplyToReview(noteText, isResolving, parentElement, errorCallback) {
const postData = getDraftReplyFormData({
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
@@ -39,19 +40,26 @@ export default {
postData.note_project_id = this.discussion.project_id;
}
- this.isReplying = false;
-
this.saveDraft(postData)
.then(() => {
+ this.isReplying = false;
this.handleClearForm(this.discussion.line_code);
})
- .catch(() => {
+ .catch((response) => {
+ const reason = response?.data?.errors;
+ const errorMessage = reason
+ ? sprintf(SAVING_THE_COMMENT_FAILED, { reason })
+ : SOMETHING_WENT_WRONG;
+
createAlert({
- message: s__('MergeRequests|An error occurred while saving the draft comment.'),
+ message: errorMessage,
+ parent: parentElement,
});
+
+ errorCallback();
});
},
- addToReview(note, positionType = null) {
+ addToReview(note, positionType = null, parentElement, errorCallback) {
const lineRange =
(this.line && this.commentLineStart && formatLineRange(this.commentLineStart, this.line)) ||
{};
@@ -88,10 +96,18 @@ export default {
this.toggleFileCommentForm(diffFile.file_path);
}
})
- .catch(() => {
+ .catch((response) => {
+ const reason = response?.data?.errors;
+ const errorMessage = reason
+ ? sprintf(SAVING_THE_COMMENT_FAILED, { reason })
+ : SOMETHING_WENT_WRONG;
+
createAlert({
- message: s__('MergeRequests|An error occurred while saving the draft comment.'),
+ message: errorMessage,
+ parent: parentElement,
});
+
+ errorCallback();
});
},
handleClearForm(lineCode) {
diff --git a/app/assets/javascripts/notes/utils.js b/app/assets/javascripts/notes/utils.js
index c5859a89182..a561d26ad56 100644
--- a/app/assets/javascripts/notes/utils.js
+++ b/app/assets/javascripts/notes/utils.js
@@ -4,7 +4,7 @@ import { sanitize } from '~/lib/dompurify';
import { markdownConfig } from '~/lib/utils/text_utility';
import { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import { sprintf } from '~/locale';
-import { COMMENT_FORM } from './i18n';
+import { UPDATE_COMMENT_FORM, COMMENT_FORM } from './i18n';
/**
* Tracks snowplow event when User toggles timeline view
@@ -23,7 +23,7 @@ export const renderMarkdown = (rawMarkdown) => {
return sanitize(marked(rawMarkdown), markdownConfig);
};
-export const getErrorMessages = (data, status) => {
+export const createNoteErrorMessages = (data, status) => {
const errors = data?.errors;
if (errors && status === HTTP_STATUS_UNPROCESSABLE_ENTITY) {
@@ -36,3 +36,13 @@ export const getErrorMessages = (data, status) => {
return [COMMENT_FORM.GENERIC_UNSUBMITTABLE_NETWORK];
};
+
+export const updateNoteErrorMessage = (e) => {
+ const errors = e?.response?.data?.errors;
+
+ if (errors) {
+ return sprintf(UPDATE_COMMENT_FORM.error, { reason: errors.toLowerCase() });
+ }
+
+ return UPDATE_COMMENT_FORM.defaultError;
+};