diff --git a/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue b/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue index 2ebde10c229..74917da6426 100644 --- a/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue +++ b/app/assets/javascripts/batch_comments/components/diff_file_drafts.vue @@ -15,11 +15,23 @@ export default { type: String, required: true, }, + showPin: { + type: Boolean, + required: false, + default: true, + }, + positionType: { + type: String, + required: false, + default: '', + }, }, computed: { ...mapGetters('batchComments', ['draftsForFile']), drafts() { - return this.draftsForFile(this.fileHash); + return this.draftsForFile(this.fileHash).filter( + (f) => f.position?.position_type === this.positionType, + ); }, }, }; @@ -34,6 +46,7 @@ export default { >
.then((data) => commit(types.SET_BATCH_COMMENTS_DRAFTS, data)) .then(() => { state.drafts.forEach((draft) => { - if (!draft.line_code) { + if (draft.position?.position_type === FILE_DIFF_POSITION_TYPE) { + dispatch('diffs/addDraftToFile', { filePath: draft.file_path, draft }, { root: true }); + } else if (!draft.line_code) { dispatch('convertToDiscussion', draft.discussion_id, { root: true }); } }); diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index 676944ee86a..4d02fd80ba8 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -75,7 +75,10 @@ export default { return this.getCommentFormForDiffFile(this.diffFileHash); }, showNotesContainer() { - return this.imageDiscussions.length || this.diffFileCommentForm; + return ( + this.diffViewerMode === diffViewerModes.image && + (this.imageDiscussionsWithDrafts.length || this.diffFileCommentForm) + ); }, diffFileHash() { return this.diffFile.file_hash; @@ -87,6 +90,11 @@ export default { // TODO: Do this data generation when we receive a response to save a computed property being created return this.diffLines(this.diffFile).map(mapParallel(this)) || []; }, + imageDiscussions() { + return this.diffFile.discussions.filter( + (f) => f.position?.position_type === IMAGE_DIFF_POSITION_TYPE, + ); + }, }, updated() { this.$nextTick(() => { @@ -111,6 +119,7 @@ export default { }); }, }, + IMAGE_DIFF_POSITION_TYPE, }; @@ -181,13 +190,17 @@ export default { class="d-none d-sm-block new-comment" /> - + vm.file.file_hash })], + mixins: [ + glFeatureFlagsMixin(), + IdState({ idProp: (vm) => vm.file.file_hash }), + diffLineNoteFormMixin, + ], props: { file: { type: Object, @@ -101,7 +113,7 @@ export default { 'conflictResolutionPath', 'canMerge', ]), - ...mapGetters(['isNotesFetched']), + ...mapGetters(['isNotesFetched', 'getNoteableData', 'noteableType']), ...mapGetters('diffs', ['getDiffFileDiscussions', 'isVirtualScrollingEnabled']), viewBlobHref() { return escape(this.file.view_path); @@ -175,6 +187,18 @@ export default { return this.file.viewer?.manuallyCollapsed; }, + fileDiscussions() { + return this.file.discussions.filter( + (f) => f.position?.position_type === FILE_DIFF_POSITION_TYPE, + ); + }, + showFileDiscussions() { + return ( + this.glFeatures.commentOnFiles && + !this.file.viewer?.manuallyCollapsed && + (this.fileDiscussions.length || this.file.drafts.length || this.file.hasCommentForm) + ); + }, }, watch: { 'file.id': { @@ -227,6 +251,8 @@ export default { 'assignDiscussionsToDiff', 'setRenderIt', 'setFileCollapsedByUser', + 'saveDiffDiscussion', + 'toggleFileCommentForm', ]), manageViewedEffects() { if ( @@ -316,8 +342,20 @@ export default { hideForkMessage() { this.idState.forkMessageVisible = false; }, + handleSaveNote(note) { + this.saveDiffDiscussion({ + note, + formData: { + noteableData: this.getNoteableData, + noteableType: this.noteableType, + diffFile: this.file, + positionType: FILE_DIFF_POSITION_TYPE, + }, + }); + }, }, CONFLICT_TEXT, + FILE_DIFF_POSITION_TYPE, }; @@ -422,6 +460,35 @@ export default { +
+
+ + + +
+
{{ $options.i18n.fileReviewLabel }} + f.position?.position_type === IMAGE_DIFF_POSITION_TYPE) + .concat(this.draftsForFile(this.diffFile.file_hash)); }, }, }; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 3d27bb180e6..029be6ebad9 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -50,6 +50,7 @@ import { TRACKING_SINGLE_FILE_MODE, TRACKING_MULTIPLE_FILES_MODE, EVT_MR_PREPARED, + FILE_DIFF_POSITION_TYPE, } from '../constants'; import { DISCUSSION_SINGLE_DIFF_FAILED, LOAD_SINGLE_DIFF_FAILED } from '../i18n'; import eventHub from '../event_hub'; @@ -614,6 +615,11 @@ export const saveDiffDiscussion = async ({ state, dispatch }, { note, formData } .then((discussion) => dispatch('assignDiscussionsToDiff', [discussion])) .then(() => dispatch('updateResolvableDiscussionsCounts', null, { root: true })) .then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.file_hash)) + .then(() => { + if (formData.positionType === FILE_DIFF_POSITION_TYPE) { + dispatch('toggleFileCommentForm', formData.diffFile.file_path); + } + }) .catch(() => createAlert({ message: s__('MergeRequests|Saving the comment failed'), @@ -1012,3 +1018,9 @@ export function reviewFile({ commit, state }, { file, reviewed = true }) { } export const disableVirtualScroller = ({ commit }) => commit(types.DISABLE_VIRTUAL_SCROLLING); + +export const toggleFileCommentForm = ({ commit }, filePath) => + commit(types.TOGGLE_FILE_COMMENT_FORM, filePath); + +export const addDraftToFile = ({ commit }, { filePath, draft }) => + commit(types.ADD_DRAFT_TO_FILE, { filePath, draft }); diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index 10a6a872fe4..a8a831fb269 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -63,9 +63,12 @@ export const diffHasAllCollapsedDiscussions = (state, getters) => (diff) => { * @returns {Boolean} */ export const diffHasExpandedDiscussions = () => (diff) => { - return diff[INLINE_DIFF_LINES_KEY].filter((l) => l.discussions.length >= 1).some( - (l) => l.discussionsExpanded, - ); + const diffLineDiscussionsExpanded = diff[INLINE_DIFF_LINES_KEY].filter( + (l) => l.discussions.length >= 1, + ).some((l) => l.discussionsExpanded); + const diffFileDiscussionsExpanded = diff.discussions?.some((d) => d.expanded); + + return diffFileDiscussionsExpanded || diffLineDiscussionsExpanded; }; /** @@ -74,7 +77,10 @@ export const diffHasExpandedDiscussions = () => (diff) => { * @returns {Boolean} */ export const diffHasDiscussions = () => (diff) => { - return diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1); + return ( + diff.discussions?.length >= 1 || + diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1) + ); }; /** diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js index 51c21c1bfc4..c32d82faad0 100644 --- a/app/assets/javascripts/diffs/store/mutation_types.js +++ b/app/assets/javascripts/diffs/store/mutation_types.js @@ -49,3 +49,6 @@ export const SET_SHOW_SUGGEST_POPOVER = 'SET_SHOW_SUGGEST_POPOVER'; export const TOGGLE_LINE_DISCUSSIONS = 'TOGGLE_LINE_DISCUSSIONS'; export const DISABLE_VIRTUAL_SCROLLING = 'DISABLE_VIRTUAL_SCROLLING'; + +export const TOGGLE_FILE_COMMENT_FORM = 'TOGGLE_FILE_COMMENT_FORM'; +export const ADD_DRAFT_TO_FILE = 'ADD_DRAFT_TO_FILE'; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 5e7fe8b5cd8..2786e971f4b 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -5,6 +5,7 @@ import { DIFF_FILE_AUTOMATIC_COLLAPSE, INLINE_DIFF_LINES_KEY, EXPANDED_LINE_TYPE, + FILE_DIFF_POSITION_TYPE, } from '../constants'; import * as types from './mutation_types'; import { @@ -168,6 +169,7 @@ export default { const { latestDiff } = state; const originalStartLineCode = discussion.original_position?.line_range?.start?.line_code; + const positionType = discussion.position?.position_type; const discussionLineCodes = [ discussion.line_code, originalStartLineCode, @@ -212,16 +214,7 @@ export default { state.diffFiles.forEach((file) => { if (file.file_hash === fileHash) { - if (file[INLINE_DIFF_LINES_KEY].length) { - file[INLINE_DIFF_LINES_KEY].forEach((line) => { - Object.assign( - line, - setDiscussionsExpanded(lineCheck(line) ? mapDiscussions(line) : line), - ); - }); - } - - if (!file[INLINE_DIFF_LINES_KEY].length) { + if (positionType === FILE_DIFF_POSITION_TYPE) { const newDiscussions = (file.discussions || []) .filter((d) => d.id !== discussion.id) .concat(discussion); @@ -229,6 +222,25 @@ export default { Object.assign(file, { discussions: newDiscussions, }); + } else { + if (file[INLINE_DIFF_LINES_KEY].length) { + file[INLINE_DIFF_LINES_KEY].forEach((line) => { + Object.assign( + line, + setDiscussionsExpanded(lineCheck(line) ? mapDiscussions(line) : line), + ); + }); + } + + if (!file[INLINE_DIFF_LINES_KEY].length) { + const newDiscussions = (file.discussions || []) + .filter((d) => d.id !== discussion.id) + .concat(discussion); + + Object.assign(file, { + discussions: newDiscussions, + }); + } } } }); @@ -378,4 +390,14 @@ export default { [types.DISABLE_VIRTUAL_SCROLLING](state) { state.disableVirtualScroller = true; }, + [types.TOGGLE_FILE_COMMENT_FORM](state, filePath) { + const file = findDiffFile(state.diffFiles, filePath, 'file_path'); + + file.hasCommentForm = !file.hasCommentForm; + }, + [types.ADD_DRAFT_TO_FILE](state, { filePath, draft }) { + const file = findDiffFile(state.diffFiles, filePath, 'file_path'); + + file?.drafts.push(draft); + }, }; diff --git a/app/assets/javascripts/diffs/utils/diff_file.js b/app/assets/javascripts/diffs/utils/diff_file.js index e2fb24f7b57..f2a3224d332 100644 --- a/app/assets/javascripts/diffs/utils/diff_file.js +++ b/app/assets/javascripts/diffs/utils/diff_file.js @@ -53,6 +53,9 @@ export const isNotDiffable = (file) => file?.viewer?.name === viewerModes.not_di export function prepareRawDiffFile({ file, allFiles, meta = false, index = -1 }) { const additionalProperties = { brokenSymlink: fileSymlinkInformation(file, allFiles), + hasCommentForm: false, + discussions: file.discussions || [], + drafts: [], viewer: { ...file.viewer, ...collapsed(file), diff --git a/app/assets/javascripts/notes/components/diff_discussion_header.vue b/app/assets/javascripts/notes/components/diff_discussion_header.vue index f949142d90a..c53d3203327 100644 --- a/app/assets/javascripts/notes/components/diff_discussion_header.vue +++ b/app/assets/javascripts/notes/components/diff_discussion_header.vue @@ -5,6 +5,7 @@ import { mapActions } from 'vuex'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { truncateSha } from '~/lib/utils/text_utility'; import { s__, __, sprintf } from '~/locale'; +import { FILE_DIFF_POSITION_TYPE } from '~/diffs/constants'; import NoteEditedText from './note_edited_text.vue'; import NoteHeader from './note_header.vue'; @@ -62,6 +63,7 @@ export default { for_commit: isForCommit, diff_discussion: isDiffDiscussion, active: isActive, + position, } = this.discussion; let text = s__('MergeRequests|started a thread'); @@ -75,6 +77,10 @@ export default { : s__( 'MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}', ); + } else if (isDiffDiscussion && position?.position_type === FILE_DIFF_POSITION_TYPE) { + text = isActive + ? s__('MergeRequests|started a thread on %{linkStart}a file%{linkEnd}') + : s__('MergeRequests|started a thread on %{linkStart}an old version of a file%{linkEnd}'); } else if (isDiffDiscussion) { text = isActive ? s__('MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}') diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index aabdc1c99b6..3c12ca70c2a 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -8,6 +8,7 @@ import { getDiffMode } from '~/diffs/store/utils'; import { diffViewerModes } from '~/ide/constants'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import { isCollapsed } from '~/diffs/utils/diff_file'; +import { FILE_DIFF_POSITION_TYPE } from '~/diffs/constants'; const FIRST_CHAR_REGEX = /^(\+|-| )/; @@ -53,6 +54,12 @@ export default { isCollapsed() { return isCollapsed(this.discussion.diff_file); }, + positionType() { + return this.discussion.position?.position_type; + }, + isFileDiscussion() { + return this.positionType === FILE_DIFF_POSITION_TYPE; + }, }, mounted() { if (this.isTextFile && !this.hasTruncatedDiffLines) { @@ -87,43 +94,52 @@ export default { />
-