FE backport of group boards to reduce CE conflicts
This commit is contained in:
parent
b9aa55e1ea
commit
c28d52a3a5
12 changed files with 116 additions and 53 deletions
|
@ -6,7 +6,8 @@ const Api = {
|
||||||
namespacesPath: '/api/:version/namespaces.json',
|
namespacesPath: '/api/:version/namespaces.json',
|
||||||
groupProjectsPath: '/api/:version/groups/:id/projects.json',
|
groupProjectsPath: '/api/:version/groups/:id/projects.json',
|
||||||
projectsPath: '/api/:version/projects.json',
|
projectsPath: '/api/:version/projects.json',
|
||||||
labelsPath: '/:namespace_path/:project_path/labels',
|
projectLabelsPath: '/:namespace_path/:project_path/labels',
|
||||||
|
groupLabelsPath: '/groups/:namespace_path/labels',
|
||||||
licensePath: '/api/:version/templates/licenses/:key',
|
licensePath: '/api/:version/templates/licenses/:key',
|
||||||
gitignorePath: '/api/:version/templates/gitignores/:key',
|
gitignorePath: '/api/:version/templates/gitignores/:key',
|
||||||
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
||||||
|
@ -74,9 +75,14 @@ const Api = {
|
||||||
},
|
},
|
||||||
|
|
||||||
newLabel(namespacePath, projectPath, data, callback) {
|
newLabel(namespacePath, projectPath, data, callback) {
|
||||||
const url = Api.buildUrl(Api.labelsPath)
|
let url;
|
||||||
|
if (projectPath) {
|
||||||
|
url = Api.buildUrl(Api.projectLabelsPath)
|
||||||
.replace(':namespace_path', namespacePath)
|
.replace(':namespace_path', namespacePath)
|
||||||
.replace(':project_path', projectPath);
|
.replace(':project_path', projectPath);
|
||||||
|
} else {
|
||||||
|
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
|
||||||
|
}
|
||||||
return $.ajax({
|
return $.ajax({
|
||||||
url,
|
url,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
|
|
|
@ -53,7 +53,8 @@ $(() => {
|
||||||
data: {
|
data: {
|
||||||
state: Store.state,
|
state: Store.state,
|
||||||
loading: true,
|
loading: true,
|
||||||
endpoint: $boardApp.dataset.endpoint,
|
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
|
||||||
|
listsEndpoint: $boardApp.dataset.listsEndpoint,
|
||||||
boardId: $boardApp.dataset.boardId,
|
boardId: $boardApp.dataset.boardId,
|
||||||
disabled: $boardApp.dataset.disabled === 'true',
|
disabled: $boardApp.dataset.disabled === 'true',
|
||||||
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
||||||
|
@ -68,7 +69,13 @@ $(() => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
gl.boardService = new BoardService({
|
||||||
|
boardsEndpoint: this.boardsEndpoint,
|
||||||
|
listsEndpoint: this.listsEndpoint,
|
||||||
|
bulkUpdatePath: this.bulkUpdatePath,
|
||||||
|
boardId: this.boardId,
|
||||||
|
});
|
||||||
|
Store.rootPath = this.boardsEndpoint;
|
||||||
|
|
||||||
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
||||||
this.filterManager.setup();
|
this.filterManager.setup();
|
||||||
|
@ -112,19 +119,21 @@ $(() => {
|
||||||
gl.IssueBoardsSearch = new Vue({
|
gl.IssueBoardsSearch = new Vue({
|
||||||
el: document.getElementById('js-add-list'),
|
el: document.getElementById('js-add-list'),
|
||||||
data: {
|
data: {
|
||||||
filters: Store.state.filters
|
filters: Store.state.filters,
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
gl.issueBoards.newListDropdownInit();
|
gl.issueBoards.newListDropdownInit();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
gl.IssueBoardsModalAddBtn = new Vue({
|
gl.IssueBoardsModalAddBtn = new Vue({
|
||||||
mixins: [gl.issueBoards.ModalMixins],
|
mixins: [gl.issueBoards.ModalMixins],
|
||||||
el: document.getElementById('js-add-issues-btn'),
|
el: document.getElementById('js-add-issues-btn'),
|
||||||
data: {
|
data() {
|
||||||
|
return {
|
||||||
modal: ModalStore.store,
|
modal: ModalStore.store,
|
||||||
store: Store.state,
|
store: Store.state,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
disabled() {
|
disabled() {
|
||||||
|
@ -133,6 +142,9 @@ $(() => {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
disabled() {
|
disabled() {
|
||||||
|
if (!this.store) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return !this.store.lists.filter(list => !list.preset).length;
|
return !this.store.lists.filter(list => !list.preset).length;
|
||||||
},
|
},
|
||||||
tooltipTitle() {
|
tooltipTitle() {
|
||||||
|
@ -145,7 +157,7 @@ $(() => {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateTooltip() {
|
updateTooltip() {
|
||||||
const $tooltip = $(this.$el);
|
const $tooltip = $(this.$refs.addIssuesButton);
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
|
@ -165,16 +177,19 @@ $(() => {
|
||||||
this.updateTooltip();
|
this.updateTooltip();
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
|
<div class="board-extra-actions">
|
||||||
<button
|
<button
|
||||||
class="btn btn-create pull-right prepend-left-10"
|
class="btn btn-create prepend-left-10"
|
||||||
type="button"
|
type="button"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
|
ref="addIssuesButton"
|
||||||
:class="{ 'disabled': disabled }"
|
:class="{ 'disabled': disabled }"
|
||||||
:title="tooltipTitle"
|
:title="tooltipTitle"
|
||||||
:aria-disabled="disabled"
|
:aria-disabled="disabled"
|
||||||
@click="openModal">
|
@click="openModal">
|
||||||
Add issues
|
Add issues
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
||||||
this.showIssueForm = !this.showIssueForm;
|
this.showIssueForm = !this.showIssueForm;
|
||||||
},
|
},
|
||||||
onScroll() {
|
onScroll() {
|
||||||
if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
|
if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||||
this.loadNextPage();
|
this.loadNextPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -165,11 +165,9 @@ export default {
|
||||||
v-if="loading">
|
v-if="loading">
|
||||||
<loading-icon />
|
<loading-icon />
|
||||||
</div>
|
</div>
|
||||||
<transition name="slide-down">
|
|
||||||
<board-new-issue
|
<board-new-issue
|
||||||
:list="list"
|
:list="list"
|
||||||
v-if="list.type !== 'closed' && showIssueForm"/>
|
v-if="list.type !== 'closed' && showIssueForm"/>
|
||||||
</transition>
|
|
||||||
<ul
|
<ul
|
||||||
class="board-list"
|
class="board-list"
|
||||||
v-show="!loading"
|
v-show="!loading"
|
||||||
|
|
|
@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
|
||||||
export default {
|
export default {
|
||||||
name: 'BoardNewIssue',
|
name: 'BoardNewIssue',
|
||||||
props: {
|
props: {
|
||||||
list: Object,
|
list: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -67,7 +67,10 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
||||||
return `${this.issueLinkBase}/${this.issue.id}`;
|
return `${this.issueLinkBase}/${this.issue.id}`;
|
||||||
},
|
},
|
||||||
issueId() {
|
issueId() {
|
||||||
return `#${this.issue.id}`;
|
if (this.issue.iid) {
|
||||||
|
return `#${this.issue.iid}`;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
showLabelFooter() {
|
showLabelFooter() {
|
||||||
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
|
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
|
||||||
|
@ -143,7 +146,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
|
||||||
:title="issue.title">{{ issue.title }}</a>
|
:title="issue.title">{{ issue.title }}</a>
|
||||||
<span
|
<span
|
||||||
class="card-number"
|
class="card-number"
|
||||||
v-if="issue.id"
|
v-if="issueId"
|
||||||
>
|
>
|
||||||
{{ issueId }}
|
{{ issueId }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -29,7 +29,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
|
||||||
const firstListIndex = 1;
|
const firstListIndex = 1;
|
||||||
const list = this.modal.selectedList || this.state.lists[firstListIndex];
|
const list = this.modal.selectedList || this.state.lists[firstListIndex];
|
||||||
const selectedIssues = ModalStore.getSelectedIssues();
|
const selectedIssues = ModalStore.getSelectedIssues();
|
||||||
const issueIds = selectedIssues.map(issue => issue.globalId);
|
const issueIds = selectedIssues.map(issue => issue.id);
|
||||||
|
|
||||||
// Post the data to the backend
|
// Post the data to the backend
|
||||||
gl.boardService.bulkUpdate(issueIds, {
|
gl.boardService.bulkUpdate(issueIds, {
|
||||||
|
|
|
@ -27,7 +27,7 @@ gl.issueBoards.newListDropdownInit = () => {
|
||||||
|
|
||||||
$this.glDropdown({
|
$this.glDropdown({
|
||||||
data(term, callback) {
|
data(term, callback) {
|
||||||
$.get($this.attr('data-labels'))
|
$.get($this.attr('data-list-labels-path'))
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
callback(resp);
|
callback(resp);
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,17 +18,32 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
issueUpdate: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
updateUrl() {
|
||||||
|
return this.issueUpdate;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeIssue() {
|
removeIssue() {
|
||||||
const issue = this.issue;
|
const issue = this.issue;
|
||||||
const lists = issue.getLists();
|
const lists = issue.getLists();
|
||||||
const labelIds = lists.map(list => list.label.id);
|
const listLabelIds = lists.map(list => list.label.id);
|
||||||
|
const labelIds = this.issue.labels
|
||||||
|
.map(label => label.id)
|
||||||
|
.filter(id => !listLabelIds.includes(id));
|
||||||
|
|
||||||
// Post the remove data
|
// Post the remove data
|
||||||
gl.boardService.bulkUpdate([issue.globalId], {
|
const data = {
|
||||||
remove_label_ids: labelIds,
|
issue: {
|
||||||
}).catch(() => {
|
label_ids: labelIds,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Vue.http.patch(this.updateUrl, data).catch(() => {
|
||||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||||
|
|
||||||
lists.forEach((list) => {
|
lists.forEach((list) => {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import Vue from 'vue';
|
||||||
|
|
||||||
class ListIssue {
|
class ListIssue {
|
||||||
constructor (obj, defaultAvatar) {
|
constructor (obj, defaultAvatar) {
|
||||||
this.globalId = obj.id;
|
this.id = obj.id;
|
||||||
this.id = obj.iid;
|
this.iid = obj.iid;
|
||||||
this.title = obj.title;
|
this.title = obj.title;
|
||||||
this.confidential = obj.confidential;
|
this.confidential = obj.confidential;
|
||||||
this.dueDate = obj.due_date;
|
this.dueDate = obj.due_date;
|
||||||
|
|
|
@ -4,6 +4,7 @@ class ListLabel {
|
||||||
constructor (obj) {
|
constructor (obj) {
|
||||||
this.id = obj.id;
|
this.id = obj.id;
|
||||||
this.title = obj.title;
|
this.title = obj.title;
|
||||||
|
this.type = obj.type;
|
||||||
this.color = obj.color;
|
this.color = obj.color;
|
||||||
this.textColor = obj.text_color;
|
this.textColor = obj.text_color;
|
||||||
this.description = obj.description;
|
this.description = obj.description;
|
||||||
|
|
|
@ -110,11 +110,12 @@ class List {
|
||||||
return gl.boardService.newIssue(this.id, issue)
|
return gl.boardService.newIssue(this.id, issue)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
issue.id = data.iid;
|
issue.id = data.id;
|
||||||
|
issue.iid = data.iid;
|
||||||
|
|
||||||
if (this.issuesSize > 1) {
|
if (this.issuesSize > 1) {
|
||||||
const moveBeforeIid = this.issues[1].id;
|
const moveBeforeId = this.issues[1].id;
|
||||||
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
|
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -126,19 +127,19 @@ class List {
|
||||||
}
|
}
|
||||||
|
|
||||||
addIssue (issue, listFrom, newIndex) {
|
addIssue (issue, listFrom, newIndex) {
|
||||||
let moveBeforeIid = null;
|
let moveBeforeId = null;
|
||||||
let moveAfterIid = null;
|
let moveAfterId = null;
|
||||||
|
|
||||||
if (!this.findIssue(issue.id)) {
|
if (!this.findIssue(issue.id)) {
|
||||||
if (newIndex !== undefined) {
|
if (newIndex !== undefined) {
|
||||||
this.issues.splice(newIndex, 0, issue);
|
this.issues.splice(newIndex, 0, issue);
|
||||||
|
|
||||||
if (this.issues[newIndex - 1]) {
|
if (this.issues[newIndex - 1]) {
|
||||||
moveBeforeIid = this.issues[newIndex - 1].id;
|
moveBeforeId = this.issues[newIndex - 1].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.issues[newIndex + 1]) {
|
if (this.issues[newIndex + 1]) {
|
||||||
moveAfterIid = this.issues[newIndex + 1].id;
|
moveAfterId = this.issues[newIndex + 1].id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.issues.push(issue);
|
this.issues.push(issue);
|
||||||
|
@ -151,30 +152,30 @@ class List {
|
||||||
if (listFrom) {
|
if (listFrom) {
|
||||||
this.issuesSize += 1;
|
this.issuesSize += 1;
|
||||||
|
|
||||||
this.updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid);
|
this.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveIssue (issue, oldIndex, newIndex, moveBeforeIid, moveAfterIid) {
|
moveIssue (issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
|
||||||
this.issues.splice(oldIndex, 1);
|
this.issues.splice(oldIndex, 1);
|
||||||
this.issues.splice(newIndex, 0, issue);
|
this.issues.splice(newIndex, 0, issue);
|
||||||
|
|
||||||
gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid)
|
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// TODO: handle request error
|
// TODO: handle request error
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
|
updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) {
|
||||||
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid)
|
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// TODO: handle request error
|
// TODO: handle request error
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findIssue (id) {
|
findIssue (id) {
|
||||||
return this.issues.filter(issue => issue.id === id)[0];
|
return this.issues.find(issue => issue.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeIssue (removeIssue) {
|
removeIssue (removeIssue) {
|
||||||
|
|
|
@ -145,7 +145,7 @@ describe('Api', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('newLabel', () => {
|
fdescribe('newLabel', () => {
|
||||||
it('creates a new label', (done) => {
|
it('creates a new label', (done) => {
|
||||||
const namespace = 'some namespace';
|
const namespace = 'some namespace';
|
||||||
const project = 'some project';
|
const project = 'some project';
|
||||||
|
@ -167,6 +167,27 @@ describe('Api', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('creates a new group label', (done) => {
|
||||||
|
const namespace = 'some namespace';
|
||||||
|
const labelData = { some: 'data' };
|
||||||
|
const expectedUrl = `${dummyUrlRoot}/${namespace}/labels`;
|
||||||
|
const expectedData = {
|
||||||
|
label: labelData,
|
||||||
|
};
|
||||||
|
spyOn(jQuery, 'ajax').and.callFake((request) => {
|
||||||
|
expect(request.url).toEqual(expectedUrl);
|
||||||
|
expect(request.dataType).toEqual('json');
|
||||||
|
expect(request.type).toEqual('POST');
|
||||||
|
expect(request.data).toEqual(expectedData);
|
||||||
|
return sendDummyResponse();
|
||||||
|
});
|
||||||
|
|
||||||
|
Api.newLabel(namespace, null, labelData, (response) => {
|
||||||
|
expect(response).toBe(dummyResponse);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('groupProjects', () => {
|
describe('groupProjects', () => {
|
||||||
|
|
Loading…
Reference in a new issue