Commit f5fa604c authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '21505-quickactions-update-pd' into 'master'

Apply quickactions when modifying comments

See merge request gitlab-org/gitlab-ce!31136
parents e5e6a5fb a13abd67
...@@ -19,6 +19,7 @@ const httpStatusCodes = { ...@@ -19,6 +19,7 @@ const httpStatusCodes = {
UNAUTHORIZED: 401, UNAUTHORIZED: 401,
FORBIDDEN: 403, FORBIDDEN: 403,
NOT_FOUND: 404, NOT_FOUND: 404,
GONE: 410,
UNPROCESSABLE_ENTITY: 422, UNPROCESSABLE_ENTITY: 422,
}; };
......
...@@ -14,6 +14,7 @@ import NoteBody from './note_body.vue'; ...@@ -14,6 +14,7 @@ import NoteBody from './note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import noteable from '../mixins/noteable'; import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
import httpStatusCodes from '~/lib/utils/http_status';
export default { export default {
name: 'NoteableNote', name: 'NoteableNote',
...@@ -122,7 +123,13 @@ export default { ...@@ -122,7 +123,13 @@ export default {
}, },
methods: { methods: {
...mapActions(['deleteNote', 'updateNote', 'toggleResolveNote', 'scrollToNoteIfNeeded']), ...mapActions([
'deleteNote',
'removeNote',
'updateNote',
'toggleResolveNote',
'scrollToNoteIfNeeded',
]),
editHandler() { editHandler() {
this.isEditing = true; this.isEditing = true;
this.$emit('handleEdit'); this.$emit('handleEdit');
...@@ -185,15 +192,21 @@ export default { ...@@ -185,15 +192,21 @@ export default {
this.updateSuccess(); this.updateSuccess();
callback(); callback();
}) })
.catch(() => { .catch(response => {
this.isRequesting = false; if (response.status === httpStatusCodes.GONE) {
this.isEditing = true; this.removeNote(this.note);
this.$nextTick(() => { this.updateSuccess();
const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText);
callback(); callback();
}); } else {
this.isRequesting = false;
this.isEditing = true;
this.$nextTick(() => {
const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText);
callback();
});
}
}); });
}, },
formCancelHandler(shouldConfirm, isDirty) { formCancelHandler(shouldConfirm, isDirty) {
......
...@@ -61,18 +61,22 @@ export const updateDiscussion = ({ commit, state }, discussion) => { ...@@ -61,18 +61,22 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
return utils.findNoteObjectById(state.discussions, discussion.id); return utils.findNoteObjectById(state.discussions, discussion.id);
}; };
export const deleteNote = ({ commit, dispatch, state }, note) => export const removeNote = ({ commit, dispatch, state }, note) => {
axios.delete(note.path).then(() => { const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
commit(types.DELETE_NOTE, note); commit(types.DELETE_NOTE, note);
dispatch('updateMergeRequestWidget'); dispatch('updateMergeRequestWidget');
dispatch('updateResolvableDiscussionsCounts'); dispatch('updateResolvableDiscussionsCounts');
if (isInMRPage()) { if (isInMRPage()) {
dispatch('diffs/removeDiscussionsFromDiff', discussion); dispatch('diffs/removeDiscussionsFromDiff', discussion);
} }
};
export const deleteNote = ({ dispatch }, note) =>
axios.delete(note.path).then(() => {
dispatch('removeNote', note);
}); });
export const updateNote = ({ commit, dispatch }, { endpoint, note }) => export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
......
...@@ -73,6 +73,11 @@ module NotesActions ...@@ -73,6 +73,11 @@ module NotesActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def update def update
@note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note)
unless @note
head :gone
return
end
prepare_notes_for_rendering([@note]) prepare_notes_for_rendering([@note])
respond_to do |format| respond_to do |format|
......
...@@ -6,7 +6,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesHelper include NotesHelper
include ToggleAwardEmoji include ToggleAwardEmoji
before_action :whitelist_query_limiting, only: [:create] before_action :whitelist_query_limiting, only: [:create, :update]
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve] before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
......
...@@ -8,24 +8,70 @@ module Notes ...@@ -8,24 +8,70 @@ module Notes
old_mentioned_users = note.mentioned_users.to_a old_mentioned_users = note.mentioned_users.to_a
note.update(params.merge(updated_by: current_user)) note.update(params.merge(updated_by: current_user))
note.create_new_cross_references!(current_user)
if note.previous_changes.include?('note') only_commands = false
TodoService.new.update_note(note, current_user, old_mentioned_users)
quick_actions_service = QuickActionsService.new(project, current_user)
if quick_actions_service.supported?(note)
content, update_params, message = quick_actions_service.execute(note, {})
only_commands = content.empty?
note.note = content
end
unless only_commands
note.create_new_cross_references!(current_user)
update_todos(note, old_mentioned_users)
update_suggestions(note)
end end
if note.supports_suggestion? if quick_actions_service.commands_executed_count.to_i > 0
Suggestion.transaction do if update_params.present?
note.suggestions.delete_all quick_actions_service.apply_updates(update_params, note)
Suggestions::CreateService.new(note).execute note.commands_changes = update_params
end end
# We need to refresh the previous suggestions call cache if only_commands
# in order to get the new records. delete_note(note, message)
note.reset note = nil
else
note.save
end
end end
note note
end end
private
def delete_note(note, message)
# We must add the error after we call #save because errors are reset
# when #save is called
note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
Notes::DestroyService.new(project, current_user).execute(note)
end
def update_suggestions(note)
return unless note.supports_suggestion?
Suggestion.transaction do
note.suggestions.delete_all
Suggestions::CreateService.new(note).execute
end
# We need to refresh the previous suggestions call cache
# in order to get the new records.
note.reset
end
def update_todos(note, old_mentioned_users)
return unless note.previous_changes.include?('note')
TodoService.new.update_note(note, current_user, old_mentioned_users)
end
end end
end end
---
title: Apply quickactions when modifying comments
merge_request: 31136
author:
type: added
...@@ -2977,6 +2977,9 @@ msgstr "" ...@@ -2977,6 +2977,9 @@ msgstr ""
msgid "Commands applied" msgid "Commands applied"
msgstr "" msgstr ""
msgid "Commands did not apply"
msgstr ""
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
......
...@@ -336,7 +336,7 @@ describe('Actions Notes Store', () => { ...@@ -336,7 +336,7 @@ describe('Actions Notes Store', () => {
}); });
}); });
describe('deleteNote', () => { describe('removeNote', () => {
const endpoint = `${TEST_HOST}/note`; const endpoint = `${TEST_HOST}/note`;
let axiosMock; let axiosMock;
...@@ -357,7 +357,7 @@ describe('Actions Notes Store', () => { ...@@ -357,7 +357,7 @@ describe('Actions Notes Store', () => {
const note = { path: endpoint, id: 1 }; const note = { path: endpoint, id: 1 };
testAction( testAction(
actions.deleteNote, actions.removeNote,
note, note,
store.state, store.state,
[ [
...@@ -384,7 +384,7 @@ describe('Actions Notes Store', () => { ...@@ -384,7 +384,7 @@ describe('Actions Notes Store', () => {
$('body').attr('data-page', 'projects:merge_requests:show'); $('body').attr('data-page', 'projects:merge_requests:show');
testAction( testAction(
actions.deleteNote, actions.removeNote,
note, note,
store.state, store.state,
[ [
...@@ -409,6 +409,45 @@ describe('Actions Notes Store', () => { ...@@ -409,6 +409,45 @@ describe('Actions Notes Store', () => {
}); });
}); });
describe('deleteNote', () => {
const endpoint = `${TEST_HOST}/note`;
let axiosMock;
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
axiosMock.onDelete(endpoint).replyOnce(200, {});
$('body').attr('data-page', '');
});
afterEach(() => {
axiosMock.restore();
$('body').attr('data-page', '');
});
it('dispatches removeNote', done => {
const note = { path: endpoint, id: 1 };
testAction(
actions.deleteNote,
note,
{},
[],
[
{
type: 'removeNote',
payload: {
id: 1,
path: 'http://test.host/note',
},
},
],
done,
);
});
});
describe('createNewNote', () => { describe('createNewNote', () => {
describe('success', () => { describe('success', () => {
const res = { const res = {
......
...@@ -89,5 +89,54 @@ shared_examples 'move quick action' do ...@@ -89,5 +89,54 @@ shared_examples 'move quick action' do
it_behaves_like 'applies the commands to issues in both projects, target and source' it_behaves_like 'applies the commands to issues in both projects, target and source'
end end
end end
context 'when editing comments' do
let(:target_project) { create(:project, :public) }
before do
target_project.add_maintainer(user)
sign_in(user)
visit project_issue_path(project, issue)
wait_for_all_requests
end
it 'moves the issue after quickcommand note was updated' do
# misspelled quick action
add_note("test note.\n/mvoe #{target_project.full_path}")
expect(issue.reload).not_to be_closed
edit_note("/mvoe #{target_project.full_path}", "test note.\n/move #{target_project.full_path}")
wait_for_all_requests
expect(page).to have_content 'test note.'
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
wait_for_all_requests
expect(page).to have_content 'Issues 1'
end
it 'deletes the note if it was updated to just contain a command' do
# missspelled quick action
add_note("test note.\n/mvoe #{target_project.full_path}")
expect(page).not_to have_content 'Commands applied'
expect(issue.reload).not_to be_closed
edit_note("/mvoe #{target_project.full_path}", "/move #{target_project.full_path}")
wait_for_all_requests
expect(page).not_to have_content "/move #{target_project.full_path}"
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
wait_for_all_requests
expect(page).to have_content 'Issues 1'
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment