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/batch_comments/components')
-rw-r--r--app/assets/javascripts/batch_comments/components/diff_file_drafts.vue41
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue113
-rw-r--r--app/assets/javascripts/batch_comments/components/drafts_count.vue15
-rw-r--r--app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue32
-rw-r--r--app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue45
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_dropdown.vue111
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_item.vue143
-rw-r--r--app/assets/javascripts/batch_comments/components/publish_button.vue55
-rw-r--r--app/assets/javascripts/batch_comments/components/review_bar.vue70
9 files changed, 625 insertions, 0 deletions
diff --git a/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue b/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue
new file mode 100644
index 00000000000..570954c7200
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue
@@ -0,0 +1,41 @@
+<script>
+import { mapGetters } from 'vuex';
+import imageDiff from '~/diffs/mixins/image_diff';
+import DraftNote from './draft_note.vue';
+
+export default {
+ components: {
+ DraftNote,
+ },
+ mixins: [imageDiff],
+ props: {
+ fileHash: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters('batchComments', ['draftsForFile']),
+ drafts() {
+ return this.draftsForFile(this.fileHash);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ v-for="(draft, index) in drafts"
+ :key="draft.id"
+ class="discussion-notes diff-discussions position-relative"
+ >
+ <div class="notes">
+ <span class="d-block btn-transparent badge badge-pill is-draft js-diff-notes-index">
+ {{ toggleText(draft, index) }}
+ </span>
+ <draft-note :draft="draft" />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue
new file mode 100644
index 00000000000..963d104b6b3
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/draft_note.vue
@@ -0,0 +1,113 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import NoteableNote from '~/notes/components/noteable_note.vue';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import PublishButton from './publish_button.vue';
+
+export default {
+ components: {
+ NoteableNote,
+ PublishButton,
+ LoadingButton,
+ },
+ props: {
+ draft: {
+ type: Object,
+ required: true,
+ },
+ diffFile: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ isEditingDraft: false,
+ };
+ },
+ computed: {
+ ...mapState('batchComments', ['isPublishing']),
+ ...mapGetters('batchComments', ['isPublishingDraft']),
+ draftCommands() {
+ return this.draft.references.commands;
+ },
+ },
+ mounted() {
+ if (window.location.hash && window.location.hash === `#note_${this.draft.id}`) {
+ this.scrollToDraft(this.draft);
+ }
+ },
+ methods: {
+ ...mapActions('batchComments', [
+ 'deleteDraft',
+ 'updateDraft',
+ 'publishSingleDraft',
+ 'scrollToDraft',
+ 'toggleResolveDiscussion',
+ ]),
+ update(data) {
+ this.updateDraft(data);
+ },
+ publishNow() {
+ this.publishSingleDraft(this.draft.id);
+ },
+ handleEditing() {
+ this.isEditingDraft = true;
+ },
+ handleNotEditing() {
+ this.isEditingDraft = false;
+ },
+ },
+};
+</script>
+<template>
+ <article class="draft-note-component note-wrapper">
+ <ul class="notes draft-notes">
+ <noteable-note
+ :note="draft"
+ :diff-lines="diffFile.highlighted_diff_lines"
+ :line="line"
+ class="draft-note"
+ @handleEdit="handleEditing"
+ @cancelForm="handleNotEditing"
+ @updateSuccess="handleNotEditing"
+ @handleDeleteNote="deleteDraft"
+ @handleUpdateNote="update"
+ @toggleResolveStatus="toggleResolveDiscussion(draft.id)"
+ >
+ <strong slot="note-header-info" class="badge draft-pending-label append-right-4">
+ {{ __('Pending') }}
+ </strong>
+ </noteable-note>
+ </ul>
+
+ <template v-if="!isEditingDraft">
+ <div
+ v-if="draftCommands"
+ class="referenced-commands draft-note-commands"
+ v-html="draftCommands"
+ ></div>
+
+ <p class="draft-note-actions d-flex">
+ <publish-button
+ :show-count="true"
+ :should-publish="false"
+ class="btn btn-success btn-inverted gl-mr-3"
+ />
+ <loading-button
+ ref="publishNowButton"
+ :loading="isPublishingDraft(draft.id) || isPublishing"
+ :label="__('Add comment now')"
+ container-class="btn btn-inverted"
+ @click="publishNow"
+ />
+ </p>
+ </template>
+ </article>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/drafts_count.vue b/app/assets/javascripts/batch_comments/components/drafts_count.vue
new file mode 100644
index 00000000000..f1180760c4d
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/drafts_count.vue
@@ -0,0 +1,15 @@
+<script>
+import { mapGetters } from 'vuex';
+
+export default {
+ computed: {
+ ...mapGetters('batchComments', ['draftsCount']),
+ },
+};
+</script>
+<template>
+ <span class="drafts-count-component">
+ <span class="drafts-count-number">{{ draftsCount }}</span>
+ <span class="sr-only"> {{ n__('draft', 'drafts', draftsCount) }} </span>
+ </span>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue b/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue
new file mode 100644
index 00000000000..385725cd109
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue
@@ -0,0 +1,32 @@
+<script>
+import DraftNote from './draft_note.vue';
+
+export default {
+ components: {
+ DraftNote,
+ },
+ props: {
+ draft: {
+ type: Object,
+ required: true,
+ },
+ diffFile: {
+ type: Object,
+ required: true,
+ },
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+};
+</script>
+
+<template>
+ <tr class="notes_holder js-temp-notes-holder">
+ <td class="notes-content" colspan="4">
+ <div class="content"><draft-note :draft="draft" :diff-file="diffFile" :line="line" /></div>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue b/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue
new file mode 100644
index 00000000000..68fd20e56bc
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/parallel_draft_comment_row.vue
@@ -0,0 +1,45 @@
+<script>
+import { mapGetters } from 'vuex';
+import DraftNote from './draft_note.vue';
+
+export default {
+ components: {
+ DraftNote,
+ },
+ props: {
+ line: {
+ type: Object,
+ required: true,
+ },
+ diffFileContentSha: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters('batchComments', ['draftForLine']),
+ className() {
+ return this.leftDraft > 0 || this.rightDraft > 0 ? '' : 'js-temp-notes-holder';
+ },
+ leftDraft() {
+ return this.draftForLine(this.diffFileContentSha, this.line, 'left');
+ },
+ rightDraft() {
+ return this.draftForLine(this.diffFileContentSha, this.line, 'right');
+ },
+ },
+};
+</script>
+
+<template>
+ <tr :class="className" class="notes_holder">
+ <td class="notes_line old"></td>
+ <td class="notes-content parallel old" colspan="2">
+ <div v-if="leftDraft.isDraft" class="content"><draft-note :draft="leftDraft" /></div>
+ </td>
+ <td class="notes_line new"></td>
+ <td class="notes-content parallel new" colspan="2">
+ <div v-if="rightDraft.isDraft" class="content"><draft-note :draft="rightDraft" /></div>
+ </td>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
new file mode 100644
index 00000000000..195e1b7ec5c
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
@@ -0,0 +1,111 @@
+<script>
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { sprintf, n__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import DraftsCount from './drafts_count.vue';
+import PublishButton from './publish_button.vue';
+import PreviewItem from './preview_item.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ Icon,
+ DraftsCount,
+ PublishButton,
+ PreviewItem,
+ },
+ computed: {
+ ...mapGetters(['isNotesFetched']),
+ ...mapGetters('batchComments', ['draftsCount', 'sortedDrafts']),
+ ...mapState('batchComments', ['showPreviewDropdown']),
+ dropdownTitle() {
+ return sprintf(
+ n__('%{count} pending comment', '%{count} pending comments', this.draftsCount),
+ { count: this.draftsCount },
+ );
+ },
+ },
+ watch: {
+ showPreviewDropdown() {
+ if (this.showPreviewDropdown && this.$refs.dropdown) {
+ this.$nextTick(() => this.$refs.dropdown.focus());
+ }
+ },
+ },
+ mounted() {
+ document.addEventListener('click', this.onClickDocument);
+ },
+ beforeDestroy() {
+ document.removeEventListener('click', this.onClickDocument);
+ },
+ methods: {
+ ...mapActions('batchComments', ['toggleReviewDropdown']),
+ isLast(index) {
+ return index === this.sortedDrafts.length - 1;
+ },
+ onClickDocument({ target }) {
+ if (
+ this.showPreviewDropdown &&
+ !target.closest('.review-preview-dropdown, .js-publish-draft-button')
+ ) {
+ this.toggleReviewDropdown();
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="dropdown float-right review-preview-dropdown"
+ :class="{
+ show: showPreviewDropdown,
+ }"
+ >
+ <button
+ ref="dropdown"
+ type="button"
+ class="btn btn-success review-preview-dropdown-toggle qa-review-preview-toggle"
+ @click="toggleReviewDropdown"
+ >
+ {{ __('Finish review') }}
+ <drafts-count />
+ <icon name="angle-up" />
+ </button>
+ <div
+ class="dropdown-menu dropdown-menu-large dropdown-menu-right dropdown-open-top"
+ :class="{
+ show: showPreviewDropdown,
+ }"
+ >
+ <div class="dropdown-title">
+ {{ dropdownTitle }}
+ <button
+ :aria-label="__('Close')"
+ type="button"
+ class="dropdown-title-button dropdown-menu-close"
+ @click="toggleReviewDropdown"
+ >
+ <icon name="close" />
+ </button>
+ </div>
+ <div class="dropdown-content">
+ <ul v-if="isNotesFetched">
+ <li v-for="(draft, index) in sortedDrafts" :key="draft.id">
+ <preview-item :draft="draft" :is-last="isLast(index)" />
+ </li>
+ </ul>
+ <gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" />
+ </div>
+ <div class="dropdown-footer">
+ <publish-button
+ :show-count="false"
+ :should-publish="true"
+ :label="__('Submit review')"
+ class="float-right gl-mr-3"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/preview_item.vue b/app/assets/javascripts/batch_comments/components/preview_item.vue
new file mode 100644
index 00000000000..22495eb4d7d
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/preview_item.vue
@@ -0,0 +1,143 @@
+<script>
+import { mapActions, mapGetters } from 'vuex';
+import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
+import { sprintf, __ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import resolvedStatusMixin from '../mixins/resolved_status';
+import { GlSprintf } from '@gitlab/ui';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import {
+ getStartLineNumber,
+ getEndLineNumber,
+ getLineClasses,
+} from '~/notes/components/multiline_comment_utils';
+
+export default {
+ components: {
+ Icon,
+ GlSprintf,
+ },
+ mixins: [resolvedStatusMixin, glFeatureFlagsMixin()],
+ props: {
+ draft: {
+ type: Object,
+ required: true,
+ },
+ isLast: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapGetters('diffs', ['getDiffFileByHash']),
+ ...mapGetters(['getDiscussion']),
+ iconName() {
+ return this.isDiffDiscussion || this.draft.line_code ? 'doc-text' : 'comment';
+ },
+ discussion() {
+ return this.getDiscussion(this.draft.discussion_id);
+ },
+ isDiffDiscussion() {
+ return this.discussion && this.discussion.diff_discussion;
+ },
+ titleText() {
+ const file = this.discussion ? this.discussion.diff_file : this.draft;
+
+ if (file) {
+ return file.file_path;
+ }
+
+ return sprintf(__("%{authorsName}'s thread"), {
+ authorsName: this.discussion.notes.find(note => !note.system).author.name,
+ });
+ },
+ linePosition() {
+ if (this.draft.position && this.draft.position.position_type === IMAGE_DIFF_POSITION_TYPE) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `${this.draft.position.x}x ${this.draft.position.y}y`;
+ }
+
+ const position = this.discussion ? this.discussion.position : this.draft.position;
+
+ return position?.new_line || position?.old_line;
+ },
+ content() {
+ const el = document.createElement('div');
+ el.innerHTML = this.draft.note_html;
+
+ return el.textContent;
+ },
+ showLinePosition() {
+ return this.draft.file_hash || this.isDiffDiscussion;
+ },
+ startLineNumber() {
+ return getStartLineNumber(this.draft.position?.line_range);
+ },
+ endLineNumber() {
+ return getEndLineNumber(this.draft.position?.line_range);
+ },
+ },
+ methods: {
+ ...mapActions('batchComments', ['scrollToDraft']),
+ getLineClasses(lineNumber) {
+ return getLineClasses(lineNumber);
+ },
+ },
+ showStaysResolved: false,
+};
+</script>
+
+<template>
+ <button
+ type="button"
+ class="review-preview-item menu-item"
+ :class="[
+ componentClasses,
+ {
+ 'is-last': isLast,
+ },
+ ]"
+ @click="scrollToDraft(draft)"
+ >
+ <span class="review-preview-item-header">
+ <icon class="flex-shrink-0" :name="iconName" />
+ <span
+ class="bold text-nowrap"
+ :class="{ 'gl-align-items-center': glFeatures.multilineComments }"
+ >
+ <span class="review-preview-item-header-text block-truncated">
+ {{ titleText }}
+ </span>
+ <template v-if="showLinePosition">
+ <template v-if="!glFeatures.multilineComments"
+ >:{{ linePosition }}</template
+ >
+ <template v-else-if="startLineNumber === endLineNumber">
+ :<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span>
+ </template>
+ <gl-sprintf v-else :message="__(':%{startLine} to %{endLine}')">
+ <template #startLine>
+ <span class="gl-mr-2" :class="getLineClasses(startLineNumber)">{{
+ startLineNumber
+ }}</span>
+ </template>
+ <template #endLine>
+ <span class="gl-ml-2" :class="getLineClasses(endLineNumber)">{{
+ endLineNumber
+ }}</span>
+ </template>
+ </gl-sprintf>
+ </template>
+ </span>
+ </span>
+ <span class="review-preview-item-content">
+ <p>{{ content }}</p>
+ </span>
+ <span
+ v-if="draft.discussion_id && resolvedStatusMessage"
+ class="review-preview-item-footer draft-note-resolution p-0"
+ >
+ <icon class="gl-mr-3" name="status_success" /> {{ resolvedStatusMessage }}
+ </span>
+ </button>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/publish_button.vue b/app/assets/javascripts/batch_comments/components/publish_button.vue
new file mode 100644
index 00000000000..f4dc0f04dc3
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/publish_button.vue
@@ -0,0 +1,55 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import { __ } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import DraftsCount from './drafts_count.vue';
+
+export default {
+ components: {
+ LoadingButton,
+ DraftsCount,
+ },
+ props: {
+ showCount: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ label: {
+ type: String,
+ required: false,
+ default: __('Finish review'),
+ },
+ shouldPublish: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState('batchComments', ['isPublishing']),
+ },
+ methods: {
+ ...mapActions('batchComments', ['publishReview', 'toggleReviewDropdown']),
+ onClick() {
+ if (this.shouldPublish) {
+ this.publishReview();
+ } else {
+ this.toggleReviewDropdown();
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <loading-button
+ :loading="isPublishing"
+ container-class="btn btn-success js-publish-draft-button qa-submit-review"
+ @click="onClick"
+ >
+ <span>
+ {{ label }}
+ <drafts-count v-if="showCount" />
+ </span>
+ </loading-button>
+</template>
diff --git a/app/assets/javascripts/batch_comments/components/review_bar.vue b/app/assets/javascripts/batch_comments/components/review_bar.vue
new file mode 100644
index 00000000000..b0e8b806701
--- /dev/null
+++ b/app/assets/javascripts/batch_comments/components/review_bar.vue
@@ -0,0 +1,70 @@
+<script>
+import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlModal, GlModalDirective } from '@gitlab/ui';
+import { sprintf, s__ } from '~/locale';
+import LoadingButton from '~/vue_shared/components/loading_button.vue';
+import PreviewDropdown from './preview_dropdown.vue';
+
+export default {
+ components: {
+ LoadingButton,
+ GlModal,
+ PreviewDropdown,
+ },
+ directives: {
+ 'gl-modal': GlModalDirective,
+ },
+ computed: {
+ ...mapGetters(['isNotesFetched']),
+ ...mapState('batchComments', ['isDiscarding']),
+ ...mapGetters('batchComments', ['draftsCount']),
+ },
+ watch: {
+ isNotesFetched() {
+ if (this.isNotesFetched) {
+ this.expandAllDiscussions();
+ }
+ },
+ },
+ methods: {
+ ...mapActions('batchComments', ['discardReview', 'expandAllDiscussions']),
+ },
+ modalId: 'discard-draft-review',
+ text: sprintf(
+ s__(
+ `BatchComments|You're about to discard your review which will delete all of your pending comments.
+ The deleted comments %{strong_start}cannot%{strong_end} be restored.`,
+ ),
+ {
+ strong_start: '<strong>',
+ strong_end: '</strong>',
+ },
+ false,
+ ),
+};
+</script>
+<template>
+ <div v-show="draftsCount > 0">
+ <nav class="review-bar-component">
+ <div class="review-bar-content qa-review-bar">
+ <preview-dropdown />
+ <loading-button
+ v-gl-modal="$options.modalId"
+ :loading="isDiscarding"
+ :label="__('Discard review')"
+ class="qa-discard-review float-right"
+ />
+ </div>
+ </nav>
+ <gl-modal
+ :title="s__('BatchComments|Discard review?')"
+ :ok-title="s__('BatchComments|Delete all pending comments')"
+ :modal-id="$options.modalId"
+ title-tag="h4"
+ ok-variant="danger qa-modal-delete-pending-comments"
+ @ok="discardReview"
+ >
+ <p v-html="$options.text"></p>
+ </gl-modal>
+ </div>
+</template>