[Backport] View summed weights of issues in board column
This commit is contained in:
parent
4f08343463
commit
5815c5b48a
12 changed files with 113 additions and 46 deletions
|
@ -2,6 +2,9 @@
|
|||
|
||||
import Sortable from 'sortablejs';
|
||||
import Vue from 'vue';
|
||||
import { n__ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import Tooltip from '~/vue_shared/directives/tooltip';
|
||||
import AccessorUtilities from '../../lib/utils/accessor';
|
||||
import boardList from './board_list.vue';
|
||||
import BoardBlankState from './board_blank_state.vue';
|
||||
|
@ -17,6 +20,10 @@ gl.issueBoards.Board = Vue.extend({
|
|||
boardList,
|
||||
'board-delete': gl.issueBoards.BoardDelete,
|
||||
BoardBlankState,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
Tooltip,
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
|
@ -46,6 +53,12 @@ gl.issueBoards.Board = Vue.extend({
|
|||
filter: Store.filter,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
counterTooltip() {
|
||||
const { issuesSize } = this.list;
|
||||
return `${n__('%d issue', '%d issues', issuesSize)}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
filter: {
|
||||
handler() {
|
||||
|
|
|
@ -136,6 +136,8 @@ class List {
|
|||
}
|
||||
|
||||
this.createIssues(data.issues);
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -125,11 +125,17 @@ gl.issueBoards.BoardsStore = {
|
|||
} else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
|
||||
issue.removeAssignee(listFrom.assignee);
|
||||
listFrom.removeIssue(issue);
|
||||
} else if ((listTo.type !== 'label' && listFrom.type === 'assignee') ||
|
||||
(listTo.type !== 'assignee' && listFrom.type === 'label')) {
|
||||
} else if (this.shouldRemoveIssue(listFrom, listTo)) {
|
||||
listFrom.removeIssue(issue);
|
||||
}
|
||||
},
|
||||
shouldRemoveIssue(listFrom, listTo) {
|
||||
return (
|
||||
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
|
||||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
|
||||
(listFrom.type === 'backlog')
|
||||
);
|
||||
},
|
||||
moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
|
||||
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
|
||||
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
|
||||
|
|
|
@ -444,3 +444,5 @@ textarea {
|
|||
color: $placeholder-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.lh-100 { line-height: 1; }
|
||||
|
|
|
@ -205,7 +205,7 @@
|
|||
|
||||
.board-title {
|
||||
margin: 0;
|
||||
padding: 12px $gl-padding;
|
||||
padding: $gl-padding-8 $gl-padding;
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid $border-color;
|
||||
display: flex;
|
||||
|
|
|
@ -1,29 +1,11 @@
|
|||
.issue-count-badge {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
height: 24px;
|
||||
border-radius: $border-radius-base;
|
||||
border: 1px solid $border-color;
|
||||
padding: 5px $gl-padding-8;
|
||||
}
|
||||
|
||||
.issue-count-badge-count {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $border-radius-base;
|
||||
line-height: 1;
|
||||
|
||||
&.has-btn {
|
||||
border-right: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issue-count-badge-add-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0 $border-radius-base $border-radius-base 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
|
|
@ -12,8 +12,9 @@ module Boards
|
|||
skip_before_action :authenticate_user!, only: [:index]
|
||||
|
||||
def index
|
||||
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
|
||||
issues = issues.page(params[:page]).per(params[:per] || 20)
|
||||
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
|
||||
issues = list_service.execute
|
||||
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
|
||||
make_sure_position_is_set(issues) if Gitlab::Database.read_write?
|
||||
issues = issues.preload(:project,
|
||||
:milestone,
|
||||
|
@ -22,10 +23,7 @@ module Boards
|
|||
notes: [:award_emoji, :author]
|
||||
)
|
||||
|
||||
render json: {
|
||||
issues: serialize_as_json(issues),
|
||||
size: issues.total_count
|
||||
}
|
||||
render_issues(issues, list_service.metadata)
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -51,6 +49,13 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
def render_issues(issues, metadata)
|
||||
data = { issues: serialize_as_json(issues) }
|
||||
data.merge!(metadata)
|
||||
|
||||
render json: data
|
||||
end
|
||||
|
||||
def make_sure_position_is_set(issues)
|
||||
issues.each do |issue|
|
||||
issue.move_to_end && issue.save unless issue.relative_position
|
||||
|
|
|
@ -3,14 +3,35 @@
|
|||
module Boards
|
||||
module Issues
|
||||
class ListService < Boards::BaseService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def execute
|
||||
issues = IssuesFinder.new(current_user, filter_params).execute
|
||||
issues = filter(issues)
|
||||
issues.order_by_position_and_priority
|
||||
fetch_issues.order_by_position_and_priority
|
||||
end
|
||||
|
||||
def metadata
|
||||
keys = metadata_fields.keys
|
||||
columns = metadata_fields.values_at(*keys).join(', ')
|
||||
results = Issue.where(id: fetch_issues.select('issues.id')).pluck(columns)
|
||||
|
||||
Hash[keys.zip(results.flatten)]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metadata_fields
|
||||
{ size: 'COUNT(*)' }
|
||||
end
|
||||
|
||||
# We memoize the query here since the finder methods we use are quite complex. This does not memoize the result of the query.
|
||||
def fetch_issues
|
||||
strong_memoize(:fetch_issues) do
|
||||
issues = IssuesFinder.new(current_user, filter_params).execute
|
||||
|
||||
filter(issues).reorder(nil)
|
||||
end
|
||||
end
|
||||
|
||||
def filter(issues)
|
||||
issues = without_board_labels(issues) unless list&.movable? || list&.closed?
|
||||
issues = with_list_label(issues) if list&.label?
|
||||
|
|
|
@ -32,17 +32,21 @@
|
|||
"v-if" => "!list.preset && list.id" }
|
||||
%button.board-delete.has-tooltip.float-right{ type: "button", title: _("Delete list"), "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
|
||||
= icon("trash")
|
||||
.issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"' }
|
||||
%span.issue-count-badge-count.float-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
|
||||
.issue-count-badge.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", "v-tooltip": true, data: { placement: "top" } }
|
||||
%span.issue-count-badge-count
|
||||
%icon.mr-1{ name: "issues" }
|
||||
{{ list.issuesSize }}
|
||||
- if can?(current_user, :admin_list, current_board_parent)
|
||||
%button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
|
||||
"@click" => "showNewIssueForm",
|
||||
"v-if" => 'list.type !== "closed"',
|
||||
"aria-label" => _("New issue"),
|
||||
"title" => _("New issue"),
|
||||
data: { placement: "top", container: "body" } }
|
||||
= icon("plus", class: "js-no-trigger-collapse")
|
||||
= render_if_exists "shared/boards/components/list_weight"
|
||||
|
||||
- if can?(current_user, :admin_list, current_board_parent)
|
||||
%button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button",
|
||||
"@click" => "showNewIssueForm",
|
||||
"v-if" => 'list.type !== "closed"',
|
||||
"aria-label" => _("New issue"),
|
||||
"title" => _("New issue"),
|
||||
data: { placement: "top", container: "body" } }
|
||||
= icon("plus", class: "js-no-trigger-collapse")
|
||||
|
||||
%board-list{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"',
|
||||
":list" => "list",
|
||||
":issues" => "list.issues",
|
||||
|
|
|
@ -42,7 +42,7 @@ describe Boards::IssuesController do
|
|||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('issues')
|
||||
expect(parsed_response.length).to eq 2
|
||||
expect(parsed_response['issues'].length).to eq 2
|
||||
expect(development.issues.map(&:relative_position)).not_to include(nil)
|
||||
end
|
||||
|
||||
|
@ -80,7 +80,7 @@ describe Boards::IssuesController do
|
|||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('issues')
|
||||
expect(parsed_response.length).to eq 2
|
||||
expect(parsed_response['issues'].length).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -161,6 +161,28 @@ describe('Store', () => {
|
|||
}, 0);
|
||||
});
|
||||
|
||||
it('moves an issue from backlog to a list', (done) => {
|
||||
const backlog = gl.issueBoards.BoardsStore.addList({
|
||||
...listObj,
|
||||
list_type: 'backlog',
|
||||
});
|
||||
const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
|
||||
|
||||
expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(backlog.issues.length).toBe(1);
|
||||
expect(listTwo.issues.length).toBe(1);
|
||||
|
||||
gl.issueBoards.BoardsStore.moveIssueToList(backlog, listTwo, backlog.findIssue(1));
|
||||
|
||||
expect(backlog.issues.length).toBe(0);
|
||||
expect(listTwo.issues.length).toBe(1);
|
||||
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('moves issue to top of another list', (done) => {
|
||||
const listOne = gl.issueBoards.BoardsStore.addList(listObj);
|
||||
const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
|
||||
|
|
|
@ -7,6 +7,16 @@ shared_examples 'issues list service' do
|
|||
described_class.new(parent, user, params).execute
|
||||
end
|
||||
|
||||
context '#metadata' do
|
||||
it 'returns issues count for list' do
|
||||
params = { board_id: board.id, id: list1.id }
|
||||
|
||||
metadata = described_class.new(parent, user, params).metadata
|
||||
|
||||
expect(metadata[:size]).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'issues are ordered by priority' do
|
||||
it 'returns opened issues when list_id is missing' do
|
||||
params = { board_id: board.id }
|
||||
|
|
Loading…
Reference in a new issue