summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/diffs
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/diffs')
-rw-r--r--app/assets/javascripts/diffs/components/app.vue50
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue12
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_comment_cell.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue24
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussion_reply.vue11
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_expansion_cell.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue65
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue77
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_row.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue20
-rw-r--r--app/assets/javascripts/diffs/components/diff_row.vue23
-rw-r--r--app/assets/javascripts/diffs/components/diff_row_utils.js4
-rw-r--r--app/assets/javascripts/diffs/components/diff_stats.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_view.vue28
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue4
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue2
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue8
-rw-r--r--app/assets/javascripts/diffs/components/no_changes.vue2
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue4
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue6
-rw-r--r--app/assets/javascripts/diffs/components/settings_dropdown.vue4
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue2
-rw-r--r--app/assets/javascripts/diffs/i18n.js7
-rw-r--r--app/assets/javascripts/diffs/index.js7
-rw-r--r--app/assets/javascripts/diffs/store/actions.js46
-rw-r--r--app/assets/javascripts/diffs/store/getters.js19
-rw-r--r--app/assets/javascripts/diffs/store/getters_versions_dropdowns.js2
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js19
-rw-r--r--app/assets/javascripts/diffs/store/utils.js2
-rw-r--r--app/assets/javascripts/diffs/utils/diff_file.js6
-rw-r--r--app/assets/javascripts/diffs/utils/file_reviews.js25
-rw-r--r--app/assets/javascripts/diffs/utils/performance.js2
-rw-r--r--app/assets/javascripts/diffs/utils/suggestions.js28
-rw-r--r--app/assets/javascripts/diffs/utils/uuids.js2
38 files changed, 323 insertions, 215 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 32822fe1fe8..4323499ef1f 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -1,32 +1,17 @@
<script>
-import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlPagination, GlSprintf } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Mousetrap from 'mousetrap';
-import { __ } from '~/locale';
-import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
+import { mapState, mapGetters, mapActions } from 'vuex';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
+import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import notesEventHub from '../../notes/event_hub';
-import eventHub from '../event_hub';
-
-import CompareVersions from './compare_versions.vue';
-import DiffFile from './diff_file.vue';
-import NoChanges from './no_changes.vue';
-import CommitWidget from './commit_widget.vue';
-import TreeList from './tree_list.vue';
-
-import HiddenFilesWarning from './hidden_files_warning.vue';
-import MergeConflictWarning from './merge_conflict_warning.vue';
-import CollapsedFilesWarning from './collapsed_files_warning.vue';
-
-import { diffsApp } from '../utils/performance';
-import { fileByFile } from '../utils/preferences';
-
import {
TREE_LIST_WIDTH_STORAGE_KEY,
INITIAL_TREE_WIDTH,
@@ -40,6 +25,19 @@ import {
ALERT_COLLAPSED_FILES,
EVT_VIEW_FILE_BY_FILE,
} from '../constants';
+import eventHub from '../event_hub';
+
+import { reviewStatuses } from '../utils/file_reviews';
+import { diffsApp } from '../utils/performance';
+import { fileByFile } from '../utils/preferences';
+import CollapsedFilesWarning from './collapsed_files_warning.vue';
+import CommitWidget from './commit_widget.vue';
+import CompareVersions from './compare_versions.vue';
+import DiffFile from './diff_file.vue';
+import HiddenFilesWarning from './hidden_files_warning.vue';
+import MergeConflictWarning from './merge_conflict_warning.vue';
+import NoChanges from './no_changes.vue';
+import TreeList from './tree_list.vue';
export default {
name: 'DiffsApp',
@@ -169,12 +167,7 @@ export default {
'hasConflicts',
'viewDiffsFileByFile',
]),
- ...mapGetters('diffs', [
- 'whichCollapsedTypes',
- 'isParallelView',
- 'currentDiffIndex',
- 'fileReviews',
- ]),
+ ...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
diffs() {
if (!this.viewDiffsFileByFile) {
@@ -232,6 +225,9 @@ export default {
return visible;
},
+ fileReviews() {
+ return reviewStatuses(this.diffFiles, this.mrReviews);
+ },
},
watch: {
commit(newCommit, oldCommit) {
@@ -526,7 +522,7 @@ export default {
:file="file"
:reviewed="fileReviews[index]"
:is-first-file="index === 0"
- :is-last-file="index === diffs.length - 1"
+ :is-last-file="index === diffFilesLength - 1"
:help-page-path="helpPagePath"
:can-current-user-fork="canCurrentUserFork"
:view-diffs-file-by-file="viewDiffsFileByFile"
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index af19f90ee77..92b317eb3f0 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -1,18 +1,16 @@
<script>
/* eslint-disable vue/no-v-html */
-import { mapActions } from 'vuex';
import { GlButtonGroup, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { mapActions } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
-
-import initUserPopovers from '../../user_popovers';
import { setUrlParams } from '../../lib/utils/url_utility';
+import initUserPopovers from '../../user_popovers';
/**
* CommitItem
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 489278fd6ef..6b1e2bfb34e 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -1,13 +1,12 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton, GlSprintf } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { __ } from '~/locale';
-import { polyfillSticky } from '~/lib/utils/sticky';
-import CompareDropdownLayout from './compare_dropdown_layout.vue';
-import SettingsDropdown from './settings_dropdown.vue';
-import DiffStats from './diff_stats.vue';
import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '../constants';
import eventHub from '../event_hub';
+import CompareDropdownLayout from './compare_dropdown_layout.vue';
+import DiffStats from './diff_stats.vue';
+import SettingsDropdown from './settings_dropdown.vue';
export default {
components: {
@@ -61,9 +60,6 @@ export default {
created() {
this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
},
- mounted() {
- polyfillSticky(this.$el);
- },
methods: {
...mapActions('diffs', ['setInlineDiffViewType', 'setParallelDiffViewType', 'setShowTreeList']),
expandAllFiles() {
diff --git a/app/assets/javascripts/diffs/components/diff_comment_cell.vue b/app/assets/javascripts/diffs/components/diff_comment_cell.vue
index 4b0b603f5a5..4af4b46f94c 100644
--- a/app/assets/javascripts/diffs/components/diff_comment_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_comment_cell.vue
@@ -1,8 +1,8 @@
<script>
import { mapActions } from 'vuex';
+import DiffDiscussionReply from './diff_discussion_reply.vue';
import DiffDiscussions from './diff_discussions.vue';
import DiffLineNoteForm from './diff_line_note_form.vue';
-import DiffDiscussionReply from './diff_discussion_reply.vue';
export default {
components: {
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index f4e2571dd09..663d2bb3cf8 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -1,25 +1,25 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
+import { diffViewerModes } from '~/ide/constants';
+import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
-import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
-import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
-import InlineDiffView from './inline_diff_view.vue';
-import ParallelDiffView from './parallel_diff_view.vue';
-import DiffView from './diff_view.vue';
-import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
+import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import NoteForm from '../../notes/components/note_form.vue';
-import ImageDiffOverlay from './image_diff_overlay.vue';
-import DiffDiscussions from './diff_discussions.vue';
import eventHub from '../../notes/event_hub';
+import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
import { getDiffMode } from '../store/utils';
-import { diffViewerModes } from '~/ide/constants';
+import DiffDiscussions from './diff_discussions.vue';
import { mapInline, mapParallel } from './diff_row_utils';
+import DiffView from './diff_view.vue';
+import ImageDiffOverlay from './image_diff_overlay.vue';
+import InlineDiffView from './inline_diff_view.vue';
+import ParallelDiffView from './parallel_diff_view.vue';
export default {
components: {
diff --git a/app/assets/javascripts/diffs/components/diff_discussion_reply.vue b/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
index 531ebaddacd..9027d0c8aa4 100644
--- a/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
@@ -1,15 +1,13 @@
<script>
import { mapGetters } from 'vuex';
-import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
-import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue';
export default {
name: 'DiffDiscussionReply',
components: {
NoteSignedOutWidget,
ReplyPlaceholder,
- UserAvatarLink,
},
props: {
hasForm: {
@@ -36,13 +34,6 @@ export default {
<template v-if="userCanReply">
<slot v-if="hasForm" name="form"></slot>
<template v-else-if="renderReplyPlaceholder">
- <user-avatar-link
- :link-href="currentUser.path"
- :img-src="currentUser.avatar_url"
- :img-alt="currentUser.name"
- :img-size="40"
- class="d-none d-sm-block"
- />
<reply-placeholder
:button-text="__('Start a new discussion...')"
@onClick="$emit('showNewDiscussionForm')"
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index 7b55bd2104d..d0d457d8582 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -1,6 +1,6 @@
<script>
-import { mapActions } from 'vuex';
import { GlIcon } from '@gitlab/ui';
+import { mapActions } from 'vuex';
import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
export default {
diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
index 2d1a7237122..67900af8789 100644
--- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
@@ -1,6 +1,6 @@
<script>
-import { mapState, mapActions } from 'vuex';
import { GlIcon } from '@gitlab/ui';
+import { mapState, mapActions } from 'vuex';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__, sprintf } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index e613b684345..f77c8d7406b 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -1,16 +1,14 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
+import { GlButton, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml, GlSprintf } from '@gitlab/ui';
import { escape } from 'lodash';
-import { GlButton, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { sprintf } from '~/locale';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { hasDiff } from '~/helpers/diffs_helper';
-import notesEventHub from '../../notes/event_hub';
-import DiffFileHeader from './diff_file_header.vue';
-import DiffContent from './diff_content.vue';
import { diffViewerErrors } from '~/ide/constants';
-import { collapsedType, isCollapsed } from '../utils/diff_file';
+import { sprintf } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import notesEventHub from '../../notes/event_hub';
+
import {
DIFF_FILE_AUTOMATIC_COLLAPSE,
DIFF_FILE_MANUAL_COLLAPSE,
@@ -18,8 +16,11 @@ import {
EVT_PERF_MARK_DIFF_FILES_END,
EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN,
} from '../constants';
-import { DIFF_FILE, GENERIC_ERROR } from '../i18n';
import eventHub from '../event_hub';
+import { DIFF_FILE, GENERIC_ERROR } from '../i18n';
+import { collapsedType, isCollapsed, getShortShaFromFile } from '../utils/diff_file';
+import DiffContent from './diff_content.vue';
+import DiffFileHeader from './diff_file_header.vue';
export default {
components: {
@@ -27,6 +28,7 @@ export default {
DiffContent,
GlButton,
GlLoadingIcon,
+ GlSprintf,
},
directives: {
SafeHtml,
@@ -81,15 +83,11 @@ export default {
...mapState('diffs', ['currentDiffFileId']),
...mapGetters(['isNotesFetched']),
...mapGetters('diffs', ['getDiffFileDiscussions']),
- viewBlobLink() {
- return sprintf(
- this.$options.i18n.blobView,
- {
- linkStart: `<a href="${escape(this.file.view_path)}">`,
- linkEnd: '</a>',
- },
- false,
- );
+ viewBlobHref() {
+ return escape(this.file.view_path);
+ },
+ shortSha() {
+ return getShortShaFromFile(this.file);
},
showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
@@ -98,7 +96,7 @@ export default {
return hasDiff(this.file);
},
isFileTooLarge() {
- return this.file.viewer.error === diffViewerErrors.too_large;
+ return !this.manuallyCollapsed && this.file.viewer.error === diffViewerErrors.too_large;
},
errorMessage() {
return !this.manuallyCollapsed ? this.file.viewer.error_message : '';
@@ -144,6 +142,12 @@ export default {
showContent() {
return !this.isCollapsed && !this.isFileTooLarge;
},
+ showLocalFileReviews() {
+ const loggedIn = Boolean(gon.current_user_id);
+ const featureOn = this.glFeatures.localFileReviews;
+
+ return loggedIn && featureOn;
+ },
},
watch: {
'file.file_hash': {
@@ -181,6 +185,10 @@ export default {
if (this.hasDiff) {
this.postRender();
}
+
+ if (this.reviewed && !this.isCollapsed && this.showLocalFileReviews) {
+ this.handleToggle();
+ }
},
beforeDestroy() {
eventHub.$off(EVT_EXPAND_ALL_FILES, this.expandAllListener);
@@ -273,9 +281,11 @@ export default {
:can-current-user-fork="canCurrentUserFork"
:diff-file="file"
:collapsible="true"
+ :reviewed="reviewed"
:expanded="!isCollapsed"
:add-merge-request-buttons="true"
:view-diffs-file-by-file="viewDiffsFileByFile"
+ :show-local-file-reviews="showLocalFileReviews"
class="js-file-title file-title gl-border-1 gl-border-solid gl-border-gray-100"
:class="hasBodyClasses.header"
@toggleFile="handleToggle"
@@ -309,14 +319,27 @@ export default {
data-testid="loader-icon"
/>
<div v-else-if="errorMessage" class="diff-viewer">
- <div v-safe-html="errorMessage" class="nothing-here-block"></div>
+ <div
+ v-if="isFileTooLarge"
+ class="collapsed-file-warning gl-p-7 gl-bg-orange-50 gl-text-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
+ >
+ <p class="gl-mb-5">
+ {{ $options.i18n.tooLarge }}
+ </p>
+ <gl-button data-testid="blob-button" category="secondary" :href="viewBlobHref">
+ <gl-sprintf :message="$options.i18n.blobView">
+ <template #commitSha>{{ shortSha }}</template>
+ </gl-sprintf>
+ </gl-button>
+ </div>
+ <div v-else v-safe-html="errorMessage" class="nothing-here-block"></div>
</div>
<template v-else>
<div
v-show="showWarning"
class="collapsed-file-warning gl-p-7 gl-bg-orange-50 gl-text-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base"
>
- <p class="gl-mb-8">
+ <p class="gl-mb-5">
{{ $options.i18n.autoCollapsed }}
</p>
<gl-button
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 53d1383b82e..1195a7f2565 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -1,6 +1,4 @@
<script>
-import { escape } from 'lodash';
-import { mapActions, mapGetters } from 'vuex';
import {
GlTooltipDirective,
GlSafeHtmlDirective,
@@ -10,17 +8,25 @@ import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
+ GlFormCheckbox,
GlLoadingIcon,
} from '@gitlab/ui';
-import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
-import { truncateSha } from '~/lib/utils/text_utility';
-import { __, s__, sprintf } from '~/locale';
+import { escape } from 'lodash';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { diffViewerModes } from '~/ide/constants';
-import DiffStats from './diff_stats.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
-import { isCollapsed } from '../utils/diff_file';
+import { truncateSha } from '~/lib/utils/text_utility';
+import { __, s__, sprintf } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+import { DIFF_FILE_AUTOMATIC_COLLAPSE } from '../constants';
import { DIFF_FILE_HEADER } from '../i18n';
+import { collapsedType, isCollapsed } from '../utils/diff_file';
+import { reviewable } from '../utils/file_reviews';
+
+import DiffStats from './diff_stats.vue';
export default {
components: {
@@ -33,12 +39,14 @@ export default {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
+ GlFormCheckbox,
GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
},
+ mixins: [glFeatureFlagsMixin()],
i18n: {
...DIFF_FILE_HEADER,
},
@@ -76,6 +84,16 @@ export default {
required: false,
default: false,
},
+ showLocalFileReviews: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ reviewed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -83,6 +101,7 @@ export default {
};
},
computed: {
+ ...mapState('diffs', ['latestDiff']),
...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
diffContentIDSelector() {
return `#diff-content-${this.diffFile.file_hash}`;
@@ -170,6 +189,9 @@ export default {
(this.diffFile.edit_path || this.diffFile.ide_edit_path)
);
},
+ isReviewable() {
+ return reviewable(this.diffFile);
+ },
},
methods: {
...mapActions('diffs', [
@@ -177,6 +199,8 @@ export default {
'toggleFileDiscussionWrappers',
'toggleFullDiff',
'toggleActiveFileByHash',
+ 'reviewFile',
+ 'setFileCollapsedByUser',
]),
handleToggleFile() {
this.$emit('toggleFile');
@@ -204,6 +228,26 @@ export default {
setMoreActionsShown(val) {
this.moreActionsShown = val;
},
+ toggleReview(newReviewedStatus) {
+ const autoCollapsed =
+ this.isCollapsed && collapsedType(this.diffFile) === DIFF_FILE_AUTOMATIC_COLLAPSE;
+ const open = this.expanded;
+ const closed = !open;
+ const reviewed = newReviewedStatus;
+
+ this.reviewFile({ file: this.diffFile, reviewed });
+
+ if (reviewed && autoCollapsed) {
+ this.setFileCollapsedByUser({
+ filePath: this.diffFile.file_path,
+ collapsed: true,
+ });
+ }
+
+ if ((open && reviewed) || (closed && !reviewed)) {
+ this.$emit('toggleFile');
+ }
+ },
},
};
</script>
@@ -213,6 +257,8 @@ export default {
ref="header"
:class="{ 'gl-z-dropdown-menu!': moreActionsShown }"
class="js-file-title file-title file-title-flex-parent"
+ data-qa-selector="file_title_container"
+ :data-qa-file-name="filePath"
@click.self="handleToggleFile"
>
<div class="file-header-content">
@@ -289,6 +335,19 @@ export default {
class="file-actions d-flex align-items-center gl-ml-auto gl-align-self-start"
>
<diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
+ <gl-form-checkbox
+ v-if="isReviewable && showLocalFileReviews"
+ v-gl-tooltip.hover
+ data-testid="fileReviewCheckbox"
+ class="gl-mb-0"
+ :title="$options.i18n.fileReviewTooltip"
+ :checked="reviewed"
+ @change="toggleReview"
+ >
+ <span class="gl-line-height-20">
+ {{ $options.i18n.fileReviewLabel }}
+ </span>
+ </gl-form-checkbox>
<gl-button-group class="gl-pt-0!">
<gl-button
v-if="diffFile.external_url"
@@ -307,6 +366,7 @@ export default {
right
toggle-class="btn-icon js-diff-more-actions"
class="gl-pt-0!"
+ data-qa-selector="dropdown_button"
@show="setMoreActionsShown(true)"
@hidden="setMoreActionsShown(false)"
>
@@ -340,6 +400,7 @@ export default {
ref="ideEditButton"
:href="diffFile.ide_edit_path"
class="js-ide-edit-blob"
+ data-qa-selector="edit_in_ide_button"
>
{{ __('Edit in Web IDE') }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/diffs/components/diff_file_row.vue b/app/assets/javascripts/diffs/components/diff_file_row.vue
index 6c5d9170c9e..89822ba7878 100644
--- a/app/assets/javascripts/diffs/components/diff_file_row.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_row.vue
@@ -3,9 +3,9 @@
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import FileRow from '~/vue_shared/components/file_row.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import FileRow from '~/vue_shared/components/file_row.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import FileRowStats from './file_row_stats.vue';
export default {
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index f62b31734c5..1f3ec7092bc 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -1,7 +1,7 @@
<script>
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import { n__ } from '~/locale';
import { truncate } from '~/lib/utils/text_utility';
+import { n__ } from '~/locale';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants';
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 463b7f5cff4..2f09f2e24b2 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -1,22 +1,20 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
+import { s__ } from '~/locale';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { s__ } from '~/locale';
-import noteForm from '../../notes/components/note_form.vue';
import MultilineCommentForm from '../../notes/components/multiline_comment_form.vue';
-import autosave from '../../notes/mixins/autosave';
-import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
-import { DIFF_NOTE_TYPE, INLINE_DIFF_LINES_KEY, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
import {
commentLineOptions,
formatLineRange,
} from '../../notes/components/multiline_comment_utils';
+import noteForm from '../../notes/components/note_form.vue';
+import autosave from '../../notes/mixins/autosave';
+import { DIFF_NOTE_TYPE, INLINE_DIFF_LINES_KEY, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
export default {
components: {
noteForm,
- userAvatarLink,
MultilineCommentForm,
},
mixins: [autosave, diffLineNoteFormMixin, glFeatureFlagsMixin()],
@@ -167,21 +165,13 @@ export default {
<template>
<div class="content discussion-form discussion-form-container discussion-notes">
- <div v-if="glFeatures.multilineComments" class="gl-mb-3 gl-text-gray-500 gl-pb-3">
+ <div class="gl-mb-3 gl-text-gray-500 gl-pb-3">
<multiline-comment-form
v-model="commentLineStart"
:line="line"
:comment-line-options="commentLineOptions"
/>
</div>
- <user-avatar-link
- v-if="author"
- :link-href="author.path"
- :img-src="author.avatar_url"
- :img-alt="author.name"
- :img-size="40"
- class="d-none d-sm-block"
- />
<note-form
ref="noteForm"
:is-editing="true"
diff --git a/app/assets/javascripts/diffs/components/diff_row.vue b/app/assets/javascripts/diffs/components/diff_row.vue
index db03da966c3..ab6890d66b5 100644
--- a/app/assets/javascripts/diffs/components/diff_row.vue
+++ b/app/assets/javascripts/diffs/components/diff_row.vue
@@ -1,6 +1,8 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
CONTEXT_LINE_CLASS_NAME,
PARALLEL_DIFF_VIEW_TYPE,
@@ -10,7 +12,6 @@ import {
CONFLICT_THEIR,
CONFLICT_MARKER,
} from '../constants';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils';
@@ -99,10 +100,10 @@ export default {
});
},
addCommentTooltipLeft() {
- return utils.addCommentTooltip(this.line.left);
+ return utils.addCommentTooltip(this.line.left, this.glFeatures.dragCommentSelection);
},
addCommentTooltipRight() {
- return utils.addCommentTooltip(this.line.right);
+ return utils.addCommentTooltip(this.line.right, this.glFeatures.dragCommentSelection);
},
emptyCellRightClassMap() {
return { conflict_their: this.line.left?.type === CONFLICT_OUR };
@@ -111,13 +112,7 @@ export default {
return { conflict_our: this.line.right?.type === CONFLICT_THEIR };
},
shouldRenderCommentButton() {
- return (
- this.isLoggedIn &&
- !this.line.isContextLineLeft &&
- !this.line.isMetaLineLeft &&
- !this.line.hasDiscussionsLeft &&
- !this.line.hasDiscussionsRight
- );
+ return this.isLoggedIn && !this.line.isContextLineLeft && !this.line.isMetaLineLeft;
},
isLeftConflictMarker() {
return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type);
@@ -168,7 +163,7 @@ export default {
this.$emit('enterdragging', { ...line, index });
},
onDragStart(line) {
- this.$root.$emit('bv::hide::tooltip');
+ this.$root.$emit(BV_HIDE_TOOLTIP);
this.dragging = true;
this.$emit('startdragging', line);
},
@@ -199,7 +194,7 @@ export default {
>
<template v-if="!isLeftConflictMarker">
<span
- v-if="shouldRenderCommentButton"
+ v-if="shouldRenderCommentButton && !line.hasDiscussionsLeft"
v-gl-tooltip
data-testid="leftCommentButton"
class="add-diff-note tooltip-wrapper"
@@ -301,7 +296,7 @@ export default {
<div :class="classNameMapCellRight" class="diff-td diff-line-num new_line">
<template v-if="line.right.type !== $options.CONFLICT_MARKER_THEIR">
<span
- v-if="shouldRenderCommentButton"
+ v-if="shouldRenderCommentButton && !line.hasDiscussionsRight"
v-gl-tooltip
data-testid="rightCommentButton"
class="add-diff-note tooltip-wrapper"
diff --git a/app/assets/javascripts/diffs/components/diff_row_utils.js b/app/assets/javascripts/diffs/components/diff_row_utils.js
index 7606c39ad37..cd45474afcd 100644
--- a/app/assets/javascripts/diffs/components/diff_row_utils.js
+++ b/app/assets/javascripts/diffs/components/diff_row_utils.js
@@ -50,11 +50,11 @@ export const classNameMapCell = ({ line, hll, isLoggedIn, isHover }) => {
];
};
-export const addCommentTooltip = (line) => {
+export const addCommentTooltip = (line, dragCommentSelectionEnabled = false) => {
let tooltip;
if (!line) return tooltip;
- tooltip = gon.drag_comment_selection
+ tooltip = dragCommentSelectionEnabled
? __('Add a comment to this line or drag for multiple lines')
: __('Add a comment to this line');
const brokenSymlinks = line.commentsDisabled;
diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue
index f229fc4cf60..0303700f42a 100644
--- a/app/assets/javascripts/diffs/components/diff_stats.vue
+++ b/app/assets/javascripts/diffs/components/diff_stats.vue
@@ -1,6 +1,6 @@
<script>
-import { isNumber } from 'lodash';
import { GlIcon } from '@gitlab/ui';
+import { isNumber } from 'lodash';
import { n__ } from '~/locale';
export default {
diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue
index 79800f835f4..43cfa22073f 100644
--- a/app/assets/javascripts/diffs/components/diff_view.vue
+++ b/app/assets/javascripts/diffs/components/diff_view.vue
@@ -1,11 +1,11 @@
<script>
import { mapGetters, mapState, mapActions } from 'vuex';
-import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DraftNote from '~/batch_comments/components/draft_note.vue';
-import DiffRow from './diff_row.vue';
+import draftCommentsMixin from '~/diffs/mixins/draft_comments';
+import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
import DiffCommentCell from './diff_comment_cell.vue';
import DiffExpansionCell from './diff_expansion_cell.vue';
-import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
+import DiffRow from './diff_row.vue';
export default {
components: {
@@ -61,10 +61,10 @@ export default {
...mapActions(['setSelectedCommentPosition']),
...mapActions('diffs', ['showCommentForm']),
showCommentLeft(line) {
- return !this.inline || line.left;
+ return line.left && !line.right;
},
showCommentRight(line) {
- return !this.inline || (line.right && !line.left);
+ return line.right && !line.left;
},
onStartDragging(line) {
this.dragStart = line;
@@ -138,24 +138,30 @@ export default {
:class="line.commentRowClasses"
class="diff-grid-comments diff-tr notes_holder"
>
- <div v-if="showCommentLeft(line)" class="diff-td notes-content parallel old">
+ <div
+ v-if="line.left || !inline"
+ :class="{ parallel: !inline }"
+ class="diff-td notes-content old"
+ >
<diff-comment-cell
- v-if="line.left"
+ v-if="line.left && (line.left.renderDiscussion || line.left.hasCommentForm)"
:line="line.left"
:diff-file-hash="diffFile.file_hash"
:help-page-path="helpPagePath"
- :has-draft="line.left.hasDraft"
line-position="left"
/>
</div>
- <div v-if="showCommentRight(line)" class="diff-td notes-content parallel new">
+ <div
+ v-if="line.right || !inline"
+ :class="{ parallel: !inline }"
+ class="diff-td notes-content new"
+ >
<diff-comment-cell
- v-if="line.right"
+ v-if="line.right && (line.right.renderDiscussion || line.right.hasCommentForm)"
:line="line.right"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
- :has-draft="line.right.hasDraft"
line-position="right"
/>
</div>
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index 6a1e0d8cbd6..3d05202fb2d 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -1,8 +1,8 @@
<script>
-import { mapActions, mapGetters } from 'vuex';
+import { GlIcon } from '@gitlab/ui';
import { isArray } from 'lodash';
+import { mapActions, mapGetters } from 'vuex';
import imageDiffMixin from 'ee_else_ce/diffs/mixins/image_diff';
-import { GlIcon } from '@gitlab/ui';
function calcPercent(pos, size, renderedSize) {
return (((pos / size) * 100) / ((renderedSize / size) * 100)) * 100;
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index 014b1ebe54b..154f2fdcfc7 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -1,6 +1,6 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { CONTEXT_LINE_CLASS_NAME } from '../constants';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import {
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index 28485a2fdac..e407609d9e9 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -1,12 +1,12 @@
<script>
import { mapGetters, mapState } from 'vuex';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DraftNote from '~/batch_comments/components/draft_note.vue';
-import inlineDiffTableRow from './inline_diff_table_row.vue';
+import draftCommentsMixin from '~/diffs/mixins/draft_comments';
+import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import DiffCommentCell from './diff_comment_cell.vue';
import DiffExpansionCell from './diff_expansion_cell.vue';
-import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
+import inlineDiffTableRow from './inline_diff_table_row.vue';
export default {
components: {
diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue
index e0fdbf6ac3a..ab518fcfb16 100644
--- a/app/assets/javascripts/diffs/components/no_changes.vue
+++ b/app/assets/javascripts/diffs/components/no_changes.vue
@@ -1,6 +1,6 @@
<script>
-import { mapGetters } from 'vuex';
import { GlButton, GlSprintf } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
export default {
components: {
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index 47eecef2385..3d20dfd0c9b 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -1,7 +1,7 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
-import $ from 'jquery';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import $ from 'jquery';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils';
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 21e0bf18dbf..b167081a379 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -1,11 +1,11 @@
<script>
import { mapGetters, mapState } from 'vuex';
-import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DraftNote from '~/batch_comments/components/draft_note.vue';
-import parallelDiffTableRow from './parallel_diff_table_row.vue';
+import draftCommentsMixin from '~/diffs/mixins/draft_comments';
+import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
import DiffCommentCell from './diff_comment_cell.vue';
import DiffExpansionCell from './diff_expansion_cell.vue';
-import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
+import parallelDiffTableRow from './parallel_diff_table_row.vue';
export default {
components: {
diff --git a/app/assets/javascripts/diffs/components/settings_dropdown.vue b/app/assets/javascripts/diffs/components/settings_dropdown.vue
index 2fe2fd6b3d8..7d74e81257a 100644
--- a/app/assets/javascripts/diffs/components/settings_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/settings_dropdown.vue
@@ -1,9 +1,9 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButtonGroup, GlButton, GlDropdown, GlFormCheckbox } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
-import eventHub from '../event_hub';
import { EVT_VIEW_FILE_BY_FILE } from '../constants';
+import eventHub from '../event_hub';
import { SETTINGS_DROPDOWN } from '../i18n';
export default {
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 1a258695fa0..39ce849fc03 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,6 +1,6 @@
<script>
-import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
+import { mapActions, mapGetters, mapState } from 'vuex';
import { s__, sprintf } from '~/locale';
import FileTree from '~/vue_shared/components/file_tree.vue';
import DiffFileRow from './diff_file_row.vue';
diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js
index c4ac99ead91..2a061876937 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -1,13 +1,16 @@
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
export const GENERIC_ERROR = __('Something went wrong on our end. Please try again!');
export const DIFF_FILE_HEADER = {
optionsDropdownTitle: __('Options'),
+ fileReviewLabel: __('Viewed'),
+ fileReviewTooltip: __('Collapses this file (only for you) until it’s changed again.'),
};
export const DIFF_FILE = {
- blobView: __('You can %{linkStart}view the blob%{linkEnd} instead.'),
+ tooLarge: s__('MRDiffFile|Changes are too large to be shown.'),
+ blobView: s__('MRDiffFile|View file @ %{commitSha}'),
editInFork: __(
"You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
),
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 4e0720c645a..68fe204d955 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -1,15 +1,14 @@
+import Cookies from 'js-cookie';
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
-import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import eventHub from '../notes/event_hub';
import diffsApp from './components/app.vue';
-import { getDerivedMergeRequestInformation } from './utils/merge_request';
-import { getReviewsForMergeRequest } from './utils/file_reviews';
-
import { TREE_LIST_STORAGE_KEY, DIFF_WHITESPACE_COOKIE_NAME } from './constants';
+import { getReviewsForMergeRequest } from './utils/file_reviews';
+import { getDerivedMergeRequestInformation } from './utils/merge_request';
export default function initDiffsApp(store) {
const fileFinderEl = document.getElementById('js-diff-file-finder');
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index e95e9ac3ee4..4b2dc2d45df 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -1,25 +1,14 @@
-import Vue from 'vue';
import Cookies from 'js-cookie';
-import Poll from '~/lib/utils/poll';
-import axios from '~/lib/utils/axios_utils';
-import httpStatusCodes from '~/lib/utils/http_status';
+import Vue from 'vue';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { __, s__ } from '~/locale';
+import { diffViewerModes } from '~/ide/constants';
+import axios from '~/lib/utils/axios_utils';
import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
+import httpStatusCodes from '~/lib/utils/http_status';
+import Poll from '~/lib/utils/poll';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
-import TreeWorker from '../workers/tree_worker';
+import { __, s__ } from '~/locale';
import notesEventHub from '../../notes/event_hub';
-import eventHub from '../event_hub';
-import {
- getDiffPositionByLineCode,
- getNoteFormData,
- convertExpandLines,
- idleCallback,
- allDiscussionWrappersExpanded,
- prepareDiffData,
- prepareLineForRenamedFile,
-} from './utils';
-import * as types from './mutation_types';
import {
PARALLEL_DIFF_VIEW_TYPE,
INLINE_DIFF_VIEW_TYPE,
@@ -48,10 +37,21 @@ import {
DIFF_VIEW_ALL_FILES,
DIFF_FILE_BY_FILE_COOKIE_NAME,
} from '../constants';
-import { diffViewerModes } from '~/ide/constants';
+import eventHub from '../event_hub';
import { isCollapsed } from '../utils/diff_file';
-import { getDerivedMergeRequestInformation } from '../utils/merge_request';
import { markFileReview, setReviewsForMergeRequest } from '../utils/file_reviews';
+import { getDerivedMergeRequestInformation } from '../utils/merge_request';
+import TreeWorker from '../workers/tree_worker';
+import * as types from './mutation_types';
+import {
+ getDiffPositionByLineCode,
+ getNoteFormData,
+ convertExpandLines,
+ idleCallback,
+ allDiscussionWrappersExpanded,
+ prepareDiffData,
+ prepareLineForRenamedFile,
+} from './utils';
export const setBaseConfig = ({ commit }, options) => {
const {
@@ -749,12 +749,10 @@ export const setFileByFile = ({ commit }, { fileByFile }) => {
);
};
-export function reviewFile({ commit, state, getters }, { file, reviewed = true }) {
+export function reviewFile({ commit, state }, { file, reviewed = true }) {
const { mrPath } = getDerivedMergeRequestInformation({ endpoint: file.load_collapsed_diff_url });
- const reviews = setReviewsForMergeRequest(
- mrPath,
- markFileReview(getters.fileReviews(state), file, reviewed),
- );
+ const reviews = markFileReview(state.mrReviews, file, reviewed);
+ setReviewsForMergeRequest(mrPath, reviews);
commit(types.SET_MR_FILE_REVIEWS, reviews);
}
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js
index a167b6d4694..1fc2a684e95 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -1,11 +1,11 @@
import { __, n__ } from '~/locale';
-import { parallelizeDiffLines } from './utils';
-import { isFileReviewed } from '../utils/file_reviews';
import {
PARALLEL_DIFF_VIEW_TYPE,
INLINE_DIFF_VIEW_TYPE,
INLINE_DIFF_LINES_KEY,
} from '../constants';
+import { computeSuggestionCommitMessage } from '../utils/suggestions';
+import { parallelizeDiffLines } from './utils';
export * from './getters_versions_dropdowns';
@@ -156,6 +156,17 @@ export const diffLines = (state) => (file, unifiedDiffComponents) => {
);
};
-export function fileReviews(state) {
- return state.diffFiles.map((file) => isFileReviewed(state.mrReviews, file));
+export function suggestionCommitMessage(state) {
+ return (values = {}) =>
+ computeSuggestionCommitMessage({
+ message: state.defaultSuggestionCommitMessage,
+ values: {
+ branch_name: state.branchName,
+ project_path: state.projectPath,
+ project_name: state.projectName,
+ username: state.username,
+ user_full_name: state.userFullName,
+ ...values,
+ },
+ });
}
diff --git a/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js b/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js
index 3f33b0c900e..01811e60caa 100644
--- a/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js
+++ b/app/assets/javascripts/diffs/store/getters_versions_dropdowns.js
@@ -1,5 +1,5 @@
-import { __, n__, sprintf } from '~/locale';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
+import { __, n__, sprintf } from '~/locale';
import { DIFF_COMPARE_BASE_VERSION_INDEX, DIFF_COMPARE_HEAD_VERSION_INDEX } from '../constants';
export const selectedTargetIndex = (state) =>
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index aa89c74cef0..f93435363ec 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -47,4 +47,5 @@ export default () => ({
showSuggestPopover: true,
defaultSuggestionCommitMessage: '',
mrReviews: {},
+ latestDiff: true,
});
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 06f0f2c3dfb..d06793c05af 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -1,6 +1,12 @@
import Vue from 'vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import {
+ DIFF_FILE_MANUAL_COLLAPSE,
+ DIFF_FILE_AUTOMATIC_COLLAPSE,
+ INLINE_DIFF_LINES_KEY,
+} from '../constants';
+import * as types from './mutation_types';
+import {
findDiffFile,
addLineReferences,
removeMatchLine,
@@ -9,12 +15,6 @@ import {
isDiscussionApplicableToLine,
updateLineInFile,
} from './utils';
-import {
- DIFF_FILE_MANUAL_COLLAPSE,
- DIFF_FILE_AUTOMATIC_COLLAPSE,
- INLINE_DIFF_LINES_KEY,
-} from '../constants';
-import * as types from './mutation_types';
function updateDiffFilesInState(state, files) {
return Object.assign(state, { diffFiles: files });
@@ -159,7 +159,12 @@ export default {
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) {
const { latestDiff } = state;
- const discussionLineCodes = [discussion.line_code, ...(discussion.line_codes || [])];
+ const originalStartLineCode = discussion.original_position?.line_range?.start?.line_code;
+ const discussionLineCodes = [
+ discussion.line_code,
+ originalStartLineCode,
+ ...(discussion.line_codes || []),
+ ];
const fileHash = discussion.diff_file.file_hash;
const lineCheck = (line) =>
discussionLineCodes.some(
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index c52da558be2..87b4f33c216 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -1,6 +1,6 @@
import { property, isEqual } from 'lodash';
-import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import { diffModes, diffViewerModes } from '~/ide/constants';
+import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
import {
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
diff --git a/app/assets/javascripts/diffs/utils/diff_file.js b/app/assets/javascripts/diffs/utils/diff_file.js
index ce0398e75fc..7e6fde320d2 100644
--- a/app/assets/javascripts/diffs/utils/diff_file.js
+++ b/app/assets/javascripts/diffs/utils/diff_file.js
@@ -1,3 +1,5 @@
+import { truncateSha } from '~/lib/utils/text_utility';
+
import {
DIFF_FILE_SYMLINK_MODE,
DIFF_FILE_DELETED_MODE,
@@ -78,3 +80,7 @@ export function isCollapsed(file) {
return collapsedStates[type];
}
+
+export function getShortShaFromFile(file) {
+ return file.content_sha ? truncateSha(String(file.content_sha)) : null;
+}
diff --git a/app/assets/javascripts/diffs/utils/file_reviews.js b/app/assets/javascripts/diffs/utils/file_reviews.js
index 0047955643a..5fafc1714ae 100644
--- a/app/assets/javascripts/diffs/utils/file_reviews.js
+++ b/app/assets/javascripts/diffs/utils/file_reviews.js
@@ -2,6 +2,16 @@ function getFileReviewsKey(mrPath) {
return `${mrPath}-file-reviews`;
}
+export function isFileReviewed(reviews, file) {
+ const fileReviews = reviews[file.file_identifier_hash];
+
+ return file?.id && fileReviews?.length ? new Set(fileReviews).has(file.id) : false;
+}
+
+export function reviewStatuses(files, reviews) {
+ return files.map((file) => isFileReviewed(reviews, file));
+}
+
export function getReviewsForMergeRequest(mrPath) {
const reviewsForMr = localStorage.getItem(getFileReviewsKey(mrPath));
let reviews = {};
@@ -23,23 +33,17 @@ export function setReviewsForMergeRequest(mrPath, reviews) {
return reviews;
}
-export function isFileReviewed(reviews, file) {
- const fileReviews = reviews[file.file_identifier_hash];
-
- return file?.id && fileReviews?.length ? new Set(fileReviews).has(file.id) : false;
-}
-
export function reviewable(file) {
return Boolean(file.id) && Boolean(file.file_identifier_hash);
}
export function markFileReview(reviews, file, reviewed = true) {
const usableReviews = { ...(reviews || {}) };
- let updatedReviews = usableReviews;
+ const updatedReviews = usableReviews;
let fileReviews;
if (reviewable(file)) {
- fileReviews = new Set([...(usableReviews[file.file_identifier_hash] || [])]);
+ fileReviews = new Set(usableReviews[file.file_identifier_hash] || []);
if (reviewed) {
fileReviews.add(file.id);
@@ -47,10 +51,7 @@ export function markFileReview(reviews, file, reviewed = true) {
fileReviews.delete(file.id);
}
- updatedReviews = {
- ...usableReviews,
- [file.file_identifier_hash]: Array.from(fileReviews),
- };
+ updatedReviews[file.file_identifier_hash] = Array.from(fileReviews);
if (updatedReviews[file.file_identifier_hash].length === 0) {
delete updatedReviews[file.file_identifier_hash];
diff --git a/app/assets/javascripts/diffs/utils/performance.js b/app/assets/javascripts/diffs/utils/performance.js
index dcde6f4ecc4..50bf17001a6 100644
--- a/app/assets/javascripts/diffs/utils/performance.js
+++ b/app/assets/javascripts/diffs/utils/performance.js
@@ -9,7 +9,6 @@ import {
MR_DIFFS_MEASURE_DIFF_FILES_DONE,
} from '../../performance/constants';
-import eventHub from '../event_hub';
import {
EVT_PERF_MARK_FILE_TREE_START,
EVT_PERF_MARK_FILE_TREE_END,
@@ -17,6 +16,7 @@ import {
EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN,
EVT_PERF_MARK_DIFF_FILES_END,
} from '../constants';
+import eventHub from '../event_hub';
function treeStart() {
performanceMarkAndMeasure({
diff --git a/app/assets/javascripts/diffs/utils/suggestions.js b/app/assets/javascripts/diffs/utils/suggestions.js
new file mode 100644
index 00000000000..a272f7f3257
--- /dev/null
+++ b/app/assets/javascripts/diffs/utils/suggestions.js
@@ -0,0 +1,28 @@
+function removeEmptyProperties(dict) {
+ const noBlanks = Object.entries(dict).reduce((final, [key, value]) => {
+ const upd = { ...final };
+
+ // The number 0 shouldn't be falsey when we're printing variables
+ if (value || value === 0) {
+ upd[key] = value;
+ }
+
+ return upd;
+ }, {});
+
+ return noBlanks;
+}
+
+export function computeSuggestionCommitMessage({ message, values = {} } = {}) {
+ const noEmpties = removeEmptyProperties(values);
+ const matchPhrases = Object.keys(noEmpties)
+ .map((key) => `%{${key}}`)
+ .join('|');
+ const replacementExpression = new RegExp(`(${matchPhrases})`, 'gm');
+
+ return message.replace(replacementExpression, (match) => {
+ const key = match.replace(/(^%{|}$)/gm, '');
+
+ return noEmpties[key];
+ });
+}
diff --git a/app/assets/javascripts/diffs/utils/uuids.js b/app/assets/javascripts/diffs/utils/uuids.js
index 1fe5f9f6499..98fe4bf9664 100644
--- a/app/assets/javascripts/diffs/utils/uuids.js
+++ b/app/assets/javascripts/diffs/utils/uuids.js
@@ -12,8 +12,8 @@
*/
import { MersenneTwister } from 'fast-mersenne-twister';
-import stringHash from 'string-hash';
import { isString } from 'lodash';
+import stringHash from 'string-hash';
import { v4 } from 'uuid';
function getSeed(seeds) {