diff options
author | Oswaldo Ferreira <oswaldo@gitlab.com> | 2018-12-13 19:17:19 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-12-13 19:17:19 +0000 |
commit | ed3034bbb71d43b12944a9da29b5264cb3ff3312 (patch) | |
tree | 3110713f4455a4b3a830e177422663e082fc0eb9 /app/assets/javascripts/notes | |
parent | eb81c1239ef86561b4304339609be32318419dbb (diff) | |
download | gitlab-ce-ed3034bbb71d43b12944a9da29b5264cb3ff3312.tar.gz |
Allow suggesting single line changes in diffs
Diffstat (limited to 'app/assets/javascripts/notes')
10 files changed, 150 insertions, 2 deletions
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index c0bee600181..bcf5d334da4 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -1,10 +1,12 @@ <script> +import { mapActions } from 'vuex'; import $ from 'jquery'; import noteEditedText from './note_edited_text.vue'; import noteAwardsList from './note_awards_list.vue'; import noteAttachment from './note_attachment.vue'; import noteForm from './note_form.vue'; import autosave from '../mixins/autosave'; +import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; export default { components: { @@ -12,6 +14,7 @@ export default { noteAwardsList, noteAttachment, noteForm, + Suggestions, }, mixins: [autosave], props: { @@ -19,6 +22,11 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, canEdit: { type: Boolean, required: true, @@ -28,11 +36,22 @@ export default { required: false, default: false, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, computed: { noteBody() { return this.note.note; }, + hasSuggestion() { + return this.note.suggestions && this.note.suggestions.length; + }, + lineType() { + return this.line ? this.line.type : null; + }, }, mounted() { this.renderGFM(); @@ -53,6 +72,7 @@ export default { } }, methods: { + ...mapActions(['submitSuggestion']), renderGFM() { $(this.$refs['note-body']).renderGFM(); }, @@ -62,19 +82,35 @@ export default { formCancelHandler(shouldConfirm, isDirty) { this.$emit('cancelForm', shouldConfirm, isDirty); }, + applySuggestion({ suggestionId, flashContainer, callback }) { + const { discussion_id: discussionId, id: noteId } = this.note; + + this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer, callback }); + }, }, }; </script> <template> <div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body"> - <div class="note-text md" v-html="note.note_html"></div> + <suggestions + v-if="hasSuggestion && !isEditing" + :suggestions="note.suggestions" + :note-html="note.note_html" + :line-type="lineType" + :help-page-path="helpPagePath" + @apply="applySuggestion" + /> + <div v-else class="note-text md" v-html="note.note_html"></div> <note-form v-if="isEditing" ref="noteForm" :is-editing="isEditing" :note-body="noteBody" :note-id="note.id" + :line="line" + :note="note" + :help-page-path="helpPagePath" :markdown-version="note.cached_markdown_version" @handleFormUpdate="handleFormUpdate" @cancelForm="formCancelHandler" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 95164183ccb..9b7f3d3588d 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -1,4 +1,5 @@ <script> +import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mapGetters, mapActions } from 'vuex'; import eventHub from '../event_hub'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; @@ -53,6 +54,21 @@ export default { required: false, default: false, }, + line: { + type: Object, + required: false, + default: null, + }, + note: { + type: Object, + required: false, + default: null, + }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -79,7 +95,8 @@ export default { return '#'; }, markdownPreviewPath() { - return this.getNoteableDataByProp('preview_note_path'); + const notable = this.getNoteableDataByProp('preview_note_path'); + return mergeUrlParams({ preview_suggestions: true }, notable); }, markdownDocsPath() { return this.getNotesDataByProp('markdownDocsPath'); @@ -93,6 +110,18 @@ export default { isDisabled() { return !this.updatedNoteBody.length || this.isSubmitting; }, + discussionNote() { + const discussionNote = this.discussion.id + ? this.getDiscussionLastNote(this.discussion) + : this.note; + return discussionNote || {}; + }, + canSuggest() { + return ( + this.getNoteableData.can_receive_suggestion && + (this.line && this.line.can_receive_suggestion) + ); + }, }, watch: { noteBody() { @@ -171,7 +200,11 @@ export default { :markdown-docs-path="markdownDocsPath" :markdown-version="markdownVersion" :quick-actions-docs-path="quickActionsDocsPath" + :line="line" + :note="discussionNote" + :can-suggest="canSuggest" :add-spacing-classes="false" + :help-page-path="helpPagePath" > <textarea id="note_note" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 5c9a28b8512..4156fe0d229 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -49,6 +49,11 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, renderDiffFile: { type: Boolean, required: false, @@ -64,6 +69,11 @@ export default { required: false, default: false, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { const { diff_discussion: isDiffDiscussion, resolved } = this.discussion; @@ -194,6 +204,13 @@ export default { false, ); }, + diffLine() { + if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) { + return this.discussion.truncated_diff_lines.slice(-1)[0]; + } + + return this.line; + }, }, watch: { isReplying() { @@ -357,6 +374,8 @@ Please check your network connection and try again.`; <component :is="componentName(initialDiscussion)" :note="componentData(initialDiscussion)" + :line="line" + :help-page-path="helpPagePath" @handleDeleteNote="deleteNoteHandler" > <slot slot="avatar-badge" name="avatar-badge"></slot> @@ -373,6 +392,8 @@ Please check your network connection and try again.`; v-for="note in replies" :key="note.id" :note="componentData(note)" + :help-page-path="helpPagePath" + :line="line" @handleDeleteNote="deleteNoteHandler" /> </template> @@ -383,6 +404,8 @@ Please check your network connection and try again.`; v-for="(note, index) in discussion.notes" :key="note.id" :note="componentData(note)" + :help-page-path="helpPagePath" + :line="diffLine" @handleDeleteNote="deleteNoteHandler" > <slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot> @@ -447,6 +470,7 @@ Please check your network connection and try again.`; ref="noteForm" :discussion="discussion" :is-editing="false" + :line="diffLine" save-button-title="Comment" @handleFormUpdate="saveReply" @cancelForm="cancelReplyForm" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index a17be51353e..57e9c40bd61 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -27,6 +27,16 @@ export default { type: Object, required: true, }, + line: { + type: Object, + required: false, + default: null, + }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -220,8 +230,10 @@ export default { <note-body ref="noteBody" :note="note" + :line="line" :can-edit="note.current_user.can_edit" :is-editing="isEditing" + :help-page-path="helpPagePath" @handleFormUpdate="formUpdateHandler" @cancelForm="formCancelHandler" /> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 27f896cee35..f3fcfdfda05 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -49,6 +49,11 @@ export default { required: false, default: 0, }, + helpPagePath: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -206,6 +211,7 @@ export default { :key="discussion.id" :discussion="discussion" :render-diff-file="true" + :help-page-path="helpPagePath" /> </template> </ul> diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index 47a6f07cce2..237e70c0a4c 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import Api from '~/api'; import VueResource from 'vue-resource'; import * as constants from '../constants'; @@ -44,4 +45,7 @@ export default { toggleIssueState(endpoint, data) { return Vue.http.put(endpoint, data); }, + applySuggestion(id) { + return Api.applySuggestion(id); + }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 4716ab52333..65f85314fa0 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -405,5 +405,25 @@ export const startTaskList = ({ dispatch }) => export const updateResolvableDiscussonsCounts = ({ commit }) => commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS); +export const submitSuggestion = ( + { commit }, + { discussionId, noteId, suggestionId, flashContainer, callback }, +) => { + service + .applySuggestion(suggestionId) + .then(() => { + commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }); + callback(); + }) + .catch(() => { + Flash( + __('Something went wrong while applying the suggestion. Please try again.'), + 'alert', + flashContainer, + ); + callback(); + }); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index b5fe8bdb1d3..887e6d22b06 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -20,6 +20,7 @@ export default () => ({ userData: {}, noteableData: { current_user: {}, + preview_note_path: 'path/to/preview', }, commentsDisabled: false, resolvableDiscussionsCount: 0, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 9c68ab67a8c..df943c155f4 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -16,6 +16,7 @@ export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; export const DISABLE_COMMENTS = 'DISABLE_COMMENTS'; +export const APPLY_SUGGESTION = 'APPLY_SUGGESTION'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 39ff0ff73d7..8992454be2e 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -197,6 +197,17 @@ export default { } }, + [types.APPLY_SUGGESTION](state, { noteId, discussionId, suggestionId }) { + const noteObj = utils.findNoteObjectById(state.discussions, discussionId); + const comment = utils.findNoteObjectById(noteObj.notes, noteId); + + comment.suggestions = comment.suggestions.map(suggestion => ({ + ...suggestion, + applied: suggestion.applied || suggestion.id === suggestionId, + appliable: false, + })); + }, + [types.UPDATE_DISCUSSION](state, noteData) { const note = noteData; const selectedDiscussion = state.discussions.find(disc => disc.id === note.id); |