Commit a8cb90d6 authored by jakeburden's avatar jakeburden

Allows assign and unassign from comment

Add locale for assignment button

Test that the “assign” button renders for users

Fix assign/unassign copy

Add “from” in unassign copy

Move assign button below delete button

Use issue api directly to save assignee

Add mutation to update state of assignees

Create assignees getter/setter

Update Note component assignee state

Add api method to update merge requests

Move result handler to callback function for reuse

Create handler for assigning from merge requests

Update locale translations

Move display text to computed prop

Use data-testid as suggested by our guidelines

Use event hub instead of window.emitSidebarEvent

Move assigne update handler to own method

Make targetType a computed prop

Use Array.some rather than Array.map and .includes

Use destructuring to get values

Remove unnecessary assignment

Get assignees from vuex

Remove duplicate event emitter

Get targetType from this context

Use this context where needed

Use updated api endpoint methods

Static analysis fix

Reorded sidebar actions

Add flash message on error

Check that this.assignees exists

Remove unused currentUser prop

Remove unused mixin

Add locale file changes for assignee updates

Remove check for status code in assignee update handler

Fix method call to update assigneess

Remove unused computed assignees

Use parentheses rather than underscores

Add spec for UPDATE_ASSIGNEES mutation

Add spec for updateAssignees action

Fix path for updating merge requests

Add axios mock to test updateAssignees is called

Remove duplicate API path

Only display button on issues

Adjust tests for only displaying on issues

Revert "Add project id to merge request notable data"

This reverts commit bf7c6d7836fb165a1982ac9c094843e6fa705e75.

Run prettier on note_actions.vue

Add a changelog

Remove duplicate method
parent bf3fcd03
<script>
import { __ } from '~/locale';
import { mapGetters } from 'vuex';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import Icon from '~/vue_shared/components/icon.vue';
import ReplyButton from './note_actions/reply_button.vue';
import eventHub from '~/sidebar/event_hub';
import Api from '~/api';
import flash from '~/flash';
export default {
name: 'NoteActions',
......@@ -17,6 +21,10 @@ export default {
},
mixins: [resolvedStatusMixin],
props: {
author: {
type: Object,
required: true,
},
authorId: {
type: Number,
required: true,
......@@ -87,7 +95,7 @@ export default {
},
},
computed: {
...mapGetters(['getUserDataByProp']),
...mapGetters(['getUserDataByProp', 'getNoteableData']),
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
......@@ -100,6 +108,26 @@ export default {
currentUserId() {
return this.getUserDataByProp('id');
},
isUserAssigned() {
return this.assignees && this.assignees.some(({ id }) => id === this.author.id);
},
displayAssignUserText() {
return this.isUserAssigned
? __('Unassign from commenting user')
: __('Assign to commenting user');
},
sidebarAction() {
return this.isUserAssigned ? 'sidebar.addAssignee' : 'sidebar.removeAssignee';
},
targetType() {
return this.getNoteableData.targetType;
},
assignees() {
return this.getNoteableData.assignees || [];
},
isIssue() {
return this.targetType === 'issue';
},
},
methods: {
onEdit() {
......@@ -116,6 +144,29 @@ export default {
this.$root.$emit('bv::hide::tooltip');
});
},
handleAssigneeUpdate(assignees) {
this.$emit('updateAssignees', assignees);
eventHub.$emit(this.sidebarAction, this.author);
eventHub.$emit('sidebar.saveAssignees');
},
assignUser() {
let { assignees } = this;
const { project_id, iid } = this.getNoteableData;
if (this.isUserAssigned) {
assignees = assignees.filter(assignee => assignee.id !== this.author.id);
} else {
assignees.push({ id: this.author.id });
}
if (this.targetType === 'issue') {
Api.updateIssue(project_id, iid, {
assignee_ids: assignees.map(assignee => assignee.id),
})
.then(() => this.handleAssigneeUpdate(assignees))
.catch(() => flash(__('Something went wrong while updating assignees')));
}
},
},
};
</script>
......@@ -215,6 +266,16 @@ export default {
<span class="text-danger">{{ __('Delete comment') }}</span>
</button>
</li>
<li v-if="isIssue">
<button
class="btn-default btn-transparent"
data-testid="assign-user"
type="button"
@click="assignUser"
>
{{ displayAssignUserText }}
</button>
</li>
</ul>
</div>
</div>
......
......@@ -184,6 +184,7 @@ export default {
'updateNote',
'toggleResolveNote',
'scrollToNoteIfNeeded',
'updateAssignees',
]),
editHandler() {
this.isEditing = true;
......@@ -299,6 +300,9 @@ export default {
getLineClasses(lineNumber) {
return getLineClasses(lineNumber);
},
assigneesUpdate(assignees) {
this.updateAssignees(assignees);
},
},
};
</script>
......@@ -355,6 +359,7 @@ export default {
<span v-else-if="note.created_at" class="d-none d-sm-inline">&middot;</span>
</note-header>
<note-actions
:author="author"
:author-id="author.id"
:note-id="note.id"
:note-url="note.noteable_note_url"
......@@ -377,6 +382,7 @@ export default {
@handleDelete="deleteHandler"
@handleResolve="resolveHandler"
@startReplying="$emit('startReplying')"
@updateAssignees="assigneesUpdate"
/>
</div>
<div class="timeline-discussion-body">
......
......@@ -647,5 +647,9 @@ export const receiveDeleteDescriptionVersionError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR, error);
};
export const updateAssignees = ({ commit }, assignees) => {
commit(types.UPDATE_ASSIGNEES, assignees);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -23,6 +23,7 @@ export const REMOVE_SUGGESTION_FROM_BATCH = 'REMOVE_SUGGESTION_FROM_BATCH';
export const CLEAR_SUGGESTION_BATCH = 'CLEAR_SUGGESTION_BATCH';
export const CONVERT_TO_DISCUSSION = 'CONVERT_TO_DISCUSSION';
export const REMOVE_CONVERTED_DISCUSSION = 'REMOVE_CONVERTED_DISCUSSION';
export const UPDATE_ASSIGNEES = 'UPDATE_ASSIGNEES';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
......
......@@ -355,4 +355,7 @@ export default {
[types.RECEIVE_DELETE_DESCRIPTION_VERSION_ERROR](state) {
state.isLoadingDescriptionVersion = false;
},
[types.UPDATE_ASSIGNEES](state, assignees) {
state.noteableData.assignees = assignees;
},
};
---
title: Resolve Add a button to assign users who have commented on an issue
merge_request: 23883
author:
type: added
......@@ -2970,6 +2970,9 @@ msgstr ""
msgid "Assign to"
msgstr ""
msgid "Assign to commenting user"
msgstr ""
msgid "Assign yourself to these issues"
msgstr ""
......@@ -20950,6 +20953,9 @@ msgstr ""
msgid "Something went wrong while updating a requirement."
msgstr ""
msgid "Something went wrong while updating assignees"
msgstr ""
msgid "Something went wrong while updating your list settings"
msgstr ""
......@@ -24059,6 +24065,9 @@ msgstr ""
msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}"
msgstr ""
msgid "Unassign from commenting user"
msgstr ""
msgid "Unblock"
msgstr ""
......
......@@ -4,26 +4,33 @@ import { TEST_HOST } from 'spec/test_constants';
import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
describe('noteActions', () => {
let wrapper;
let store;
let props;
let actions;
let axiosMock;
const shallowMountNoteActions = propsData => {
const shallowMountNoteActions = (propsData, computed) => {
const localVue = createLocalVue();
return shallowMount(localVue.extend(noteActions), {
store,
propsData,
localVue,
computed,
});
};
beforeEach(() => {
store = createStore();
props = {
accessLevel: 'Maintainer',
authorId: 26,
authorId: 1,
author: userDataMock,
canDelete: true,
canEdit: true,
canAwardEmoji: true,
......@@ -33,10 +40,17 @@ describe('noteActions', () => {
reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`,
showReply: false,
};
actions = {
updateAssignees: jest.fn(),
};
axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
axiosMock.restore();
});
describe('user is logged in', () => {
......@@ -76,6 +90,14 @@ describe('noteActions', () => {
it('should not show copy link action when `noteUrl` prop is empty', done => {
wrapper.setProps({
...props,
author: {
avatar_url: 'mock_path',
id: 26,
name: 'Example Maintainer',
path: '/ExampleMaintainer',
state: 'active',
username: 'ExampleMaintainer',
},
noteUrl: '',
});
......@@ -104,6 +126,25 @@ describe('noteActions', () => {
})
.catch(done.fail);
});
it('should be possible to assign or unassign the comment author', () => {
wrapper = shallowMountNoteActions(props, {
targetType: () => 'issue',
});
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
expect(assignUserButton.exists()).toBe(true);
assignUserButton.trigger('click');
axiosMock.onPut(`${TEST_HOST}/api/v4/projects/group/project/issues/1`).reply(() => {
expect(actions.updateAssignees).toHaveBeenCalled();
});
});
it('should not be possible to assign or unassign the comment author in a merge request', () => {
const assignUserButton = wrapper.find('[data-testid="assign-user"]');
expect(assignUserButton.exists()).toBe(false);
});
});
});
......
......@@ -1141,4 +1141,17 @@ describe('Actions Notes Store', () => {
});
});
});
describe('updateAssignees', () => {
it('update the assignees state', done => {
testAction(
actions.updateAssignees,
[userDataMock.id],
{ state: noteableDataMock },
[{ type: mutationTypes.UPDATE_ASSIGNEES, payload: [userDataMock.id] }],
[],
done,
);
});
});
});
......@@ -805,4 +805,16 @@ describe('Notes Store mutations', () => {
expect(state.batchSuggestionsInfo.length).toEqual(0);
});
});
describe('UPDATE_ASSIGNEES', () => {
it('should update assignees', () => {
const state = {
noteableData: noteableDataMock,
};
mutations.UPDATE_ASSIGNEES(state, [userDataMock.id]);
expect(state.noteableData.assignees).toEqual([userDataMock.id]);
});
});
});
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