Move open items to another sprint

stable
Daniel García 2018-11-14 13:52:56 +01:00 committed by Alex Hermida
parent d8d05bea14
commit 09fd3a9d72
17 changed files with 609 additions and 3 deletions

View File

@ -163,7 +163,7 @@ class ConfirmService extends taiga.Service
return defered.promise
success: (title, message, icon) ->
success: (title, message, icon, action) ->
defered = @q.defer()
el = angular.element(".lightbox-generic-success")
@ -193,6 +193,9 @@ class ConfirmService extends taiga.Service
# Render content
el.find(".title").html(title) if title
el.find(".message").html(message) if message
if action
el.find(".button-green").html(action)
el.find(".button-green").attr('title', action)
# Assign event handlers
el.on "click.confirm-dialog", ".button-green", (event) =>

View File

@ -116,7 +116,6 @@ urls = {
"bulk-update-us-milestone": "/userstories/bulk_update_milestone"
"bulk-update-us-miles-order": "/userstories/bulk_update_sprint_order"
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
"bulk-update-us-milestone": "/userstories/bulk_update_milestone"
"userstories-filters": "/userstories/filters_data"
"userstory-upvote": "/userstories/%s/upvote"
"userstory-downvote": "/userstories/%s/downvote"
@ -127,6 +126,7 @@ urls = {
"tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
"bulk-update-task-milestone": "/tasks/bulk_update_milestone"
"task-upvote": "/tasks/%s/upvote"
"task-downvote": "/tasks/%s/downvote"
"task-watch": "/tasks/%s/watch"
@ -136,6 +136,7 @@ urls = {
# Issues
"issues": "/issues"
"bulk-create-issues": "/issues/bulk_create"
"bulk-update-issue-milestone": "/issues/bulk_update_milestone"
"issues-filters": "/issues/filters_data"
"issue-upvote": "/issues/%s/upvote"
"issue-downvote": "/issues/%s/downvote"

View File

@ -99,6 +99,11 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
hash = generateHash([projectId, ns])
return $storage.get(hash) or {}
service.bulkUpdateMilestone = (projectId, milestoneId, data) ->
url = $urls.resolve("bulk-update-issue-milestone")
params = {project_id: projectId, milestone_id: milestoneId, bulk_issues: data}
return $http.post(url, params)
return (instance) ->
instance.issues = service

View File

@ -85,6 +85,11 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
params = {project_id: projectId, bulk_tasks: data}
return $http.post(url, params)
service.bulkUpdateMilestone = (projectId, milestoneId, data) ->
url = $urls.resolve("bulk-update-task-milestone")
params = {project_id: projectId, milestone_id: milestoneId, bulk_tasks: data}
return $http.post(url, params)
service.reorder = (id, data, setOrders) ->
url = $urls.resolve("tasks") + "/#{id}"

View File

@ -332,6 +332,13 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@scope.$on("taskboard:task:move", @.taskMove)
@scope.$on("assigned-to:added", @.onAssignedToChanged)
@scope.$on "taskboard:items:move", (event, itemsMoved) =>
if itemsMoved.uss
@.firstLoad()
else
@.loadTasks() if itemsMoved.tasks
@.loadIssues() if itemsMoved.issues
onAssignedToChanged: (ctx, userid, model) ->
if model.getName() == 'tasks'
model.assigned_to = userid
@ -430,6 +437,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
return @rs.issues.listInProject(@scope.projectId, @scope.sprintId, params).then (issues) =>
@taskboardIssuesService.init(@scope.project, @scope.usersById, @scope.issueStatusById)
@taskboardIssuesService.set(issues)
@scope.taskBoardLoading = false
loadTasks: ->
params = {}
@ -577,6 +585,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@confirm.notify("error")
taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
@scope.movingTask = true
task = @taskboardTasksService.getTaskModel(task.get('id'))
moveUpdateData = @taskboardTasksService.move(task.id, usId, statusId, order)
@ -593,6 +602,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
}
promise = @repo.save(task, true, params, options, true).then (result) =>
if result[0].user_story
@.reloadUserStory(result[0].user_story)
@scope.movingTask = false
headers = result[1]
if headers && headers['taiga-info-order-updated']
@ -604,6 +617,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
if @.isFilterDataTypeSelected('status')
@.loadTasks()
reloadUserStory: (userStoryId) ->
@rs.userstories.get(@scope.project.id, userStoryId).then (us) =>
@scope.userstories = _.map(@scope.userstories, (x) -> if x.id == us.id then us else x)
## Template actions
addNewTask: (type, us) ->

View File

@ -3,6 +3,7 @@
"YES": "Yes",
"NO": "No",
"OR": "or",
"I_GET_IT": "OK, I get it",
"LOADING": "Loading...",
"DATE": "DD MMM YYYY",
"DATETIME": "DD MMM YYYY HH:mm",
@ -1392,6 +1393,19 @@
"OPTIMAL": "Optimal pending points for day {{formattedDate}} should be {{roundedValue}}",
"REAL": "Real pending points for day {{formattedDate}} is {{roundedValue}}",
"DATE": "DD MMMM YYYY"
},
"MOVE_TO_SPRINT": {
"TITLE_ACTION_MOVE_UNFINISHED": "Move unfinished items to another sprint",
"TITLE_MOVE_UNFINISHED": "Move unfinished items to another open sprint",
"MOVE_TO_OPEN_SPRINT": "Move to open sprint",
"SELECT_DESTINATION_PLACEHOLDER": "Select destination",
"UNFINISHED_USER_STORIES_COUNT": "{total, plural, one{<strong>#</strong> unfinished user story} other{<strong>#</strong> unfinished user stories}}",
"UNFINISHED_UNASSIGNED_TASKS_COUNT": "{total, plural, one{<strong>#</strong> unfinished unnasigned task} other{<strong>#</strong> unfinished unnasigned tasks}}",
"UNFINISHED_ISSUES_COUNT": "{total, plural, one{<strong>#</strong> unfinished issue} other{<strong>#</strong> unfinished unnasigned issue}}",
"WARNING_ISSUES_NOT_MOVED_TITLE": "You just moved all user stories and taks, and the sprint'll be closed",
"WARNING_ISSUES_NOT_MOVED": "The issues'll remain in the sprint and don't be removed",
"WARNING_SPRINT_STILL_OPEN_TITLE": "{total, plural, one{You just moved # item!} other{You just moved # items!}}",
"WARNING_SPRINT_STILL_OPEN": "Please note that <strong>the sprint {{sprintName}} appears as open</strong> as long as still contains open items."
}
},
"TASK": {

View File

@ -0,0 +1,145 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: components/move-to-sprint/move-to-sprint-lb/move-to-sprint-lb.controller.coffee
###
module = angular.module("taigaComponents")
class MoveToSprintLightboxController
@.$inject = [
'$rootScope'
'$scope'
'$tgResources'
'tgProjectService'
'$translate'
'lightboxService'
'$tgConfirm'
]
constructor: (
@rootScope
@scope
@rs
@projectService
@translate
@lightboxService
@confirm
) ->
@.projectId = @projectService.project.get('id')
@.loading = false
@.someSelected = false
@.selectedSprintId = null
@.typesSelected = {
uss: false
tasks: false
issues: false
}
@.itemsToMove = {}
@._loadSprints()
@scope.$watch "vm.openItems", (openItems) =>
return if !openItems
@._init(openItems)
_init: (openItems) ->
@.hasManyItemTypes = _.size(@.openItems) > 1
@.ussCount = parseInt(openItems.uss?.length)
@.updateSelected('uss', @.ussCount > 0)
@.tasksCount = parseInt(openItems.tasks?.length)
@.updateSelected('tasks', @.tasksCount > 0)
@.issuesCount = parseInt(openItems.issues?.length)
@.updateSelected('issues', @.issuesCount > 0)
_loadSprints: () ->
@rs.sprints.list(@.projectId, {closed: false}).then (data) =>
@.sprints = data.milestones
updateSelected: (itemType, value) ->
@.typesSelected[itemType] = value
@.someSelected = _.some(@.typesSelected)
if value is true
@.itemsToMove[itemType] = @.openItems[itemType]
else if @.itemsToMove[itemType]
delete @.itemsToMove[itemType]
submit: () ->
itemsNotMoved = {}
_.map @.openItems, (itemsList, itemsType) =>
if not @.itemsToMove[itemsType]
itemsNotMoved[itemsType] = true
@.loading = true
@moveItems().then () =>
@rootScope.$broadcast("taskboard:items:move", @.typesSelected)
@lightboxService.closeAll()
@.loading = false
if _.size(itemsNotMoved) > 0
@.displayWarning(itemsNotMoved)
moveItems: () ->
promises = []
if @.itemsToMove.uss
promises.push(
@rs.userstories.bulkUpdateMilestone(
@.projectId
@.selectedSprintId
@.itemsToMove.uss
)
)
if @.itemsToMove.tasks
promises.push(
@rs.tasks.bulkUpdateMilestone(
@.projectId
@.selectedSprintId
@.itemsToMove.tasks
)
)
if @.itemsToMove.issues
promises.push(
@rs.issues.bulkUpdateMilestone(
@.projectId
@.selectedSprintId
@.itemsToMove.issues
)
)
return Promise.all(promises)
displayWarning: (itemsNotMoved) ->
action = @translate.instant('COMMON.I_GET_IT')
if _.size(itemsNotMoved) == 1 and itemsNotMoved.issues is true
title = @translate.instant('TASKBOARD.MOVE_TO_SPRINT.WARNING_ISSUES_NOT_MOVED_TITLE')
desc = @translate.instant('TASKBOARD.MOVE_TO_SPRINT.WARNING_ISSUES_NOT_MOVED')
else
totalItemsMoved = 0
_.map @.itemsToMove, (itemsList, itemsType) -> totalItemsMoved += itemsList.length
title = @translate.instant(
'TASKBOARD.MOVE_TO_SPRINT.WARNING_SPRINT_STILL_OPEN_TITLE'
{ total: totalItemsMoved }
'messageformat'
)
desc = @translate.instant(
'TASKBOARD.MOVE_TO_SPRINT.WARNING_SPRINT_STILL_OPEN'
{ sprintName: @.sprint?.name }
)
@confirm.success(title, desc, null, action)
module.controller("MoveToSprintLbCtrl", MoveToSprintLightboxController)

View File

@ -0,0 +1,42 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: components/move-to-sprint/move-to-sprint-lb/move-to-sprint-lb.directive.coffee
###
module = angular.module("taigaComponents")
moveToSprintLightboxDirective = (lightboxService) ->
link = (scope, el, attrs, ctrl) ->
lightboxService.open(el)
return {
scope: {}
bindToController: {
openItems: "="
sprint: "="
},
templateUrl: "components/move-to-sprint/move-to-sprint-lb/move-to-sprint-lb.html"
controller: "MoveToSprintLbCtrl"
controllerAs: "vm"
link: link
}
moveToSprintLightboxDirective.$inject = [
"lightboxService"
]
module.directive("tgLbMoveToSprint", moveToSprintLightboxDirective)

View File

@ -0,0 +1,67 @@
tg-lightbox-close
.move-to-sprint-container
.move-to-sprint-header
h2.title {{ 'TASKBOARD.MOVE_TO_SPRINT.TITLE_ACTION_MOVE_UNFINISHED'|translate }}
ul
li.choice(ng-if="vm.ussCount")
span(ng-bind-html="'TASKBOARD.MOVE_TO_SPRINT.UNFINISHED_USER_STORIES_COUNT'|translate:{ total: vm.ussCount || 0 }:'messageformat'")
.check.js-check(ng-if="vm.hasManyItemTypes")
input(
type="checkbox"
ng-checked="vm.typesSelected['uss']"
ng-model="vm.typesSelected['uss']"
ng-change="vm.updateSelected('uss', vm.typesSelected['uss'])"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
li.choice(ng-if="vm.tasksCount")
span(ng-bind-html="'TASKBOARD.MOVE_TO_SPRINT.UNFINISHED_UNASSIGNED_TASKS_COUNT'|translate:{ total: vm.tasksCount || 0 }:'messageformat'")
.check.js-check(ng-if="vm.hasManyItemTypes")
input(
type="checkbox"
ng-checked="vm.typesSelected['tasks']"
ng-model="vm.typesSelected['tasks']"
ng-change="vm.updateSelected('tasks', vm.typesSelected['tasks'])"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
li.choice(ng-if="vm.issuesCount")
span(ng-bind-html="'TASKBOARD.MOVE_TO_SPRINT.UNFINISHED_ISSUES_COUNT'|translate:{ total: vm.issuesCount || 0 }:'messageformat'")
.check.js-check(ng-if="vm.hasManyItemTypes")
input(
type="checkbox"
ng-checked="vm.typesSelected['issues']"
ng-model="vm.typesSelected['issues']"
ng-change="vm.updateSelected('issues', vm.typesSelected['issues'])"
)
div
span.check-text.check-yes(translate="COMMON.YES")
span.check-text.check-no(translate="COMMON.NO")
.move-to-sprint-controls
fieldset
label {{ 'TASKBOARD.MOVE_TO_SPRINT.MOVE_TO_OPEN_SPRINT'|translate }}
select.sprint-select(
ng-model="vm.selectedSprintId"
ng-options="s.id as s.name for s in vm.sprints|filter: { id: '!' + vm.sprint.id }"
id="sprint-selector-dropdown"
autofocus
)
option(
value=""
disabled
selected
translate="TASKBOARD.MOVE_TO_SPRINT.SELECT_DESTINATION_PLACEHOLDER"
)
button.button-green.move-button(
href=""
ng-click="vm.submit()"
translate="COMMON.SAVE"
ng-disabled="!(vm.selectedSprintId && vm.someSelected)"
)

View File

@ -0,0 +1,32 @@
.lightbox-move-to-sprint .move-to-sprint-container {
display: flex;
flex-direction: column;
max-width: 550px;
width: 100%;
.move-to-sprint-header {
margin: 0 auto;
max-width: 400px;
text-align: center;
ul {
display: inline-block;
margin: .5em auto 2.5em;
width: auto;
}
li {
display: flex;
justify-content: space-between;
margin-top: 1em;
}
.check {
margin-left: 4em;
}
}
.move-to-sprint-controls {
.sprint-select {
margin-top: .5em;
}
.move-button {
width: 100%;
}
}
}

View File

@ -0,0 +1,90 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: components/move-to-sprint/move-to-sprint-controller.coffee
###
taiga = @.taiga
class MoveToSprintController
@.$inject = [
'$scope'
'tgLightboxFactory'
]
constructor: (@scope, @lightboxFactory) ->
@.hasOpenItems = false
@.disabled = false
@.openItems = {
uss: []
tasks: []
issues: []
}
@scope.$watch "vm.uss", () => @getOpenUss()
@scope.$watch "vm.unnasignedTasks", () => @getOpenUnassignedTasks()
@scope.$watch "vm.issues", () => @getOpenIssues()
checkOpenItems: () ->
return _.some(Object.keys(@.openItems), (x) => @.openItems[x].length > 0)
openLightbox: () ->
if @.disabled is not true && @.hasOpenItems
openItems = {}
_.map @.openItems, (itemsList, itemsType) ->
if itemsList.length
openItems[itemsType] = itemsList
@lightboxFactory.create('tg-lb-move-to-sprint', {
"class": "lightbox lightbox-move-to-sprint"
"sprint": "sprint"
"open-items": "openItems"
}, {
sprint: @.sprint
openItems: openItems
})
getOpenUss: () ->
return if !@.uss
@.openItems.uss = []
@.uss.map (us) =>
if us.is_closed is false
@.openItems.uss.push({
us_id: us.id
order: us.sprint_order
})
@.hasOpenItems = @checkOpenItems()
getOpenUnassignedTasks: () ->
return if !@.unnasignedTasks
@.openItems.tasks = []
@.unnasignedTasks.map (column) => column.map (task) =>
if task.get('model').get('is_closed') is false
@.openItems.tasks.push({
task_id: task.get('model').get('id')
order: task.get('model').get('taskboard_order')
})
@.hasOpenItems = @checkOpenItems()
getOpenIssues: () ->
return if !@.issues
@.openItems.issues = []
@.issues.map (issue) =>
if issue.get('status').get('is_closed') is false
@.openItems.issues.push({ issue_id: issue.get('id') })
@.hasOpenItems = @checkOpenItems()
angular.module('taigaComponents').controller('MoveToSprintCtrl', MoveToSprintController)

View File

@ -0,0 +1,114 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: components/move-to-sprint/move-to-sprint-controller.spec.coffee
###
describe "MoveToSprint", ->
provide = null
$controller = null
scope = null
ctrl = null
mocks = {}
_mockTgLightboxFactory = () ->
mocks.tgLightboxFactory = {
create: sinon.stub()
}
provide.value "tgLightboxFactory", mocks.tgLightboxFactory
_mocks = () ->
module ($provide) ->
provide = $provide
_mockTgLightboxFactory()
return null
_inject = ->
inject (_$controller_, $rootScope) ->
$controller = _$controller_
scope = $rootScope.$new()
_setup = ->
_mocks()
_inject()
beforeEach ->
module "taigaComponents"
_setup()
ctrl = $controller("MoveToSprintCtrl", {
$scope: scope
}, {
uss: null,
unnasignedTasks: null,
issues: null,
disabled: false
})
describe "button", ->
it "is disabled by default", () ->
expect(ctrl.hasOpenItems).to.be.false
it "is enabled when milestone has open user stories", () ->
ctrl.uss = [
{ id: 1, is_closed: true, sprint_order: 5 }
{ id: 2, is_closed: false, sprint_order: 6 }
{ id: 3, is_closed: false, sprint_order: 7 }
]
ctrl.getOpenUss()
expect(ctrl.hasOpenItems).to.be.true
expect(ctrl.openItems.uss).to.be.eql([
{ us_id: 2, order: 6 }
{ us_id: 3, order: 7 }
])
it "is enabled when milestone has open unassigned tasks", () ->
ctrl.unnasignedTasks = Immutable.fromJS([
[
{ model: { id: 1, is_closed: true, taskboard_order: 5 } }
{ model: { id: 2, is_closed: false, taskboard_order: 6 } }
],
[{ model: { id: 3, is_closed: false, taskboard_order: 7 } }]
])
ctrl.getOpenUnassignedTasks()
expect(ctrl.hasOpenItems).to.be.true
expect(ctrl.openItems.tasks).to.be.eql([
{ task_id: 2, order: 6 }
{ task_id: 3, order: 7 }
])
it "is enabled when milestone has open issues", () ->
ctrl.issues = Immutable.fromJS([
{ id: 1, status: { is_closed: true } }
{ id: 2, status: { is_closed: false } }
])
ctrl.getOpenIssues()
expect(ctrl.hasOpenItems).to.be.true
expect(ctrl.openItems.issues).to.be.eql([{ issue_id: 2 }])
describe "lightbox", ->
it "is opened on button click if has open items", () ->
ctrl.issues = Immutable.fromJS([
{ id: 1, status: { is_closed: false } }
])
ctrl.getOpenIssues()
ctrl.openLightbox()
expect(mocks.tgLightboxFactory.create).have.been.called
it "is not opened on button click if has no open items", () ->
ctrl.openLightbox()
expect(mocks.tgLightboxFactory.create).not.have.been.called

View File

@ -0,0 +1,41 @@
###
# Copyright (C) 2014-2018 Taiga Agile LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: components/move-to-sprint/move-to-sprint.directive.coffee
###
module = angular.module("taigaComponents")
moveToSprintDirective = (taskboardTasksService) ->
return {
controller: "MoveToSprintCtrl"
controllerAs: "vm"
bindToController: true
templateUrl: 'components/move-to-sprint/move-to-sprint.html'
scope: {
sprint: '='
uss: '='
unnasignedTasks: '='
issues: '='
disabled: '='
}
}
moveToSprintDirective.$inject = [
'tgTaskboardTasks'
]
module.directive('tgMoveToSprint', [moveToSprintDirective])

View File

@ -0,0 +1,6 @@
a.move-to-sprint-button.is-editable(
title="{{ 'TASKBOARD.MOVE_TO_SPRINT.TITLE_ACTION_MOVE_UNFINISHED' | translate }}"
ng-class="{'disabled': !vm.hasOpenItems || vm.disabled}"
ng-click="vm.openLightbox()"
)
tg-svg(svg-icon="icon-move")

View File

@ -0,0 +1,15 @@
.move-to-sprint-button {
color: $white;
&:not(.disabled) {
cursor: pointer;
&:hover {
color: $primary-light;
}
}
&.disabled {
opacity: .5;
&:hover {
color: $white;
}
}
}

View File

@ -23,6 +23,16 @@ div.summary.large-summary
span.number(ng-bind="stats.completed_tasks|default:'--'")
span.description(translate="BACKLOG.SPRINT_SUMMARY.CLOSED_TASKS")
div.summary-stats.summary-move-unfinished
tg-move-to-sprint(
sprint="sprint"
uss="userstories"
unnasigned-tasks="usTasks.get('null')"
issues="milestoneIssues"
disabled="movingTask"
)
span.taskBoardLoading
div.summary-stats.summary-iocaine(title="{{'COMMON.IOCAINE_TEXT' | translate}}")
tg-svg(svg-icon="icon-iocaine")
span.number(ng-bind="stats.iocaine_doses|default:'--'")

View File

@ -149,7 +149,7 @@ $summary-background: $grayer;
margin: 0;
}
&.summary-completed-points,
&.summary-closed-tasks {
&.summary-move-unfinished {
border-right: 1px solid $blackish;
margin-right: 0;
padding-right: 1rem;