summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-02 18:08:32 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-02 18:08:32 +0000
commitf3e7bc80608c100227030030a6a601897f8e4ff9 (patch)
tree65afc2ae0ee2ccf7cf8d4efbf44077f816cade09 /app/assets
parenteea1fbf9f980fed108601412b63e627d3eebd46d (diff)
downloadgitlab-ce-f3e7bc80608c100227030030a6a601897f8e4ff9.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/batch_comments/components/draft_note.vue12
-rw-r--r--app/assets/javascripts/batch_comments/components/inline_draft_comment_row.vue11
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_item.vue51
-rw-r--r--app/assets/javascripts/batch_comments/services/drafts_service.js4
-rw-r--r--app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js6
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue36
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue2
-rw-r--r--app/assets/javascripts/diffs/store/utils.js2
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_form.vue68
-rw-r--r--app/assets/javascripts/notes/components/multiline_comment_utils.js57
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue82
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue3
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));
},