Commit 862e0eb5 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'multiple-assignees-issue-board-sidebar' into 'multiple_assignees_review'

Add multiple assignees to issue board sidebar

See merge request !1731
parents 973f7588 a2bebc74
......@@ -12,7 +12,7 @@ require('./models/issue');
require('./models/label');
require('./models/list');
require('./models/milestone');
require('./models/user');
require('./models/assignee');
require('./stores/boards_store');
require('./stores/modal_store');
require('./services/board_service');
......
......@@ -5,6 +5,10 @@
/* global Sidebar */
import Vue from 'vue';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees';
require('./sidebar/remove_issue');
......@@ -23,6 +27,7 @@ require('./sidebar/remove_issue');
detail: Store.detail,
issue: {},
list: {},
loadingAssignees: false,
};
},
computed: {
......@@ -41,6 +46,10 @@ require('./sidebar/remove_issue');
this.issue = this.detail.issue;
this.list = this.detail.list;
this.$nextTick(() => {
this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate;
});
},
deep: true
},
......@@ -56,7 +65,46 @@ require('./sidebar/remove_issue');
methods: {
closeSidebar () {
this.detail.issue = {};
}
},
assignSelf () {
// Notify gl dropdown that we are now assigning to current user
this.$refs.assigneeBlock.dispatchEvent(new Event('assignYourself'));
this.addAssignee(this.currentUser);
this.saveAssignees();
},
removeAssignee (a) {
gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a);
},
addAssignee (a) {
gl.issueBoards.BoardsStore.detail.issue.addAssignee(a);
},
removeAllAssignees () {
gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees();
},
saveAssignees () {
this.loadingAssignees = true;
gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint)
.then(() => this.loadingAssignees = false)
.catch(() => {
this.loadingAssignees = false;
return new Flash('An error occurred while saving assignees');
});
},
},
created () {
// Get events from glDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$on('sidebar.saveAssignees', this.saveAssignees);
},
beforeDestroy() {
eventHub.$off('sidebar.removeAssignee', this.removeAssignee);
eventHub.$off('sidebar.addAssignee', this.addAssignee);
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
},
mounted () {
new IssuableContext(this.currentUser);
......@@ -68,6 +116,8 @@ require('./sidebar/remove_issue');
},
components: {
removeBtn: gl.issueBoards.RemoveIssueBtn,
'assignee-title': AssigneeTitle,
assignees: Assignees,
},
});
})();
......@@ -158,7 +158,7 @@ import eventHub from '../eventhub';
>
<img
class="avatar avatar-inline s20"
:src="assignee.avatar"
:src="assignee.avatarUrl"
width="20"
height="20"
:alt="avatarUrlTitle(assignee)"
......
/* eslint-disable no-unused-vars */
class ListUser {
class ListAssignee {
constructor(user) {
this.id = user.id;
this.name = user.name;
this.username = user.username;
this.avatar = user.avatar_url;
this.avatarUrl = user.avatar_url;
}
}
window.ListUser = ListUser;
window.ListAssignee = ListAssignee;
/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */
/* global ListLabel */
/* global ListMilestone */
/* global ListUser */
/* global ListAssignee */
import Vue from 'vue';
......@@ -14,6 +14,7 @@ class ListIssue {
this.dueDate = obj.due_date;
this.subscribed = obj.subscribed;
this.labels = [];
this.assignees = [];
this.selected = false;
this.position = obj.relative_position || Infinity;
this.milestone_id = obj.milestone_id;
......@@ -26,7 +27,7 @@ class ListIssue {
this.labels.push(new ListLabel(label));
});
this.assignees = obj.assignees.map(a => new ListUser(a));
this.assignees = obj.assignees.map(a => new ListAssignee(a));
}
addLabel (label) {
......@@ -49,6 +50,26 @@ class ListIssue {
labels.forEach(this.removeLabel.bind(this));
}
addAssignee (assignee) {
if (!this.findAssignee(assignee)) {
this.assignees.push(new ListAssignee(assignee));
}
}
findAssignee (findAssignee) {
return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0];
}
removeAssignee (removeAssignee) {
if (removeAssignee) {
this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id);
}
}
removeAllAssignees () {
this.assignees = [];
}
getLists () {
return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
}
......@@ -58,7 +79,7 @@ class ListIssue {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
due_date: this.dueDate,
assignee_id: this.assignee ? this.assignee.id : null,
assignee_ids: this.assignees.length > 0 ? this.assignees.map((u) => u.id) : [0],
label_ids: this.labels.map((label) => label.id)
}
};
......
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
/* global Issuable */
/* global ListUser */
import Vue from 'vue';
import eventHub from './sidebar/event_hub';
(function() {
......@@ -81,14 +79,6 @@ import eventHub from './sidebar/event_hub';
.get();
};
var updateIssueBoardsIssue = function () {
$loading.removeClass('hidden').fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(function () {
$loading.fadeOut();
});
};
$('.assign-to-me-link').on('click', (e) => {
e.preventDefault();
$(e.currentTarget).hide();
......@@ -98,22 +88,11 @@ import eventHub from './sidebar/event_hub';
$dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default');
});
$block.on('click', '.js-assign-yourself', function(e) {
$block.on('click', '.js-assign-yourself', (e) => {
e.preventDefault();
if ($dropdown.hasClass('js-issue-board-sidebar')) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({
id: _this.currentUser.id,
username: _this.currentUser.username,
name: _this.currentUser.name,
avatar_url: _this.currentUser.avatar_url
}));
updateIssueBoardsIssue();
} else {
return assignTo(_this.currentUser.id);
}
return assignTo(_this.currentUser.id);
});
assignTo = function(selected) {
var data;
data = {};
......@@ -289,7 +268,6 @@ import eventHub from './sidebar/event_hub';
return $value.css('display', '');
},
multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(options) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
......@@ -367,19 +345,6 @@ import eventHub from './sidebar/event_hub';
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (user.id) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({
id: user.id,
username: user.username,
name: user.name,
avatar_url: user.avatar_url
}));
} else {
Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'assignee');
}
updateIssueBoardsIssue();
} else if (!$dropdown.hasClass('js-multiselect')) {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected);
......
.block.assignee
.title.hide-collapsed
Assignee
- if can?(current_user, :admin_issue, @project)
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "edit-link pull-right"
.value.hide-collapsed
%span.assign-yourself.no-value{ "v-if" => "!issue.assignee" }
No assignee
- if can?(current_user, :admin_issue, @project)
\-
%a.js-assign-yourself{ href: "#" }
assign yourself
%a.author_link.bold{ ":href" => "'#{root_url}' + issue.assignee.username",
"v-if" => "issue.assignee" }
%img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar",
width: "32", alt: "Avatar" }
%span.author
{{ issue.assignee.name }}
%span.username
= precede "@" do
{{ issue.assignee.username }}
.block.assignee{ ref: "assigneeBlock" }
%template{ "v-if" => "issue.assignees"}
%assignee-title{ ":number-of-assignees" => "issue.assignees.length",
":loading" => "loadingAssignees",
":editable" => can?(current_user, :admin_issue, @project) }
%assignees{ class: "value", "root-path" => "#{root_url}",
":users" => "issue.assignees",
"@assign-self" => "assignSelf" }
- if can?(current_user, :admin_issue, @project)
.selectbox.hide-collapsed
%input{ type: "hidden",
name: "issue[assignee_id]",
id: "issue_assignee_id",
":value" => "issue.assignee.id",
"v-if" => "issue.assignee" }
name: "issue[assignee_ids][]",
":value" => "assignee.id",
"v-if" => "issue.assignees",
"v-for" => "assignee in issue.assignees" }
.dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", field_name: "issue[assignee_id]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true" },
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", dropdown: { header: 'Assignee(s)'} },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee
Select assignee(s)
= icon("chevron-down")
.dropdown-menu.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to")
= dropdown_filter("Search users")
= dropdown_content
......
/* global ListUser */
/* global ListAssignee */
/* global ListLabel */
/* global listObj */
/* global ListIssue */
......@@ -14,7 +14,7 @@ require('~/boards/components/issue_card_inner');
require('./mock_data');
describe('Issue card component', () => {
const user = new ListUser({
const user = new ListAssignee({
id: 1,
name: 'testing 123',
username: 'test',
......
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