diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-02 18:08:32 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-02 18:08:32 +0000 |
commit | f3e7bc80608c100227030030a6a601897f8e4ff9 (patch) | |
tree | 65afc2ae0ee2ccf7cf8d4efbf44077f816cade09 /app/assets | |
parent | eea1fbf9f980fed108601412b63e627d3eebd46d (diff) | |
download | gitlab-ce-f3e7bc80608c100227030030a6a601897f8e4ff9.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
12 files changed, 319 insertions, 15 deletions
diff --git a/app/assets/javascripts/batch_comments/components/draft_note.vue b/app/assets/javascripts/batch_comments/components/draft_note.vue index 3e0ae6f65c6..963d104b6b3 100644 --- a/app/assets/javascripts/batch_comments/components/draft_note.vue +++ b/app/assets/javascripts/batch_comments/components/draft_note.vue @@ -15,6 +15,16 @@ export default { type: Object, required: true, }, + diffFile: { + type: Object, + required: false, + default: () => ({}), + }, + line: { + type: Object, + required: false, + default: null, + }, }, data() { return { @@ -61,6 +71,8 @@ export default { <ul class="notes draft-notes"> <noteable-note :note="draft" + :diff-lines="diffFile.highlighted_diff_lines" + :line="line" class="draft-note" @handleEdit="handleEditing" @cancelForm="handleNotEditing" diff --git a/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue b/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue index b7c9831dd45..385725cd109 100644 --- a/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue +++ b/app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue @@ -10,6 +10,15 @@ export default { type: Object, required: true, }, + diffFile: { + type: Object, + required: true, + }, + line: { + type: Object, + required: false, + default: null, + }, }, }; </script> @@ -17,7 +26,7 @@ export default { <template> <tr class="notes_holder js-temp-notes-holder"> <td class="notes-content" colspan="4"> - <div class="content"><draft-note :draft="draft" /></div> + <div class="content"><draft-note :draft="draft" :diff-file="diffFile" :line="line" /></div> </td> </tr> </template> diff --git a/app/assets/javascripts/batch_comments/components/preview_item.vue b/app/assets/javascripts/batch_comments/components/preview_item.vue index df9c419ff96..22495eb4d7d 100644 --- a/app/assets/javascripts/batch_comments/components/preview_item.vue +++ b/app/assets/javascripts/batch_comments/components/preview_item.vue @@ -4,12 +4,20 @@ import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants'; import { sprintf, __ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import resolvedStatusMixin from '../mixins/resolved_status'; +import { GlSprintf } from '@gitlab/ui'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { + getStartLineNumber, + getEndLineNumber, + getLineClasses, +} from '~/notes/components/multiline_comment_utils'; export default { components: { Icon, + GlSprintf, }, - mixins: [resolvedStatusMixin], + mixins: [resolvedStatusMixin, glFeatureFlagsMixin()], props: { draft: { type: Object, @@ -51,7 +59,7 @@ export default { const position = this.discussion ? this.discussion.position : this.draft.position; - return position.new_line || position.old_line; + return position?.new_line || position?.old_line; }, content() { const el = document.createElement('div'); @@ -62,9 +70,18 @@ export default { showLinePosition() { return this.draft.file_hash || this.isDiffDiscussion; }, + startLineNumber() { + return getStartLineNumber(this.draft.position?.line_range); + }, + endLineNumber() { + return getEndLineNumber(this.draft.position?.line_range); + }, }, methods: { ...mapActions('batchComments', ['scrollToDraft']), + getLineClasses(lineNumber) { + return getLineClasses(lineNumber); + }, }, showStaysResolved: false, }; @@ -83,11 +100,33 @@ export default { @click="scrollToDraft(draft)" > <span class="review-preview-item-header"> - <icon class="gl-mr-3 flex-shrink-0" :name="iconName" /> - <span class="bold text-nowrap"> - <span class="review-preview-item-header-text block-truncated"> {{ titleText }} </span> + <icon class="flex-shrink-0" :name="iconName" /> + <span + class="bold text-nowrap" + :class="{ 'gl-align-items-center': glFeatures.multilineComments }" + > + <span class="review-preview-item-header-text block-truncated"> + {{ titleText }} + </span> <template v-if="showLinePosition"> - :{{ linePosition }} + <template v-if="!glFeatures.multilineComments" + >:{{ linePosition }}</template + > + <template v-else-if="startLineNumber === endLineNumber"> + :<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span> + </template> + <gl-sprintf v-else :message="__(':%{startLine} to %{endLine}')"> + <template #startLine> + <span class="gl-mr-2" :class="getLineClasses(startLineNumber)">{{ + startLineNumber + }}</span> + </template> + <template #endLine> + <span class="gl-ml-2" :class="getLineClasses(endLineNumber)">{{ + endLineNumber + }}</span> + </template> + </gl-sprintf> </template> </span> </span> diff --git a/app/assets/javascripts/batch_comments/services/drafts_service.js b/app/assets/javascripts/batch_comments/services/drafts_service.js index e81b48cd873..36d2f8df612 100644 --- a/app/assets/javascripts/batch_comments/services/drafts_service.js +++ b/app/assets/javascripts/batch_comments/services/drafts_service.js @@ -25,9 +25,9 @@ export default { discard(endpoint) { return axios.delete(endpoint); }, - update(endpoint, { draftId, note, resolveDiscussion }) { + update(endpoint, { draftId, note, resolveDiscussion, position }) { return axios.put(`${endpoint}/${draftId}`, { - draft_note: { note, resolve_discussion: resolveDiscussion }, + draft_note: { note, resolve_discussion: resolveDiscussion, position }, }); }, }; diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js index ae3d5161665..1ef012696c5 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js @@ -84,12 +84,16 @@ export const discardReview = ({ commit, getters }) => { .catch(() => commit(types.RECEIVE_DISCARD_REVIEW_ERROR)); }; -export const updateDraft = ({ commit, getters }, { note, noteText, resolveDiscussion, callback }) => +export const updateDraft = ( + { commit, getters }, + { note, noteText, resolveDiscussion, position, callback }, +) => service .update(getters.getNotesData.draftsPath, { draftId: note.id, note: noteText, resolveDiscussion, + position: JSON.stringify(position), }) .then(res => res.data) .then(data => commit(types.RECEIVE_DRAFT_UPDATE_SUCCESS, data)) 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 a94c85001a4..74305ee69bc 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -1,18 +1,22 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; 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 } from '../constants'; +import { commentLineOptions } from '../../notes/components/multiline_comment_utils'; export default { components: { noteForm, userAvatarLink, + MultilineCommentForm, }, - mixins: [autosave, diffLineNoteFormMixin], + mixins: [autosave, diffLineNoteFormMixin, glFeatureFlagsMixin()], props: { diffFileHash: { type: String, @@ -37,6 +41,14 @@ export default { default: '', }, }, + data() { + return { + commentLineStart: { + lineCode: this.line.line_code, + type: this.line.type, + }, + }; + }, computed: { ...mapState({ noteableData: state => state.notes.noteableData, @@ -62,11 +74,20 @@ export default { diffViewType: this.diffViewType, diffFile: this.diffFile, linePosition: this.linePosition, + lineRange: { + start_line_code: this.commentLineStart.lineCode, + start_line_type: this.commentLineStart.type, + end_line_code: this.line.line_code, + end_line_type: this.line.type, + }, }; }, diffFile() { return this.getDiffFileByHash(this.diffFileHash); }, + commentLineOptions() { + return commentLineOptions(this.diffFile.highlighted_diff_lines, this.line.line_code); + }, }, mounted() { if (this.isLoggedIn) { @@ -83,7 +104,6 @@ export default { methods: { ...mapActions('diffs', [ 'cancelCommentForm', - 'assignDiscussionsToDiff', 'saveDiffDiscussion', 'setSuggestPopoverDismissed', ]), @@ -116,6 +136,16 @@ 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-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 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" @@ -133,7 +163,7 @@ export default { :diff-file="diffFile" :show-suggest-popover="showSuggestPopover" save-button-title="Comment" - class="diff-comment-form" + class="diff-comment-form prepend-top-10" @handleFormUpdateAddToReview="addToReview" @cancelForm="handleCancelCommentForm" @handleFormUpdate="handleSaveNote" diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index f1c42e7cc72..ad72016f03b 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -80,6 +80,8 @@ export default { v-if="shouldRenderDraftRow(diffFile.file_hash, line)" :key="`draft_${index}`" :draft="draftForLine(diffFile.file_hash, line)" + :diff-file="diffFile" + :line="line" /> </template> </tbody> diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 2be71c77087..d261be1b550 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -40,6 +40,7 @@ export function getFormData(params) { diffViewType, linePosition, positionType, + lineRange, } = params; const position = JSON.stringify({ @@ -55,6 +56,7 @@ export function getFormData(params) { y: params.y, width: params.width, height: params.height, + line_range: lineRange, }); const postData = { diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue new file mode 100644 index 00000000000..5fba011a153 --- /dev/null +++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue @@ -0,0 +1,68 @@ +<script> +import { GlFormSelect, GlSprintf } from '@gitlab/ui'; +import { getSymbol, getLineClasses } from './multiline_comment_utils'; + +export default { + components: { GlFormSelect, GlSprintf }, + props: { + lineRange: { + type: Object, + required: false, + default: null, + }, + line: { + type: Object, + required: true, + }, + commentLineOptions: { + type: Array, + required: true, + }, + }, + data() { + return { + commentLineStart: { + lineCode: this.lineRange ? this.lineRange.start_line_code : this.line.line_code, + type: this.lineRange ? this.lineRange.start_line_type : this.line.type, + }, + }; + }, + methods: { + getSymbol({ type }) { + return getSymbol(type); + }, + getLineClasses(line) { + return getLineClasses(line); + }, + }, +}; +</script> + +<template> + <div> + <gl-sprintf + :message=" + s__('MergeRequestDiffs|Commenting on lines %{selectStart}start%{selectEnd} to %{end}') + " + > + <template #select> + <label for="comment-line-start" class="sr-only">{{ + s__('MergeRequestDiffs|Select comment starting line') + }}</label> + <gl-form-select + id="comment-line-start" + :value="commentLineStart" + :options="commentLineOptions" + size="sm" + class="gl-w-auto gl-vertical-align-baseline" + @change="$emit('input', $event)" + /> + </template> + <template #end> + <span :class="getLineClasses(line)"> + {{ getSymbol(line) + (line.new_line || line.old_line) }} + </span> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/multiline_comment_utils.js b/app/assets/javascripts/notes/components/multiline_comment_utils.js new file mode 100644 index 00000000000..dc9c55e9b30 --- /dev/null +++ b/app/assets/javascripts/notes/components/multiline_comment_utils.js @@ -0,0 +1,57 @@ +import { takeRightWhile } from 'lodash'; + +export function getSymbol(type) { + if (type === 'new') return '+'; + if (type === 'old') return '-'; + return ''; +} + +function getLineNumber(lineRange, key) { + if (!lineRange || !key) return ''; + const lineCode = lineRange[`${key}_line_code`] || ''; + const lineType = lineRange[`${key}_line_type`] || ''; + const lines = lineCode.split('_') || []; + const lineNumber = lineType === 'old' ? lines[1] : lines[2]; + return (lineNumber && getSymbol(lineType) + lineNumber) || ''; +} + +export function getStartLineNumber(lineRange) { + return getLineNumber(lineRange, 'start'); +} + +export function getEndLineNumber(lineRange) { + return getLineNumber(lineRange, 'end'); +} + +export function getLineClasses(line) { + const symbol = typeof line === 'string' ? line.charAt(0) : getSymbol(line?.type); + + if (symbol !== '+' && symbol !== '-') return ''; + + return [ + 'gl-px-1 gl-rounded-small gl-border-solid gl-border-1 gl-border-white', + { + 'gl-bg-green-100 gl-text-green-800': symbol === '+', + 'gl-bg-red-100 gl-text-red-800': symbol === '-', + }, + ]; +} + +export function commentLineOptions(diffLines, lineCode) { + const selectedIndex = diffLines.findIndex(line => line.line_code === lineCode); + const notMatchType = l => l.type !== 'match'; + + // We're limiting adding comments to only lines above the current line + // to make rendering simpler. Future interations will use a more + // intuitive dragging interface that will make this unnecessary + const upToSelected = diffLines.slice(0, selectedIndex + 1); + + // Only include the lines up to the first "Show unchanged lines" block + // i.e. not a "match" type + const lines = takeRightWhile(upToSelected, notMatchType); + + return lines.map(l => ({ + value: { lineCode: l.line_code, type: l.type }, + text: `${getSymbol(l.type)}${l.new_line || l.old_line}`, + })); +} diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 161bb4336ae..ada7c0465a0 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -2,6 +2,8 @@ import $ from 'jquery'; import { mapGetters, mapActions } from 'vuex'; import { escape } from 'lodash'; +import { GlSprintf } from '@gitlab/ui'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { truncateSha } from '~/lib/utils/text_utility'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import { __, s__, sprintf } from '../../locale'; @@ -14,17 +16,26 @@ import eventHub from '../event_hub'; import noteable from '../mixins/noteable'; import resolvable from '../mixins/resolvable'; import httpStatusCodes from '~/lib/utils/http_status'; +import { + getStartLineNumber, + getEndLineNumber, + getLineClasses, + commentLineOptions, +} from './multiline_comment_utils'; +import MultilineCommentForm from './multiline_comment_form.vue'; export default { name: 'NoteableNote', components: { + GlSprintf, userAvatarLink, noteHeader, noteActions, NoteBody, TimelineEntryItem, + MultilineCommentForm, }, - mixins: [noteable, resolvable], + mixins: [noteable, resolvable, glFeatureFlagsMixin()], props: { note: { type: Object, @@ -50,6 +61,11 @@ export default { required: false, default: false, }, + diffLines: { + type: Object, + required: false, + default: null, + }, }, data() { return { @@ -57,9 +73,14 @@ export default { isDeleting: false, isRequesting: false, isResolving: false, + commentLineStart: { + line_code: this.line?.line_code, + type: this.line?.type, + }, }; }, computed: { + ...mapGetters('diffs', ['getDiffFileByHash']), ...mapGetters(['targetNoteHash', 'getNoteableData', 'getUserData', 'commentsDisabled']), author() { return this.note.author; @@ -113,6 +134,32 @@ export default { (this.note.isDraft && this.note.discussion_id !== null) ); }, + lineRange() { + return this.note.position?.line_range; + }, + startLineNumber() { + return getStartLineNumber(this.lineRange); + }, + endLineNumber() { + return getEndLineNumber(this.lineRange); + }, + showMultiLineComment() { + return ( + this.glFeatures.multilineComments && + this.startLineNumber && + this.endLineNumber && + (this.startLineNumber !== this.endLineNumber || this.isEditing) + ); + }, + commentLineOptions() { + if (this.diffLines) { + return commentLineOptions(this.diffLines, this.line.line_code); + } + + const diffFile = this.diffFile || this.getDiffFileByHash(this.targetNoteHash); + if (!diffFile) return null; + return commentLineOptions(diffFile.highlighted_diff_lines, this.line.line_code); + }, }, created() { @@ -174,10 +221,20 @@ export default { this.$emit('updateSuccess'); }, formUpdateHandler(noteText, parentElement, callback, resolveDiscussion) { + const position = { + ...this.note.position, + line_range: { + start_line_code: this.commentLineStart?.lineCode, + start_line_type: this.commentLineStart?.type, + end_line_code: this.line?.line_code, + end_line_type: this.line?.type, + }, + }; this.$emit('handleUpdateNote', { note: this.note, noteText, resolveDiscussion, + position, callback: () => this.updateSuccess(), }); @@ -239,6 +296,9 @@ export default { noteBody.note.note = noteText; } }, + getLineClasses(lineNumber) { + return getLineClasses(lineNumber); + }, }, }; </script> @@ -251,6 +311,26 @@ export default { :data-note-id="note.id" class="note note-wrapper qa-noteable-note-item" > + <div v-if="showMultiLineComment" data-testid="multiline-comment"> + <multiline-comment-form + v-if="isEditing && commentLineOptions && line" + v-model="commentLineStart" + :line="line" + :comment-line-options="commentLineOptions" + :line-range="note.position.line_range" + class="gl-mb-3 gl-text-gray-700 gl-border-gray-200 gl-border-b-solid gl-border-b-1 gl-pb-3" + /> + <div v-else class="gl-mb-3 gl-text-gray-700"> + <gl-sprintf :message="__('Comment on lines %{startLine} to %{endLine}')"> + <template #startLine> + <span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span> + </template> + <template #endLine> + <span :class="getLineClasses(endLineNumber)">{{ endLineNumber }}</span> + </template> + </gl-sprintf> + </div> + </div> <div v-once class="timeline-icon"> <user-avatar-link :link-href="author.path" diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index 2757d64bd7d..fd1f9eae152 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -82,7 +82,8 @@ export default { }) .catch(() => createFlash(__('Failed to load emoji list.'))); }, - showEmojiMenu() { + showEmojiMenu(e) { + e.stopPropagation(); this.isEmojiMenuVisible = true; this.emojiMenu.showEmojiMenu($(this.$refs.toggleEmojiMenuButton)); }, |