diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-25 09:09:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-25 09:09:10 +0000 |
commit | b9c00e60c46522bdafd67c4680cf8bfd18269959 (patch) | |
tree | 7d417c7ab1482fe81f1436f30dba280b05182df7 /app | |
parent | 7f15f475306e60325cc9e86977f38680987e5c38 (diff) | |
download | gitlab-ce-b9c00e60c46522bdafd67c4680cf8bfd18269959.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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) |