Move open items to another sprint
parent
d8d05bea14
commit
09fd3a9d72
|
@ -163,7 +163,7 @@ class ConfirmService extends taiga.Service
|
||||||
|
|
||||||
return defered.promise
|
return defered.promise
|
||||||
|
|
||||||
success: (title, message, icon) ->
|
success: (title, message, icon, action) ->
|
||||||
defered = @q.defer()
|
defered = @q.defer()
|
||||||
|
|
||||||
el = angular.element(".lightbox-generic-success")
|
el = angular.element(".lightbox-generic-success")
|
||||||
|
@ -193,6 +193,9 @@ class ConfirmService extends taiga.Service
|
||||||
# Render content
|
# Render content
|
||||||
el.find(".title").html(title) if title
|
el.find(".title").html(title) if title
|
||||||
el.find(".message").html(message) if message
|
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
|
# Assign event handlers
|
||||||
el.on "click.confirm-dialog", ".button-green", (event) =>
|
el.on "click.confirm-dialog", ".button-green", (event) =>
|
||||||
|
|
|
@ -116,7 +116,6 @@ urls = {
|
||||||
"bulk-update-us-milestone": "/userstories/bulk_update_milestone"
|
"bulk-update-us-milestone": "/userstories/bulk_update_milestone"
|
||||||
"bulk-update-us-miles-order": "/userstories/bulk_update_sprint_order"
|
"bulk-update-us-miles-order": "/userstories/bulk_update_sprint_order"
|
||||||
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
|
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
|
||||||
"bulk-update-us-milestone": "/userstories/bulk_update_milestone"
|
|
||||||
"userstories-filters": "/userstories/filters_data"
|
"userstories-filters": "/userstories/filters_data"
|
||||||
"userstory-upvote": "/userstories/%s/upvote"
|
"userstory-upvote": "/userstories/%s/upvote"
|
||||||
"userstory-downvote": "/userstories/%s/downvote"
|
"userstory-downvote": "/userstories/%s/downvote"
|
||||||
|
@ -127,6 +126,7 @@ urls = {
|
||||||
"tasks": "/tasks"
|
"tasks": "/tasks"
|
||||||
"bulk-create-tasks": "/tasks/bulk_create"
|
"bulk-create-tasks": "/tasks/bulk_create"
|
||||||
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
|
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
|
||||||
|
"bulk-update-task-milestone": "/tasks/bulk_update_milestone"
|
||||||
"task-upvote": "/tasks/%s/upvote"
|
"task-upvote": "/tasks/%s/upvote"
|
||||||
"task-downvote": "/tasks/%s/downvote"
|
"task-downvote": "/tasks/%s/downvote"
|
||||||
"task-watch": "/tasks/%s/watch"
|
"task-watch": "/tasks/%s/watch"
|
||||||
|
@ -136,6 +136,7 @@ urls = {
|
||||||
# Issues
|
# Issues
|
||||||
"issues": "/issues"
|
"issues": "/issues"
|
||||||
"bulk-create-issues": "/issues/bulk_create"
|
"bulk-create-issues": "/issues/bulk_create"
|
||||||
|
"bulk-update-issue-milestone": "/issues/bulk_update_milestone"
|
||||||
"issues-filters": "/issues/filters_data"
|
"issues-filters": "/issues/filters_data"
|
||||||
"issue-upvote": "/issues/%s/upvote"
|
"issue-upvote": "/issues/%s/upvote"
|
||||||
"issue-downvote": "/issues/%s/downvote"
|
"issue-downvote": "/issues/%s/downvote"
|
||||||
|
|
|
@ -99,6 +99,11 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
|
||||||
hash = generateHash([projectId, ns])
|
hash = generateHash([projectId, ns])
|
||||||
return $storage.get(hash) or {}
|
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) ->
|
return (instance) ->
|
||||||
instance.issues = service
|
instance.issues = service
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,11 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
|
||||||
params = {project_id: projectId, bulk_tasks: data}
|
params = {project_id: projectId, bulk_tasks: data}
|
||||||
return $http.post(url, params)
|
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) ->
|
service.reorder = (id, data, setOrders) ->
|
||||||
url = $urls.resolve("tasks") + "/#{id}"
|
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("taskboard:task:move", @.taskMove)
|
||||||
@scope.$on("assigned-to:added", @.onAssignedToChanged)
|
@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) ->
|
onAssignedToChanged: (ctx, userid, model) ->
|
||||||
if model.getName() == 'tasks'
|
if model.getName() == 'tasks'
|
||||||
model.assigned_to = userid
|
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) =>
|
return @rs.issues.listInProject(@scope.projectId, @scope.sprintId, params).then (issues) =>
|
||||||
@taskboardIssuesService.init(@scope.project, @scope.usersById, @scope.issueStatusById)
|
@taskboardIssuesService.init(@scope.project, @scope.usersById, @scope.issueStatusById)
|
||||||
@taskboardIssuesService.set(issues)
|
@taskboardIssuesService.set(issues)
|
||||||
|
@scope.taskBoardLoading = false
|
||||||
|
|
||||||
loadTasks: ->
|
loadTasks: ->
|
||||||
params = {}
|
params = {}
|
||||||
|
@ -577,6 +585,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
||||||
@confirm.notify("error")
|
@confirm.notify("error")
|
||||||
|
|
||||||
taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
|
taskMove: (ctx, task, oldStatusId, usId, statusId, order) ->
|
||||||
|
@scope.movingTask = true
|
||||||
task = @taskboardTasksService.getTaskModel(task.get('id'))
|
task = @taskboardTasksService.getTaskModel(task.get('id'))
|
||||||
|
|
||||||
moveUpdateData = @taskboardTasksService.move(task.id, usId, statusId, order)
|
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) =>
|
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]
|
headers = result[1]
|
||||||
|
|
||||||
if headers && headers['taiga-info-order-updated']
|
if headers && headers['taiga-info-order-updated']
|
||||||
|
@ -604,6 +617,9 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
|
||||||
if @.isFilterDataTypeSelected('status')
|
if @.isFilterDataTypeSelected('status')
|
||||||
@.loadTasks()
|
@.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
|
## Template actions
|
||||||
addNewTask: (type, us) ->
|
addNewTask: (type, us) ->
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"YES": "Yes",
|
"YES": "Yes",
|
||||||
"NO": "No",
|
"NO": "No",
|
||||||
"OR": "or",
|
"OR": "or",
|
||||||
|
"I_GET_IT": "OK, I get it",
|
||||||
"LOADING": "Loading...",
|
"LOADING": "Loading...",
|
||||||
"DATE": "DD MMM YYYY",
|
"DATE": "DD MMM YYYY",
|
||||||
"DATETIME": "DD MMM YYYY HH:mm",
|
"DATETIME": "DD MMM YYYY HH:mm",
|
||||||
|
@ -1392,6 +1393,19 @@
|
||||||
"OPTIMAL": "Optimal pending points for day {{formattedDate}} should be {{roundedValue}}",
|
"OPTIMAL": "Optimal pending points for day {{formattedDate}} should be {{roundedValue}}",
|
||||||
"REAL": "Real pending points for day {{formattedDate}} is {{roundedValue}}",
|
"REAL": "Real pending points for day {{formattedDate}} is {{roundedValue}}",
|
||||||
"DATE": "DD MMMM YYYY"
|
"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": {
|
"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.number(ng-bind="stats.completed_tasks|default:'--'")
|
||||||
span.description(translate="BACKLOG.SPRINT_SUMMARY.CLOSED_TASKS")
|
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}}")
|
div.summary-stats.summary-iocaine(title="{{'COMMON.IOCAINE_TEXT' | translate}}")
|
||||||
tg-svg(svg-icon="icon-iocaine")
|
tg-svg(svg-icon="icon-iocaine")
|
||||||
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
span.number(ng-bind="stats.iocaine_doses|default:'--'")
|
||||||
|
|
|
@ -149,7 +149,7 @@ $summary-background: $grayer;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
&.summary-completed-points,
|
&.summary-completed-points,
|
||||||
&.summary-closed-tasks {
|
&.summary-move-unfinished {
|
||||||
border-right: 1px solid $blackish;
|
border-right: 1px solid $blackish;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
|
Loading…
Reference in New Issue