commit
d19c431f16
|
@ -1,5 +1,7 @@
|
||||||
# Changelog #
|
# Changelog #
|
||||||
|
|
||||||
|
## 3.1.0 No name yet (no date yet)
|
||||||
|
- Velocity forecasting. Create sprints according to team velocity.
|
||||||
|
|
||||||
## 3.0.0 Stellaria Borealis (2016-10-02)
|
## 3.0.0 Stellaria Borealis (2016-10-02)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
|
||||||
createSprint = true
|
createSprint = true
|
||||||
form = null
|
form = null
|
||||||
$scope.newSprint = {}
|
$scope.newSprint = {}
|
||||||
|
ussToAdd = null
|
||||||
|
|
||||||
resetSprint = () ->
|
resetSprint = () ->
|
||||||
form.reset() if form
|
form.reset() if form
|
||||||
|
@ -97,7 +98,10 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
|
||||||
else
|
else
|
||||||
return it
|
return it
|
||||||
|
|
||||||
$rootscope.$broadcast(broadcastEvent, data)
|
if broadcastEvent == "sprintform:create:success" && ussToAdd
|
||||||
|
$rootscope.$broadcast(broadcastEvent, data, ussToAdd)
|
||||||
|
else
|
||||||
|
$rootscope.$broadcast(broadcastEvent, data)
|
||||||
|
|
||||||
lightboxService.close($el)
|
lightboxService.close($el)
|
||||||
|
|
||||||
|
@ -135,7 +139,8 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
|
||||||
|
|
||||||
return sortedSprints[sortedSprints.length - 1]
|
return sortedSprints[sortedSprints.length - 1]
|
||||||
|
|
||||||
$scope.$on "sprintform:create", (event, projectId) ->
|
$scope.$on "sprintform:create", (event, projectId, uss) ->
|
||||||
|
ussToAdd = uss
|
||||||
resetSprint()
|
resetSprint()
|
||||||
|
|
||||||
form = $el.find("form").checksley()
|
form = $el.find("form").checksley()
|
||||||
|
|
|
@ -86,6 +86,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@showTags = false
|
@showTags = false
|
||||||
@activeFilters = false
|
@activeFilters = false
|
||||||
@scope.showGraphPlaceholder = null
|
@scope.showGraphPlaceholder = null
|
||||||
|
@displayVelocity = false
|
||||||
|
|
||||||
@.initializeEventHandlers()
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
|
@ -120,8 +121,10 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@confirm.notify("success")
|
@confirm.notify("success")
|
||||||
@analytics.trackEvent("userstory", "create", "bulk create userstory on backlog", 1)
|
@analytics.trackEvent("userstory", "create", "bulk create userstory on backlog", 1)
|
||||||
|
|
||||||
@scope.$on "sprintform:create:success", =>
|
@scope.$on "sprintform:create:success", (e, data, ussToMove) =>
|
||||||
@.loadSprints()
|
@.loadSprints().then () =>
|
||||||
|
@scope.$broadcast("sprintform:create:success:callback", ussToMove)
|
||||||
|
|
||||||
@.loadProjectStats()
|
@.loadProjectStats()
|
||||||
@confirm.notify("success")
|
@confirm.notify("success")
|
||||||
@analytics.trackEvent("sprint", "create", "create sprint on backlog", 1)
|
@analytics.trackEvent("sprint", "create", "create sprint on backlog", 1)
|
||||||
|
@ -181,6 +184,17 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
toggleActiveFilters: ->
|
toggleActiveFilters: ->
|
||||||
@activeFilters = !@activeFilters
|
@activeFilters = !@activeFilters
|
||||||
|
|
||||||
|
toggleVelocityForecasting: ->
|
||||||
|
@displayVelocity = !@displayVelocity
|
||||||
|
if !@displayVelocity
|
||||||
|
@scope.visibleUserStories = _.map @scope.userstories, (it) ->
|
||||||
|
return it.ref
|
||||||
|
else
|
||||||
|
@scope.visibleUserStories = _.map @.forecastedStories, (it) ->
|
||||||
|
return it.ref
|
||||||
|
scopeDefer @scope, =>
|
||||||
|
@scope.$broadcast("userstories:loaded")
|
||||||
|
|
||||||
loadProjectStats: ->
|
loadProjectStats: ->
|
||||||
return @rs.projects.stats(@scope.projectId).then (stats) =>
|
return @rs.projects.stats(@scope.projectId).then (stats) =>
|
||||||
@scope.stats = stats
|
@scope.stats = stats
|
||||||
|
@ -192,6 +206,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@scope.stats.completedPercentage = 0
|
@scope.stats.completedPercentage = 0
|
||||||
|
|
||||||
@scope.showGraphPlaceholder = !(stats.total_points? && stats.total_milestones?)
|
@scope.showGraphPlaceholder = !(stats.total_points? && stats.total_milestones?)
|
||||||
|
@.calculateForecasting()
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
setMilestonesOrder: (sprints) ->
|
setMilestonesOrder: (sprints) ->
|
||||||
|
@ -275,6 +290,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
promise = @rs.userstories.listUnassigned(@scope.projectId, params, pageSize)
|
promise = @rs.userstories.listUnassigned(@scope.projectId, params, pageSize)
|
||||||
|
|
||||||
return promise.then (result) =>
|
return promise.then (result) =>
|
||||||
|
|
||||||
userstories = result[0]
|
userstories = result[0]
|
||||||
header = result[1]
|
header = result[1]
|
||||||
|
|
||||||
|
@ -283,6 +299,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
# NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
|
# NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
|
||||||
@scope.userstories = @scope.userstories.concat(_.sortBy(userstories, "backlog_order"))
|
@scope.userstories = @scope.userstories.concat(_.sortBy(userstories, "backlog_order"))
|
||||||
|
@scope.visibleUserStories = _.map @scope.userstories, (it) ->
|
||||||
|
return it.ref
|
||||||
|
|
||||||
for it in @scope.userstories
|
for it in @scope.userstories
|
||||||
@.backlogOrder[it.id] = it.backlog_order
|
@.backlogOrder[it.id] = it.backlog_order
|
||||||
|
@ -305,7 +323,22 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@.loadProjectStats(),
|
@.loadProjectStats(),
|
||||||
@.loadSprints(),
|
@.loadSprints(),
|
||||||
@.loadUserstories()
|
@.loadUserstories()
|
||||||
])
|
]).then(@.calculateForecasting)
|
||||||
|
|
||||||
|
calculateForecasting: ->
|
||||||
|
stats = @scope.stats
|
||||||
|
total_points = stats.total_points
|
||||||
|
current_sum = stats.assigned_points
|
||||||
|
backlog_points_sum = 0
|
||||||
|
@forecastedStories = []
|
||||||
|
|
||||||
|
for us in @scope.userstories
|
||||||
|
current_sum += us.total_points
|
||||||
|
backlog_points_sum += us.total_points
|
||||||
|
@forecastedStories.push(us)
|
||||||
|
|
||||||
|
if stats.speed > 0 && backlog_points_sum > stats.speed
|
||||||
|
break
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
||||||
|
@ -545,7 +578,7 @@ module.controller("BacklogController", BacklogController)
|
||||||
## Backlog Directive
|
## Backlog Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
BacklogDirective = ($repo, $rootscope, $translate) ->
|
BacklogDirective = ($repo, $rootscope, $translate, $rs) ->
|
||||||
## Doom line Link
|
## Doom line Link
|
||||||
doomLineTemplate = _.template("""
|
doomLineTemplate = _.template("""
|
||||||
<div class="doom-line"><span><%- text %></span></div>
|
<div class="doom-line"><span><%- text %></span></div>
|
||||||
|
@ -553,11 +586,13 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
|
|
||||||
linkDoomLine = ($scope, $el, $attrs, $ctrl) ->
|
linkDoomLine = ($scope, $el, $attrs, $ctrl) ->
|
||||||
reloadDoomLine = ->
|
reloadDoomLine = ->
|
||||||
if $scope.stats? and $scope.stats.total_points? and $scope.stats.total_points != 0
|
if $scope.displayVelocity
|
||||||
|
removeDoomlineDom()
|
||||||
|
|
||||||
|
if $scope.stats? and $scope.stats.total_points? and $scope.stats.total_points != 0 and !$scope.displayVelocity?
|
||||||
removeDoomlineDom()
|
removeDoomlineDom()
|
||||||
|
|
||||||
stats = $scope.stats
|
stats = $scope.stats
|
||||||
|
|
||||||
total_points = stats.total_points
|
total_points = stats.total_points
|
||||||
current_sum = stats.assigned_points
|
current_sum = stats.assigned_points
|
||||||
|
|
||||||
|
@ -584,6 +619,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
return _.map(rowElements, (x) -> angular.element(x))
|
return _.map(rowElements, (x) -> angular.element(x))
|
||||||
|
|
||||||
$scope.$on("userstories:loaded", reloadDoomLine)
|
$scope.$on("userstories:loaded", reloadDoomLine)
|
||||||
|
$scope.$on("userstories:forecast", removeDoomlineDom)
|
||||||
$scope.$watch("stats", reloadDoomLine)
|
$scope.$watch("stats", reloadDoomLine)
|
||||||
|
|
||||||
## Move to current sprint link
|
## Move to current sprint link
|
||||||
|
@ -614,9 +650,11 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
# Update the total of points
|
# Update the total of points
|
||||||
sprint.total_points += totalExtraPoints
|
sprint.total_points += totalExtraPoints
|
||||||
|
|
||||||
$repo.saveAll(selectedUss).then ->
|
$rs.userstories.bulkUpdateMilestone($scope.project.id, $scope.sprints[0].id, selectedUss).then =>
|
||||||
$ctrl.loadSprints()
|
$ctrl.loadSprints()
|
||||||
$ctrl.loadProjectStats()
|
$ctrl.loadProjectStats()
|
||||||
|
$ctrl.toggleVelocityForecasting()
|
||||||
|
$ctrl.calculateForecasting()
|
||||||
|
|
||||||
$el.find(".move-to-sprint").hide()
|
$el.find(".move-to-sprint").hide()
|
||||||
|
|
||||||
|
@ -626,6 +664,9 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
moveToLatestSprint = (selectedUss) ->
|
moveToLatestSprint = (selectedUss) ->
|
||||||
moveUssToSprint(selectedUss, $scope.sprints[0])
|
moveUssToSprint(selectedUss, $scope.sprints[0])
|
||||||
|
|
||||||
|
$scope.$on "sprintform:create:success:callback", (e, ussToMove) ->
|
||||||
|
_.partial(moveToCurrentSprint, ussToMove)()
|
||||||
|
|
||||||
shiftPressed = false
|
shiftPressed = false
|
||||||
lastChecked = null
|
lastChecked = null
|
||||||
|
|
||||||
|
@ -640,6 +681,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
else
|
else
|
||||||
moveToSprintDom.hide()
|
moveToSprintDom.hide()
|
||||||
|
|
||||||
|
|
||||||
$(window).on "keydown.shift-pressed keyup.shift-pressed", (event) ->
|
$(window).on "keydown.shift-pressed keyup.shift-pressed", (event) ->
|
||||||
shiftPressed = !!event.shiftKey
|
shiftPressed = !!event.shiftKey
|
||||||
|
|
||||||
|
@ -685,6 +727,22 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
|
|
||||||
showHideTags($ctrl)
|
showHideTags($ctrl)
|
||||||
|
|
||||||
|
$el.on "click", ".forecasting-add-sprint", (event) ->
|
||||||
|
ussToMoveList = $ctrl.forecastedStories
|
||||||
|
if $scope.currentSprint
|
||||||
|
ussToMove = _.map ussToMoveList, (us, index) ->
|
||||||
|
us.milestone = $scope.currentSprint.id
|
||||||
|
us.order = index
|
||||||
|
return us
|
||||||
|
|
||||||
|
$scope.$apply(_.partial(moveToCurrentSprint, ussToMove))
|
||||||
|
else
|
||||||
|
ussToMove = _.map ussToMoveList, (us, index) ->
|
||||||
|
us.order = index
|
||||||
|
return us
|
||||||
|
|
||||||
|
$rootscope.$broadcast("sprintform:create", $scope.projectId, ussToMove)
|
||||||
|
|
||||||
showHideTags = ($ctrl) ->
|
showHideTags = ($ctrl) ->
|
||||||
elm = angular.element("#show-tags")
|
elm = angular.element("#show-tags")
|
||||||
|
|
||||||
|
@ -759,7 +817,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
|
||||||
return {link: link}
|
return {link: link}
|
||||||
|
|
||||||
|
|
||||||
module.directive("tgBacklog", ["$tgRepo", "$rootScope", "$translate", BacklogDirective])
|
module.directive("tgBacklog", ["$tgRepo", "$rootScope", "$translate", "$tgResources", BacklogDirective])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## User story points directive
|
## User story points directive
|
||||||
|
|
|
@ -127,3 +127,11 @@ darkerFilter = ->
|
||||||
|
|
||||||
|
|
||||||
module.filter("darker", darkerFilter)
|
module.filter("darker", darkerFilter)
|
||||||
|
|
||||||
|
inArray = ($filter) ->
|
||||||
|
return (list, arrayFilter, element) ->
|
||||||
|
if arrayFilter
|
||||||
|
filter = $filter("filter")
|
||||||
|
return filter list, (listItem) ->
|
||||||
|
return arrayFilter.indexOf(listItem[element]) != -1
|
||||||
|
module.filter("inArray", ["$filter", inArray])
|
||||||
|
|
|
@ -109,6 +109,7 @@ 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"
|
||||||
|
|
|
@ -108,6 +108,17 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
|
||||||
params = {project_id: projectId, bulk_stories: data}
|
params = {project_id: projectId, bulk_stories: data}
|
||||||
return $http.post(url, params)
|
return $http.post(url, params)
|
||||||
|
|
||||||
|
service.bulkUpdateMilestone = (projectId, milestoneId, data) ->
|
||||||
|
url = $urls.resolve("bulk-update-us-milestone")
|
||||||
|
data = _.map data, (us) ->
|
||||||
|
return {
|
||||||
|
us_id: us.id
|
||||||
|
order: us.order
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {project_id: projectId, milestone_id: milestoneId, bulk_stories: data}
|
||||||
|
return $http.post(url, params)
|
||||||
|
|
||||||
service.listValues = (projectId, type) ->
|
service.listValues = (projectId, type) ->
|
||||||
params = {"project": projectId}
|
params = {"project": projectId}
|
||||||
service.storeQueryParams(projectId, params)
|
service.storeQueryParams(projectId, params)
|
||||||
|
|
|
@ -1249,6 +1249,12 @@
|
||||||
"SHOW": "Show tags",
|
"SHOW": "Show tags",
|
||||||
"HIDE": "Hide tags"
|
"HIDE": "Hide tags"
|
||||||
},
|
},
|
||||||
|
"FORECASTING": {
|
||||||
|
"TITLE": "Velocity forecasting",
|
||||||
|
"BACKLOG": "Display backlog",
|
||||||
|
"NEW_SPRINT": "Candidate User Stories for your next sprint based on your velocity. Click to create a new sprint.",
|
||||||
|
"CURRENT_SPRINT": "Candidate User Stories for your sprint based on your velocity. Click to add to current sprint."
|
||||||
|
},
|
||||||
"TABLE": {
|
"TABLE": {
|
||||||
"COLUMN_US": "User Stories",
|
"COLUMN_US": "User Stories",
|
||||||
"TITLE_COLUMN_POINTS": "Select view per Role"
|
"TITLE_COLUMN_POINTS": "Select view per Role"
|
||||||
|
|
|
@ -36,7 +36,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
||||||
|
|
||||||
div.backlog-menu
|
div.backlog-menu
|
||||||
div.backlog-table-options
|
div.backlog-table-options
|
||||||
a.trans-button.menu-button.move-to-current-sprint.move-to-sprint.e2e-move-to-sprint(
|
a.menu-button.move-to-current-sprint.move-to-sprint.e2e-move-to-sprint(
|
||||||
ng-if="currentSprint"
|
ng-if="currentSprint"
|
||||||
href=""
|
href=""
|
||||||
title="{{'BACKLOG.MOVE_US_TO_CURRENT_SPRINT' | translate}}"
|
title="{{'BACKLOG.MOVE_US_TO_CURRENT_SPRINT' | translate}}"
|
||||||
|
@ -44,7 +44,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
||||||
)
|
)
|
||||||
tg-svg(svg-icon="icon-move")
|
tg-svg(svg-icon="icon-move")
|
||||||
span.text(translate="BACKLOG.MOVE_US_TO_CURRENT_SPRINT")
|
span.text(translate="BACKLOG.MOVE_US_TO_CURRENT_SPRINT")
|
||||||
a.trans-button.menu-button.move-to-latest-sprint.move-to-sprint.e2e-move-to-sprint(
|
a.menu-button.move-to-latest-sprint.move-to-sprint.e2e-move-to-sprint(
|
||||||
ng-if="!currentSprint"
|
ng-if="!currentSprint"
|
||||||
href=""
|
href=""
|
||||||
title="{{'BACKLOG.MOVE_US_TO_LATEST_SPRINT' | translate}}"
|
title="{{'BACKLOG.MOVE_US_TO_LATEST_SPRINT' | translate}}"
|
||||||
|
@ -52,33 +52,61 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl",
|
||||||
)
|
)
|
||||||
tg-svg(svg-icon="icon-move")
|
tg-svg(svg-icon="icon-move")
|
||||||
span.text(translate="BACKLOG.MOVE_US_TO_LATEST_SPRINT")
|
span.text(translate="BACKLOG.MOVE_US_TO_LATEST_SPRINT")
|
||||||
a.trans-button.menu-button.e2e-open-filter.ng-animate-disabled(
|
a.menu-button.e2e-open-filter.ng-animate-disabled(
|
||||||
ng-if="!ctrl.activeFilters"
|
ng-if="!ctrl.activeFilters"
|
||||||
href=""
|
href=""
|
||||||
title="{{'BACKLOG.FILTERS.TOGGLE' | translate}}"
|
title="{{'BACKLOG.FILTERS.TOGGLE' | translate}}"
|
||||||
id="show-filters-button"
|
id="show-filters-button"
|
||||||
translate="BACKLOG.FILTERS.SHOW"
|
translate="BACKLOG.FILTERS.SHOW"
|
||||||
)
|
)
|
||||||
a.trans-button.menu-button.active.e2e-open-filter.ng-animate-disabled(
|
a.menu-button.active.e2e-open-filter.ng-animate-disabled(
|
||||||
ng-if="ctrl.activeFilters"
|
ng-if="ctrl.activeFilters"
|
||||||
href=""
|
href=""
|
||||||
title="{{'BACKLOG.FILTERS.HIDE' | translate}}"
|
title="{{'BACKLOG.FILTERS.HIDE' | translate}}"
|
||||||
id="show-filters-button"
|
id="show-filters-button"
|
||||||
translate="BACKLOG.FILTERS.HIDE"
|
translate="BACKLOG.FILTERS.HIDE"
|
||||||
)
|
)
|
||||||
a.trans-button.menu-button(
|
a.menu-button(
|
||||||
ng-if="userstories.length"
|
ng-if="userstories.length"
|
||||||
href=""
|
href=""
|
||||||
title="{{'BACKLOG.TAGS.TOGGLE' | translate}}"
|
title="{{'BACKLOG.TAGS.TOGGLE' | translate}}"
|
||||||
id="show-tags"
|
id="show-tags"
|
||||||
translate="BACKLOG.TAGS.SHOW"
|
translate="BACKLOG.TAGS.SHOW"
|
||||||
)
|
)
|
||||||
|
a.menu-button.velocity-forecasting-btn.ng-animate-disabled.e2e-velocity-forecasting(
|
||||||
|
ng-if="userstories.length && ctrl.displayVelocity "
|
||||||
|
href=""
|
||||||
|
title="{{'BACKLOG.FORECASTING.TITLE' | translate}}"
|
||||||
|
translate="BACKLOG.FORECASTING.BACKLOG"
|
||||||
|
ng-click="ctrl.toggleVelocityForecasting()"
|
||||||
|
tg-check-permission="add_milestone"
|
||||||
|
)
|
||||||
|
a.menu-button.velocity-forecasting-btn.ng-animate-disabled.e2e-velocity-forecasting(
|
||||||
|
ng-if="userstories.length && !ctrl.displayVelocity && stats.speed > 0"
|
||||||
|
href=""
|
||||||
|
title="{{'BACKLOG.FORECASTING.BACKLOG' | translate}}"
|
||||||
|
translate="BACKLOG.FORECASTING.TITLE"
|
||||||
|
ng-click="ctrl.toggleVelocityForecasting()"
|
||||||
|
tg-check-permission="add_milestone"
|
||||||
|
)
|
||||||
include ../includes/components/addnewus
|
include ../includes/components/addnewus
|
||||||
|
|
||||||
|
|
||||||
section.backlog-table(ng-class="{'hidden': !userstories.length}")
|
section.backlog-table(ng-class="{'hidden': !userstories.length}")
|
||||||
include ../includes/modules/backlog-table
|
include ../includes/modules/backlog-table
|
||||||
|
|
||||||
div.empty-large.js-empty-backlog(ng-class="{'hidden': userstories === undefined || userstories.length}")
|
.forecasting-add-sprint.e2e-velocity-forecasting-add(ng-if="ctrl.displayVelocity")
|
||||||
|
tg-svg(svg-icon="icon-add")
|
||||||
|
span(
|
||||||
|
ng-if="!currentSprint"
|
||||||
|
translate="BACKLOG.FORECASTING.NEW_SPRINT"
|
||||||
|
)
|
||||||
|
span(
|
||||||
|
ng-if="currentSprint"
|
||||||
|
translate="BACKLOG.FORECASTING.CURRENT_SPRINT"
|
||||||
|
)
|
||||||
|
|
||||||
|
.empty-large.js-empty-backlog(ng-class="{'hidden': userstories === undefined || userstories.length}")
|
||||||
img(
|
img(
|
||||||
src="/#{v}/images/empty/empty_mex.png"
|
src="/#{v}/images/empty/empty_mex.png"
|
||||||
alt="{{'BACKLOG.EMPTY' | translate}}"
|
alt="{{'BACKLOG.EMPTY' | translate}}"
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
.row.us-item-row(
|
.row.us-item-row(
|
||||||
ng-repeat="us in userstories track by us.id"
|
ng-repeat="us in userstories | inArray:visibleUserStories:'ref'"
|
||||||
tg-bind-scope
|
tg-bind-scope
|
||||||
ng-class="{blocked: us.is_blocked}"
|
ng-class="{blocked: us.is_blocked}"
|
||||||
tg-class-permission="{'readonly': '!modify_us'}"
|
tg-class-permission="{'readonly': '!modify_us'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
.input(tg-check-permission="modify_us")
|
.input(tg-check-permission="modify_us")
|
||||||
input(
|
input(
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
@ -10,7 +10,7 @@ div.backlog-table-header
|
||||||
|
|
||||||
div.backlog-table-body(
|
div.backlog-table-body(
|
||||||
tg-backlog-sortable,
|
tg-backlog-sortable,
|
||||||
ng-class="{'show-tags': ctrl.showTags, 'active-filters': ctrl.activeFilters}"
|
ng-class="{'show-tags': ctrl.showTags, 'active-filters': ctrl.activeFilters, 'forecasted-stories': ctrl.displayVelocity}"
|
||||||
infinite-scroll="ctrl.loadUserstories()"
|
infinite-scroll="ctrl.loadUserstories()"
|
||||||
infinite-scroll-disabled="ctrl.disablePagination || !ctrl.firstLoadComplete"
|
infinite-scroll-disabled="ctrl.disablePagination || !ctrl.firstLoadComplete"
|
||||||
infinite-scroll-immediate-check='false'
|
infinite-scroll-immediate-check='false'
|
||||||
|
|
|
@ -3,7 +3,7 @@ tg-lightbox-close
|
||||||
form
|
form
|
||||||
h2.title(translate="LIGHTBOX.ADD_EDIT_SPRINT.TITLE")
|
h2.title(translate="LIGHTBOX.ADD_EDIT_SPRINT.TITLE")
|
||||||
fieldset
|
fieldset
|
||||||
input.sprint-name(
|
input.sprint-name.e2e-sprint-name(
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
ng-model="newSprint.name"
|
ng-model="newSprint.name"
|
||||||
|
|
|
@ -59,6 +59,23 @@
|
||||||
color: $blackish;
|
color: $blackish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
@extend %button;
|
||||||
|
border-radius: 0;
|
||||||
|
color: $blackish;
|
||||||
|
&:hover {
|
||||||
|
background: $whitish;
|
||||||
|
color: $gray;
|
||||||
|
}
|
||||||
|
&:visited {
|
||||||
|
color: $blackish;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: $blackish;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.submit-button {
|
.submit-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,24 +25,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
@include breakpoint(laptop) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
.menu-button {
|
.menu-button {
|
||||||
border-radius: 0;
|
|
||||||
color: $blackish;
|
|
||||||
display: inline-block;
|
|
||||||
padding: .4rem 1.5rem;
|
|
||||||
&.active,
|
|
||||||
&:hover {
|
|
||||||
background: $whitish;
|
|
||||||
color: $gray;
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
&:hover {
|
|
||||||
background: darken($whitish, 10%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.move-to-sprint {
|
&.move-to-sprint {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.icon-move {
|
||||||
|
margin-right: .25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.button-bulk {
|
.button-bulk {
|
||||||
margin-left: .2rem;
|
margin-left: .2rem;
|
||||||
|
@ -70,3 +62,23 @@
|
||||||
background: $white;
|
background: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forecasting-add-sprint {
|
||||||
|
@include font-size(small);
|
||||||
|
background: $mass-white;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: .5rem 0;
|
||||||
|
text-align: center;
|
||||||
|
&:hover {
|
||||||
|
background: darken($mass-white, 3%);
|
||||||
|
transition: background .2s;
|
||||||
|
}
|
||||||
|
.icon-add {
|
||||||
|
@include svg-size(1.75rem);
|
||||||
|
background: $primary-light;
|
||||||
|
fill: $white;
|
||||||
|
margin-right: 1rem;
|
||||||
|
padding: .25rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -145,6 +145,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.backlog-table-body {
|
.backlog-table-body {
|
||||||
|
&.forecasted-stories {
|
||||||
|
border: .5rem solid $mass-white;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
.row {
|
.row {
|
||||||
border-bottom: 1px solid darken($whitish, 4%);
|
border-bottom: 1px solid darken($whitish, 4%);
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
|
|
@ -131,6 +131,22 @@ helper.openNewUs = function() {
|
||||||
$$('.new-us a').get(0).click();
|
$$('.new-us a').get(0).click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
helper.velocityForecasting = function() {
|
||||||
|
return $$('.e2e-velocity-forecasting');
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.openVelocityForecasting = function() {
|
||||||
|
$$('.e2e-velocity-forecasting').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.createSprintFromForecasting = function() {
|
||||||
|
$$('.e2e-velocity-forecasting-add').click();
|
||||||
|
let sprintName = 'sprintName' + new Date().getTime();
|
||||||
|
$('.e2e-sprint-name')
|
||||||
|
.sendKeys(sprintName)
|
||||||
|
.sendKeys(protractor.Key.ENTER);
|
||||||
|
};
|
||||||
|
|
||||||
helper.openUsBacklogEdit = function(item) {
|
helper.openUsBacklogEdit = function(item) {
|
||||||
$$('.backlog-table-body .e2e-edit').get(item).click();
|
$$('.backlog-table-body .e2e-edit').get(item).click();
|
||||||
};
|
};
|
||||||
|
|
|
@ -449,6 +449,43 @@ describe('backlog', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('velocity forecasting', function() {
|
||||||
|
it('show', async function() {
|
||||||
|
browser.get(browser.params.glob.host + 'project/project-1/backlog');
|
||||||
|
await utils.common.waitLoader();
|
||||||
|
|
||||||
|
let usCount = await backlogHelper.userStories().count();
|
||||||
|
|
||||||
|
await backlogHelper.openVelocityForecasting();
|
||||||
|
utils.common.takeScreenshot('backlog', 'velocity-forecasting');
|
||||||
|
|
||||||
|
let newUsCount = await backlogHelper.userStories().count();
|
||||||
|
|
||||||
|
expect(newUsCount).is.below(usCount);
|
||||||
|
});
|
||||||
|
it('create sprint from forecasting', async function() {
|
||||||
|
browser.get(browser.params.glob.host + 'project/project-1/backlog');
|
||||||
|
await utils.common.waitLoader();
|
||||||
|
|
||||||
|
let sprintCount = await backlogHelper.sprintsOpen().count();
|
||||||
|
|
||||||
|
backlogHelper.openVelocityForecasting();
|
||||||
|
backlogHelper.createSprintFromForecasting();
|
||||||
|
|
||||||
|
let newSprintCount = await backlogHelper.sprintsOpen().count();
|
||||||
|
|
||||||
|
expect(sprintCount).is.below(newSprintCount);
|
||||||
|
});
|
||||||
|
it.only('hide forecasting if no velocity', async function() {
|
||||||
|
browser.get(browser.params.glob.host + 'project/project-5/backlog');
|
||||||
|
await utils.common.waitLoader();
|
||||||
|
|
||||||
|
let forecasting = await backlogHelper.velocityForecasting();
|
||||||
|
|
||||||
|
expect(forecasting).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('backlog filters', sharedFilters.bind(this, 'backlog', () => {
|
describe('backlog filters', sharedFilters.bind(this, 'backlog', () => {
|
||||||
return backlogHelper.userStories().count();
|
return backlogHelper.userStories().count();
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue