Move open items to another sprint
parent
d8d05bea14
commit
09fd3a9d72
|
@ -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) =>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)"
|
||||
)
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
|
@ -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])
|
|
@ -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")
|
|
@ -0,0 +1,15 @@
|
|||
.move-to-sprint-button {
|
||||
color: $white;
|
||||
&:not(.disabled) {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $primary-light;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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:'--'")
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue