diff options
-rw-r--r-- | app/assets/javascripts/boards/components/board_list.vue | 15 | ||||
-rw-r--r-- | app/assets/javascripts/boards/stores/boards_store.js | 15 | ||||
-rw-r--r-- | app/models/list.rb | 6 | ||||
-rw-r--r-- | app/views/shared/boards/components/_board.html.haml | 3 | ||||
-rw-r--r-- | lib/api/boards.rb | 6 | ||||
-rw-r--r-- | lib/api/boards_responses.rb | 16 | ||||
-rw-r--r-- | lib/api/group_boards.rb | 6 |
7 files changed, 50 insertions, 17 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 5c7565234d8..3e610a4088c 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -112,12 +112,20 @@ export default { if (e.target) { const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list'); const toBoardType = containerEl.dataset.boardType; + const cloneActions = { + label: ['milestone', 'assignee'], + assignee: ['milestone', 'label'], + milestone: ['label', 'assignee'], + }; if (toBoardType) { const fromBoardType = this.list.type; + // For each list we check if the destination list is + // a the list were we should clone the issue + const shouldClone = Object.entries(cloneActions).some(entry => ( + fromBoardType === entry[0] && entry[1].includes(toBoardType))); - if ((fromBoardType === 'assignee' && toBoardType === 'label') || - (fromBoardType === 'label' && toBoardType === 'assignee')) { + if (shouldClone) { return 'clone'; } } @@ -145,7 +153,8 @@ export default { }); }, onUpdate: (e) => { - const sortedArray = this.sortable.toArray().filter(id => id !== '-1'); + const sortedArray = this.sortable.toArray() + .filter(id => id !== '-1'); gl.issueBoards.BoardsStore .moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray); }, diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 76467564608..957114cf420 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -108,6 +108,16 @@ gl.issueBoards.BoardsStore = { issue.findAssignee(listTo.assignee)) { const targetIssue = listTo.findIssue(issue.id); targetIssue.removeAssignee(listFrom.assignee); + } else if (listTo.type === 'milestone') { + const currentMilestone = issue.milestone; + const currentLists = this.state.lists + .filter(list => (list.type === 'milestone' && list.id !== listTo.id)) + .filter(list => list.issues.some(listIssue => issue.id === listIssue.id)); + + issue.removeMilestone(currentMilestone); + issue.addMilestone(listTo.milestone); + currentLists.forEach(currentList => currentList.removeIssue(issue)); + listTo.addIssue(issue, listFrom, newIndex); } else { // Add to new lists issues if it doesn't already exist listTo.addIssue(issue, listFrom, newIndex); @@ -125,6 +135,9 @@ gl.issueBoards.BoardsStore = { } else if (listTo.type === 'backlog' && listFrom.type === 'assignee') { issue.removeAssignee(listFrom.assignee); listFrom.removeIssue(issue); + } else if (listTo.type === 'backlog' && listFrom.type === 'milestone') { + issue.removeMilestone(listFrom.milestone); + listFrom.removeIssue(issue); } else if (this.shouldRemoveIssue(listFrom, listTo)) { listFrom.removeIssue(issue); } @@ -144,7 +157,7 @@ gl.issueBoards.BoardsStore = { }, findList (key, val, type = 'label') { const filteredList = this.state.lists.filter((list) => { - const byType = type ? (list.type === type) || (list.type === 'assignee') : true; + const byType = type ? (list.type === type) || (list.type === 'assignee') || (list.type === 'milestone') : true; return list[key] === val && byType; }); diff --git a/app/models/list.rb b/app/models/list.rb index eabe3ffccbb..1a30acc83cf 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -4,7 +4,7 @@ class List < ActiveRecord::Base belongs_to :board belongs_to :label - enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3 } + enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 } validates :board, :list_type, presence: true validates :label, :position, presence: true, if: :label? @@ -27,11 +27,11 @@ class List < ActiveRecord::Base end def destroyable? - label? + self.class.destroyable_types.include?(list_type&.to_sym) end def movable? - label? + self.class.movable_types.include?(list_type&.to_sym) end def title diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index b35877e5518..e26f5260e5b 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -6,12 +6,13 @@ %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable", ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }", "aria-hidden": "true" } + = render_if_exists "shared/boards/components/list_milestone" %a.user-avatar-link.js-no-trigger{ "v-if": "list.type === \"assignee\"", ":href": "list.assignee.path" } -# haml-lint:disable AltText %img.avatar.s20.has-tooltip{ height: "20", width: "20", ":src": "list.assignee.avatar", ":alt": "list.assignee.name" } - %span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"", + %span.board-title-text.has-tooltip.block-truncated{ "v-if": "list.type !== \"label\"", ":title" => '((list.label && list.label.description) || list.title || "")', data: { container: "body" } } {{ list.title }} diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 086d39d5070..0f89414148b 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -71,12 +71,10 @@ module API success Entities::List end params do - requires :label_id, type: Integer, desc: 'The ID of an existing label' + use :list_creation_params end post '/lists' do - unless available_labels_for(user_project).exists?(params[:label_id]) - render_api_error!({ error: 'Label not found!' }, 400) - end + authorize_list_type_resource! authorize!(:admin_list, user_project) diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index ead0943a74d..7e873012efe 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -14,7 +14,7 @@ module API def create_list create_list_service = - ::Boards::Lists::CreateService.new(board_parent, current_user, { label_id: params[:label_id] }) + ::Boards::Lists::CreateService.new(board_parent, current_user, create_list_params) list = create_list_service.execute(board) @@ -25,6 +25,10 @@ module API end end + def create_list_params + params.slice(:label_id) + end + def move_list(list) move_list_service = ::Boards::Lists::MoveService.new(board_parent, current_user, { position: params[:position].to_i }) @@ -44,6 +48,16 @@ module API end end end + + def authorize_list_type_resource! + unless available_labels_for(board_parent).exists?(params[:label_id]) + render_api_error!({ error: 'Label not found!' }, 400) + end + end + + params :list_creation_params do + requires :label_id, type: Integer, desc: 'The ID of an existing label' + end end end end diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index aa9fff25fc8..3832cdc10a8 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -70,12 +70,10 @@ module API success Entities::List end params do - requires :label_id, type: Integer, desc: 'The ID of an existing label' + use :list_creation_params end post '/lists' do - unless available_labels_for(board_parent).exists?(params[:label_id]) - render_api_error!({ error: 'Label not found!' }, 400) - end + authorize_list_type_resource! authorize!(:admin_list, user_group) |