summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-25 09:09:10 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-25 09:09:10 +0000
commitb9c00e60c46522bdafd67c4680cf8bfd18269959 (patch)
tree7d417c7ab1482fe81f1436f30dba280b05182df7 /app
parent7f15f475306e60325cc9e86977f38680987e5c38 (diff)
downloadgitlab-ce-b9c00e60c46522bdafd67c4680cf8bfd18269959.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/diffs/components/app.vue11
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue14
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue64
-rw-r--r--app/assets/javascripts/diffs/components/diff_view.vue12
-rw-r--r--app/assets/javascripts/diffs/i18n.js2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js8
-rw-r--r--app/assets/javascripts/diffs/store/getters.js5
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js1
-rw-r--r--app/assets/javascripts/diffs/utils/file_reviews.js25
-rw-r--r--app/assets/javascripts/editor/constants.js2
-rw-r--r--app/assets/javascripts/editor/editor_lite.js77
-rw-r--r--app/assets/javascripts/pipeline_editor/components/text_editor.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/editor_lite.vue5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
14 files changed, 160 insertions, 71 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 32822fe1fe8..2f20277c6ae 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -26,6 +26,7 @@ import CollapsedFilesWarning from './collapsed_files_warning.vue';
import { diffsApp } from '../utils/performance';
import { fileByFile } from '../utils/preferences';
+import { reviewStatuses } from '../utils/file_reviews';
import {
TREE_LIST_WIDTH_STORAGE_KEY,
@@ -169,12 +170,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 +228,9 @@ export default {
return visible;
},
+ fileReviews() {
+ return reviewStatuses(this.diffFiles, this.mrReviews);
+ },
},
watch: {
commit(newCommit, oldCommit) {
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index e613b684345..f5a3d69843a 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -10,7 +10,9 @@ 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 {
DIFF_FILE_AUTOMATIC_COLLAPSE,
DIFF_FILE_MANUAL_COLLAPSE,
@@ -144,6 +146,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 +189,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 +285,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"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index f4eb7a6cd2f..03e69bf9e8e 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -1,6 +1,6 @@
<script>
import { escape } from 'lodash';
-import { mapActions, mapGetters } from 'vuex';
+import { mapActions, mapGetters, mapState } from 'vuex';
import {
GlTooltipDirective,
GlSafeHtmlDirective,
@@ -10,16 +10,23 @@ import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
+ GlFormCheckbox,
GlLoadingIcon,
} from '@gitlab/ui';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
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 { diffViewerModes } from '~/ide/constants';
import DiffStats from './diff_stats.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
-import { isCollapsed } from '../utils/diff_file';
+
+import { collapsedType, isCollapsed } from '../utils/diff_file';
+import { reviewable } from '../utils/file_reviews';
+
+import { diffViewerModes } from '~/ide/constants';
+import { DIFF_FILE_AUTOMATIC_COLLAPSE } from '../constants';
+
import { DIFF_FILE_HEADER } from '../i18n';
export default {
@@ -33,12 +40,14 @@ export default {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
+ GlFormCheckbox,
GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
},
+ mixins: [glFeatureFlagsMixin()],
i18n: {
...DIFF_FILE_HEADER,
},
@@ -76,6 +85,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 +102,7 @@ export default {
};
},
computed: {
+ ...mapState('diffs', ['latestDiff']),
...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
diffContentIDSelector() {
return `#diff-content-${this.diffFile.file_hash}`;
@@ -170,6 +190,9 @@ export default {
(this.diffFile.edit_path || this.diffFile.ide_edit_path)
);
},
+ isReviewable() {
+ return reviewable(this.diffFile);
+ },
},
methods: {
...mapActions('diffs', [
@@ -177,6 +200,8 @@ export default {
'toggleFileDiscussionWrappers',
'toggleFullDiff',
'toggleActiveFileByHash',
+ 'reviewFile',
+ 'setFileCollapsedByUser',
]),
handleToggleFile() {
this.$emit('toggleFile');
@@ -204,6 +229,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>
@@ -291,6 +336,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"
diff --git a/app/assets/javascripts/diffs/components/diff_view.vue b/app/assets/javascripts/diffs/components/diff_view.vue
index 79800f835f4..e28d855ff45 100644
--- a/app/assets/javascripts/diffs/components/diff_view.vue
+++ b/app/assets/javascripts/diffs/components/diff_view.vue
@@ -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,20 @@ 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" :class="{ parallel: !inline }" class="diff-td notes-content old">
<diff-comment-cell
- v-if="line.left"
: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" :class="{ parallel: !inline }" class="diff-td notes-content new">
<diff-comment-cell
- v-if="line.right"
: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/i18n.js b/app/assets/javascripts/diffs/i18n.js
index c4ac99ead91..0e21d5042c2 100644
--- a/app/assets/javascripts/diffs/i18n.js
+++ b/app/assets/javascripts/diffs/i18n.js
@@ -4,6 +4,8 @@ export const GENERIC_ERROR = __('Something went wrong on our end. Please try aga
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 = {
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index e95e9ac3ee4..78b76f0cfa4 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -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..f9a466f1899 100644
--- a/app/assets/javascripts/diffs/store/getters.js
+++ b/app/assets/javascripts/diffs/store/getters.js
@@ -1,6 +1,5 @@
import { __, n__ } from '~/locale';
import { parallelizeDiffLines } from './utils';
-import { isFileReviewed } from '../utils/file_reviews';
import {
PARALLEL_DIFF_VIEW_TYPE,
INLINE_DIFF_VIEW_TYPE,
@@ -155,7 +154,3 @@ export const diffLines = (state) => (file, unifiedDiffComponents) => {
state.diffViewType === INLINE_DIFF_VIEW_TYPE,
);
};
-
-export function fileReviews(state) {
- return state.diffFiles.map((file) => isFileReviewed(state.mrReviews, file));
-}
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/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/editor/constants.js b/app/assets/javascripts/editor/constants.js
index 8d1a3d17c6e..9981c7f2ca2 100644
--- a/app/assets/javascripts/editor/constants.js
+++ b/app/assets/javascripts/editor/constants.js
@@ -11,6 +11,8 @@ export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __(
'Editor Lite instance is required to set up an extension.',
);
+export const EDITOR_READY_EVENT = 'editor-ready';
+
//
// EXTENSIONS' CONSTANTS
//
diff --git a/app/assets/javascripts/editor/editor_lite.js b/app/assets/javascripts/editor/editor_lite.js
index 1808f968b8c..a68b44f8204 100644
--- a/app/assets/javascripts/editor/editor_lite.js
+++ b/app/assets/javascripts/editor/editor_lite.js
@@ -5,7 +5,7 @@ import { defaultEditorOptions } from '~/ide/lib/editor_options';
import { registerLanguages } from '~/ide/utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { clearDomElement } from './utils';
-import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants';
+import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX, EDITOR_READY_EVENT } from './constants';
import { uuids } from '~/diffs/utils/uuids';
export default class EditorLite {
@@ -73,6 +73,48 @@ export default class EditorLite {
});
}
+ static prepareInstance(el) {
+ if (!el) {
+ throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
+ }
+
+ clearDomElement(el);
+
+ monacoEditor.onDidCreateEditor(() => {
+ delete el.dataset.editorLoading;
+ });
+ }
+
+ static manageDefaultExtensions(instance, el, extensions) {
+ EditorLite.loadExtensions(extensions, instance)
+ .then((modules) => {
+ if (modules) {
+ modules.forEach((module) => {
+ instance.use(module.default);
+ });
+ }
+ })
+ .then(() => {
+ el.dispatchEvent(new Event(EDITOR_READY_EVENT));
+ })
+ .catch((e) => {
+ throw e;
+ });
+ }
+
+ static createEditorModel({ blobPath, blobContent, blobGlobalId, instance } = {}) {
+ let model = null;
+ if (!instance) {
+ return null;
+ }
+ const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
+ const uri = Uri.file(uriFilePath);
+ const existingModel = monacoEditor.getModel(uri);
+ model = existingModel || monacoEditor.createModel(blobContent, undefined, uri);
+ instance.setModel(model);
+ return model;
+ }
+
/**
* Creates a monaco instance with the given options.
*
@@ -90,25 +132,15 @@ export default class EditorLite {
extensions = [],
...instanceOptions
} = {}) {
- if (!el) {
- throw new Error(EDITOR_LITE_INSTANCE_ERROR_NO_EL);
- }
-
- clearDomElement(el);
-
- const uriFilePath = joinPaths(URI_PREFIX, blobGlobalId, blobPath);
-
- const model = monacoEditor.createModel(blobContent, undefined, Uri.file(uriFilePath));
-
- monacoEditor.onDidCreateEditor(() => {
- delete el.dataset.editorLoading;
- });
+ EditorLite.prepareInstance(el);
const instance = monacoEditor.create(el, {
...this.options,
...instanceOptions,
});
- instance.setModel(model);
+
+ const model = EditorLite.createEditorModel({ blobGlobalId, blobPath, blobContent, instance });
+
instance.onDidDispose(() => {
const index = this.instances.findIndex((inst) => inst === instance);
this.instances.splice(index, 1);
@@ -117,20 +149,7 @@ export default class EditorLite {
instance.updateModelLanguage = (path) => EditorLite.updateModelLanguage(path, instance);
instance.use = (args) => this.use(args, instance);
- EditorLite.loadExtensions(extensions, instance)
- .then((modules) => {
- if (modules) {
- modules.forEach((module) => {
- instance.use(module.default);
- });
- }
- })
- .then(() => {
- el.dispatchEvent(new Event('editor-ready'));
- })
- .catch((e) => {
- throw e;
- });
+ EditorLite.manageDefaultExtensions(instance, el, extensions);
this.instances.push(instance);
return instance;
diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
index b8d49d77ea9..352b8a0c061 100644
--- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue
+++ b/app/assets/javascripts/pipeline_editor/components/text_editor.vue
@@ -1,6 +1,7 @@
<script>
import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
+import { EDITOR_READY_EVENT } from '~/editor/constants';
export default {
components: {
@@ -31,6 +32,7 @@ export default {
});
},
},
+ readyEvent: EDITOR_READY_EVENT,
};
</script>
<template>
@@ -39,7 +41,7 @@ export default {
ref="editor"
:file-name="ciConfigPath"
v-bind="$attrs"
- @editor-ready="onEditorReady"
+ @[$options.readyEvent]="onEditorReady"
v-on="$listeners"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/editor_lite.vue b/app/assets/javascripts/vue_shared/components/editor_lite.vue
index 7218b84cf8a..b5b5330973c 100644
--- a/app/assets/javascripts/vue_shared/components/editor_lite.vue
+++ b/app/assets/javascripts/vue_shared/components/editor_lite.vue
@@ -1,7 +1,7 @@
<script>
import { debounce } from 'lodash';
import Editor from '~/editor/editor_lite';
-import { CONTENT_UPDATE_DEBOUNCE } from '~/editor/constants';
+import { CONTENT_UPDATE_DEBOUNCE, EDITOR_READY_EVENT } from '~/editor/constants';
function initEditorLite({ el, ...args }) {
const editor = new Editor({
@@ -88,6 +88,7 @@ export default {
return this.editor;
},
},
+ readyEvent: EDITOR_READY_EVENT,
};
</script>
<template>
@@ -95,7 +96,7 @@ export default {
:id="`editor-lite-${fileGlobalId}`"
ref="editor"
data-editor-loading
- @editor-ready="$emit('editor-ready')"
+ @[$options.readyEvent]="$emit($options.readyEvent)"
>
<pre class="editor-loading-content">{{ value }}</pre>
</div>
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f8647881645..6e4c760b524 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true)
push_frontend_feature_flag(:codequality_mr_diff, @project)
push_frontend_feature_flag(:suggestions_custom_commit, @project)
+ push_frontend_feature_flag(:local_file_reviews, default_enabled: :yaml)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)