Refactor epics module (need tests)
parent
da6cc67897
commit
99e04c369f
|
@ -812,6 +812,7 @@ modules = [
|
||||||
"taigaHistory",
|
"taigaHistory",
|
||||||
"taigaWikiHistory",
|
"taigaWikiHistory",
|
||||||
"taigaEpics",
|
"taigaEpics",
|
||||||
|
"taigaUtils"
|
||||||
|
|
||||||
# template cache
|
# template cache
|
||||||
"templates",
|
"templates",
|
||||||
|
|
|
@ -405,6 +405,7 @@
|
||||||
},
|
},
|
||||||
"EPICS": {
|
"EPICS": {
|
||||||
"TITLE": "EPICS",
|
"TITLE": "EPICS",
|
||||||
|
"SECTION_NAME": "Epics",
|
||||||
"EPIC": "EPIC",
|
"EPIC": "EPIC",
|
||||||
"DASHBOARD": {
|
"DASHBOARD": {
|
||||||
"ADD": "+ ADD EPIC",
|
"ADD": "+ ADD EPIC",
|
||||||
|
|
|
@ -24,13 +24,19 @@ getRandomDefaultColor = taiga.getRandomDefaultColor
|
||||||
|
|
||||||
class CreateEpicController
|
class CreateEpicController
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"tgResources"
|
|
||||||
"$tgConfirm"
|
"$tgConfirm"
|
||||||
"tgAttachmentsService"
|
"tgProjectService",
|
||||||
"$q"
|
"tgEpicsService"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@rs, @confirm, @attachmentsService, @q) ->
|
constructor: (@confirm, @projectService, @epicsService) ->
|
||||||
|
# NOTE: To use Checksley setFormErrors() and validateForm()
|
||||||
|
# are defined in the directive.
|
||||||
|
|
||||||
|
# NOTE: We use project as no inmutable object to make
|
||||||
|
# the code compatible with the old code
|
||||||
|
@.project = @projectService.project.toJS()
|
||||||
|
|
||||||
@.newEpic = {
|
@.newEpic = {
|
||||||
color: getRandomDefaultColor()
|
color: getRandomDefaultColor()
|
||||||
project: @.project.id
|
project: @.project.id
|
||||||
|
@ -39,26 +45,22 @@ class CreateEpicController
|
||||||
}
|
}
|
||||||
@.attachments = Immutable.List()
|
@.attachments = Immutable.List()
|
||||||
|
|
||||||
|
@.loading = false
|
||||||
|
|
||||||
createEpic: () ->
|
createEpic: () ->
|
||||||
return if not @.validateForm()
|
return if not @.validateForm()
|
||||||
|
|
||||||
@.loading = true
|
@.loading = true
|
||||||
|
|
||||||
promise = @rs.epics.post(@.newEpic)
|
@epicsService.createEpic(@.epic, @.attachments)
|
||||||
promise.then (response) =>
|
.then (response) => # On success
|
||||||
@._createAttachments(response.data)
|
|
||||||
promise.then (response) =>
|
|
||||||
@.onCreateEpic()
|
@.onCreateEpic()
|
||||||
promise.then null, (response) =>
|
.then null, (response) => # On error
|
||||||
@.setFormErrors(response.data)
|
@.setFormErrors(response.data)
|
||||||
|
|
||||||
if response.data._error_message
|
if response.data._error_message
|
||||||
confirm.notify("error", response.data._error_message)
|
@confirm.notify("error", response.data._error_message)
|
||||||
promise.finally () =>
|
|
||||||
@.loading = false
|
@.loading = false
|
||||||
|
|
||||||
return promise
|
|
||||||
|
|
||||||
# Color selector
|
# Color selector
|
||||||
selectColor: (color) ->
|
selectColor: (color) ->
|
||||||
@.newEpic.color = color
|
@.newEpic.color = color
|
||||||
|
@ -77,9 +79,4 @@ class CreateEpicController
|
||||||
addAttachment: (attachment) ->
|
addAttachment: (attachment) ->
|
||||||
@.attachments.push(attachment)
|
@.attachments.push(attachment)
|
||||||
|
|
||||||
_createAttachments: (epic) ->
|
|
||||||
promises = _.map @.attachments.toJS(), (attachment) =>
|
|
||||||
return @attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic')
|
|
||||||
return @q.all(promises)
|
|
||||||
|
|
||||||
angular.module("taigaEpics").controller("CreateEpicCtrl", CreateEpicController)
|
angular.module("taigaEpics").controller("CreateEpicCtrl", CreateEpicController)
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
# File: create-epic.directive.coffee
|
# File: create-epic.directive.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module('taigaEpics')
|
|
||||||
|
|
||||||
CreateEpicDirective = () ->
|
CreateEpicDirective = () ->
|
||||||
link = (scope, el, attrs, ctrl) ->
|
link = (scope, el, attrs, ctrl) ->
|
||||||
form = el.find("form").checksley()
|
form = el.find("form").checksley()
|
||||||
|
@ -35,12 +33,9 @@ CreateEpicDirective = () ->
|
||||||
controller: "CreateEpicCtrl",
|
controller: "CreateEpicCtrl",
|
||||||
controllerAs: "vm",
|
controllerAs: "vm",
|
||||||
bindToController: {
|
bindToController: {
|
||||||
project: '=',
|
|
||||||
onCreateEpic: '&'
|
onCreateEpic: '&'
|
||||||
},
|
},
|
||||||
scope: {}
|
scope: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateEpicDirective.$inject = []
|
angular.module('taigaEpics').directive("tgCreateEpic", CreateEpicDirective)
|
||||||
|
|
||||||
module.directive("tgCreateEpic", CreateEpicDirective)
|
|
||||||
|
|
|
@ -17,19 +17,23 @@
|
||||||
# File: epics-table.controller.coffee
|
# File: epics-table.controller.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module("taigaEpics")
|
|
||||||
|
|
||||||
class EpicRowController
|
class EpicRowController
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"tgResources",
|
"$tgConfirm",
|
||||||
"$tgConfirm"
|
"tgProjectService",
|
||||||
|
"tgEpicsService"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@rs, @confirm) ->
|
constructor: (@confirm, @projectService, @epicsService) ->
|
||||||
@.displayUserStories = false
|
@.displayUserStories = false
|
||||||
@.displayAssignedTo = false
|
@.displayAssignedTo = false
|
||||||
|
@.displayStatusList = false
|
||||||
@.loadingStatus = false
|
@.loadingStatus = false
|
||||||
|
|
||||||
|
# NOTE: We use project as no inmutable object to make
|
||||||
|
# the code compatible with the old code
|
||||||
|
@.project = @projectService.project.toJS()
|
||||||
|
|
||||||
_calculateProgressBar: () ->
|
_calculateProgressBar: () ->
|
||||||
if @.epic.getIn(['status_extra_info', 'is_closed']) == true
|
if @.epic.getIn(['status_extra_info', 'is_closed']) == true
|
||||||
@.percentage = "100%"
|
@.percentage = "100%"
|
||||||
|
@ -42,68 +46,32 @@ class EpicRowController
|
||||||
else
|
else
|
||||||
@.percentage = "#{@.closed * 100 / @.total}%"
|
@.percentage = "#{@.closed * 100 / @.total}%"
|
||||||
|
|
||||||
updateEpicStatus: (status) ->
|
canEditEpics: () ->
|
||||||
@.loadingStatus = true
|
return @projectService.hasPermission("modify_epic")
|
||||||
@.displayStatusList = false
|
|
||||||
patch = {
|
|
||||||
'status': status,
|
|
||||||
'version': @.epic.get('version')
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess = =>
|
toggleUserStoryList: () ->
|
||||||
@.loadingStatus = false
|
|
||||||
@.onUpdateEpic()
|
|
||||||
|
|
||||||
onError = (data) =>
|
|
||||||
@confirm.notify('error')
|
|
||||||
|
|
||||||
return @rs.epics.patch(@.epic.get('id'), patch).then(onSuccess, onError)
|
|
||||||
|
|
||||||
requestUserStories: (epic) ->
|
|
||||||
if !@.displayUserStories
|
if !@.displayUserStories
|
||||||
|
@epicsService.listRelatedUserStories(@.epic)
|
||||||
onSuccess = (data) =>
|
.then (userStories) =>
|
||||||
@.epicStories = data
|
@.epicStories = userStories
|
||||||
@.displayUserStories = true
|
@.displayUserStories = true
|
||||||
|
.catch =>
|
||||||
onError = (data) =>
|
|
||||||
@confirm.notify('error')
|
@confirm.notify('error')
|
||||||
|
|
||||||
return @rs.userstories.listInEpic(@.epic.get('id')).then(onSuccess, onError)
|
|
||||||
else
|
else
|
||||||
@.displayUserStories = false
|
@.displayUserStories = false
|
||||||
|
|
||||||
onRemoveAssigned: () ->
|
updateStatus: (statusId) ->
|
||||||
id = @.epic.get('id')
|
@.displayStatusList = false
|
||||||
version = @.epic.get('version')
|
@.loadingStatus = true
|
||||||
patch = {
|
return @epicsService.updateEpicStatus(@.epic, statusId)
|
||||||
'assigned_to': null,
|
.catch () =>
|
||||||
'version': version
|
@confirm.notify('error')
|
||||||
}
|
.finally () =>
|
||||||
|
@.loadingStatus = false
|
||||||
|
|
||||||
onSuccess = =>
|
updateAssignedTo: (member) ->
|
||||||
@.onUpdateEpic()
|
return @epicsService.updateEpicAssignedTo(@.epic, member?.id)
|
||||||
|
.catch () =>
|
||||||
onError = (data) =>
|
|
||||||
@confirm.notify('error')
|
@confirm.notify('error')
|
||||||
|
|
||||||
return @rs.epics.patch(id, patch).then(onSuccess, onError)
|
angular.module("taigaEpics").controller("EpicRowCtrl", EpicRowController)
|
||||||
|
|
||||||
onAssignTo: (member) ->
|
|
||||||
id = @.epic.get('id')
|
|
||||||
version = @.epic.get('version')
|
|
||||||
patch = {
|
|
||||||
'assigned_to': member.id,
|
|
||||||
'version': version
|
|
||||||
}
|
|
||||||
|
|
||||||
onSuccess = =>
|
|
||||||
@.onUpdateEpic()
|
|
||||||
@confirm.notify('success')
|
|
||||||
|
|
||||||
onError = (data) =>
|
|
||||||
@confirm.notify('error')
|
|
||||||
|
|
||||||
return @rs.epics.patch(id, patch).then(onSuccess, onError)
|
|
||||||
|
|
||||||
module.controller("EpicRowCtrl", EpicRowController)
|
|
||||||
|
|
|
@ -17,28 +17,16 @@
|
||||||
# File: epics-table.directive.coffee
|
# File: epics-table.directive.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module('taigaEpics')
|
|
||||||
|
|
||||||
EpicRowDirective = () ->
|
EpicRowDirective = () ->
|
||||||
|
|
||||||
link = (scope, el, attrs, ctrl) ->
|
|
||||||
ctrl._calculateProgressBar()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link,
|
|
||||||
templateUrl:"epics/dashboard/epic-row/epic-row.html",
|
templateUrl:"epics/dashboard/epic-row/epic-row.html",
|
||||||
controller: "EpicRowCtrl",
|
controller: "EpicRowCtrl",
|
||||||
controllerAs: "vm",
|
controllerAs: "vm",
|
||||||
bindToController: true,
|
bindToController: true,
|
||||||
scope: {
|
scope: {
|
||||||
project: '=',
|
|
||||||
epic: '=',
|
epic: '=',
|
||||||
column: '=',
|
column: '=',
|
||||||
permissions: '=',
|
|
||||||
onUpdateEpic: "&"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EpicRowDirective.$inject = []
|
angular.module('taigaEpics').directive("tgEpicRow", EpicRowDirective)
|
||||||
|
|
||||||
module.directive("tgEpicRow", EpicRowDirective)
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
.epic-row.e2e-epic-row(
|
.epic-row.e2e-epic-row(
|
||||||
ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}"
|
ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}"
|
||||||
ng-click="vm.requestUserStories(vm.epic)"
|
ng-click="vm.toggleUserStoryList()"
|
||||||
)
|
)
|
||||||
tg-svg.icon-drag(
|
tg-svg.icon-drag(
|
||||||
svg-icon="icon-drag"
|
svg-icon="icon-drag"
|
||||||
)
|
)
|
||||||
|
|
||||||
.vote(
|
.vote(
|
||||||
ng-if="vm.column.votes"
|
ng-if="vm.column.votes"
|
||||||
ng-class="{'is-voter': vm.epic.get('is_voter')}"
|
ng-class="{'is-voter': vm.epic.get('is_voter')}"
|
||||||
|
@ -28,23 +29,26 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
.project(ng-if="vm.column.project")
|
.project(ng-if="vm.column.project")
|
||||||
.sprint(
|
|
||||||
ng-if="vm.column.sprint"
|
.sprint(ng-if="vm.column.sprint")
|
||||||
)
|
|
||||||
.assigned.e2e-assigned-to
|
.assigned.e2e-assigned-tio(ng-if="vm.column.assigned")
|
||||||
tg-assigned-to-component(
|
tg-assigned-to-component(
|
||||||
assigned-to="vm.epic.get('assigned_to_extra_info')"
|
assigned-to="vm.epic.get('assigned_to_extra_info')"
|
||||||
project="vm.project"
|
project="vm.project"
|
||||||
on-remove-assigned="vm.onRemoveAssigned()"
|
on-remove-assigned="vm.updateAssignedTo(null)"
|
||||||
on-assign-to="vm.onAssignTo(member)"
|
on-assign-to="vm.updateAssignedTo(member)"
|
||||||
|
tg-isolate-click
|
||||||
)
|
)
|
||||||
|
|
||||||
.status(
|
.status(
|
||||||
ng-if="vm.column.status && !vm.permissions.canEdit"
|
ng-if="vm.column.status && !vm.canEditEpics()"
|
||||||
)
|
)
|
||||||
span {{vm.epic.getIn(['status_extra_info', 'name'])}}
|
span {{vm.epic.getIn(['status_extra_info', 'name'])}}
|
||||||
.status(
|
.status(
|
||||||
ng-if="vm.column.status && vm.permissions.canEdit"
|
ng-if="vm.column.status && vm.canEditEpics()"
|
||||||
ng-mouseleave="vm.displayStatusList = false"
|
ng-mouseleave="vm.displayStatusList = false"
|
||||||
|
tg-isolate-click
|
||||||
)
|
)
|
||||||
button(
|
button(
|
||||||
ng-click="vm.displayStatusList = true"
|
ng-click="vm.displayStatusList = true"
|
||||||
|
@ -59,20 +63,19 @@
|
||||||
ul.epic-statuses(ng-if="vm.displayStatusList")
|
ul.epic-statuses(ng-if="vm.displayStatusList")
|
||||||
li.e2e-edit-epic-status(
|
li.e2e-edit-epic-status(
|
||||||
ng-repeat="status in vm.project.epic_statuses | orderBy:'order'"
|
ng-repeat="status in vm.project.epic_statuses | orderBy:'order'"
|
||||||
ng-click="vm.updateEpicStatus(status.id)"
|
ng-click="vm.updateStatus(status.id)"
|
||||||
) {{status.name}}
|
) {{status.name}}
|
||||||
|
|
||||||
.progress(ng-if="vm.column.progress")
|
.progress(ng-if="vm.column.progress")
|
||||||
.progress-bar
|
.progress-bar
|
||||||
.progress-status(
|
.progress-status(
|
||||||
ng-if="::vm.percentage"
|
ng-if="::vm.percentage"
|
||||||
ng-style="{'width':vm.percentage}"
|
ng-style="{'width':vm.percentage}"
|
||||||
)
|
)
|
||||||
.epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories")
|
|
||||||
|
|
||||||
|
.epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories")
|
||||||
.epic-story(tg-repeat="story in vm.epicStories track by story.get('id')")
|
.epic-story(tg-repeat="story in vm.epicStories track by story.get('id')")
|
||||||
tg-story-row.e2e-story(
|
tg-story-row.e2e-story(
|
||||||
epic="vm.epic"
|
|
||||||
story="story"
|
story="story"
|
||||||
project="vm.project"
|
|
||||||
column="vm.column"
|
column="vm.column"
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,48 +17,50 @@
|
||||||
# File: epics.dashboard.controller.coffee
|
# File: epics.dashboard.controller.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module("taigaEpics")
|
taiga = @.taiga
|
||||||
|
|
||||||
|
|
||||||
class EpicsDashboardController
|
class EpicsDashboardController
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$tgResources",
|
|
||||||
"tgResources",
|
|
||||||
"$routeParams",
|
"$routeParams",
|
||||||
"tgErrorHandlingService",
|
"tgErrorHandlingService",
|
||||||
"tgLightboxFactory",
|
"tgLightboxFactory",
|
||||||
"lightboxService",
|
"lightboxService",
|
||||||
"$tgConfirm"
|
"$tgConfirm",
|
||||||
|
"tgProjectService",
|
||||||
|
"tgEpicsService"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) ->
|
constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService,
|
||||||
@.sectionName = "Epics"
|
@confirm, @projectService, @epicsService) ->
|
||||||
@.createEpic = false
|
|
||||||
|
|
||||||
loadProject: () ->
|
@.sectionName = "EPICS.SECTION_NAME"
|
||||||
return @rs.projects.getBySlug(@params.pslug).then (project) =>
|
|
||||||
if not project.is_epics_activated
|
taiga.defineImmutableProperty @, 'project', () => return @projectService.project
|
||||||
|
taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics
|
||||||
|
|
||||||
|
@._loadInitialData()
|
||||||
|
|
||||||
|
_loadInitialData: () ->
|
||||||
|
@epicsService.clear()
|
||||||
|
@projectService.setProjectBySlug(@params.pslug)
|
||||||
|
.then () =>
|
||||||
|
if not @.project.get("is_epics_activated") or not @projectService.hasPermission("view_epics")
|
||||||
@errorHandlingService.permissionDenied()
|
@errorHandlingService.permissionDenied()
|
||||||
@.project = project
|
|
||||||
@.loadEpics()
|
|
||||||
|
|
||||||
loadEpics: () ->
|
@epicsService.fetchEpics()
|
||||||
projectId = @.project.id
|
|
||||||
return @resources.epics.list(projectId).then (epics) =>
|
|
||||||
@.epics = epics
|
|
||||||
|
|
||||||
_onCreateEpic: () ->
|
canCreateEpics: () ->
|
||||||
@lightboxService.closeAll()
|
return @projectService.hasPermission("add_epic")
|
||||||
@confirm.notify("success")
|
|
||||||
@.loadEpics()
|
|
||||||
|
|
||||||
onCreateEpic: () ->
|
onCreateEpic: () ->
|
||||||
@lightboxFactory.create('tg-create-epic', {
|
@lightboxFactory.create('tg-create-epic', {
|
||||||
"class": "lightbox lightbox-create-epic open"
|
"class": "lightbox lightbox-create-epic open"
|
||||||
"project": "project"
|
|
||||||
"on-create-epic": "onCreateEpic()"
|
"on-create-epic": "onCreateEpic()"
|
||||||
}, {
|
}, {
|
||||||
"project": @.project
|
"onCreateEpic": () =>
|
||||||
"onCreateEpic": @._onCreateEpic.bind(this)
|
@lightboxService.closeAll()
|
||||||
|
@confirm.notify("success")
|
||||||
})
|
})
|
||||||
|
|
||||||
module.controller("EpicsDashboardCtrl", EpicsDashboardController)
|
angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController)
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
header.header-with-actions
|
header.header-with-actions
|
||||||
h1(
|
h1(
|
||||||
tg-main-title
|
tg-main-title
|
||||||
project-name="vm.project.name"
|
project-name="vm.project.get('name')"
|
||||||
i18n-section-name="{{ vm.sectionName }}"
|
i18n-section-name="{{vm.sectionName}}"
|
||||||
)
|
)
|
||||||
.action-buttons(ng-if="vm.epics.size")
|
.action-buttons(ng-if="vm.epics.size && vm.canCreateEpics()")
|
||||||
button.button-green.e2e-create-epic(
|
button.button-green.e2e-create-epic(
|
||||||
translate="EPICS.DASHBOARD.ADD"
|
translate="EPICS.DASHBOARD.ADD"
|
||||||
title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}",
|
title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}",
|
||||||
|
@ -15,10 +15,7 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
tg-epics-table(
|
tg-epics-table(
|
||||||
ng-if="vm.project && vm.epics.size"
|
ng-if="vm.epics.size"
|
||||||
project="vm.project"
|
|
||||||
epics="vm.epics"
|
|
||||||
on-update-epic="vm.loadEpics()"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
section.empty-epics(ng-if="!vm.epics.size")
|
section.empty-epics(ng-if="!vm.epics.size")
|
||||||
|
@ -30,11 +27,12 @@
|
||||||
p(translate="EPICS.EMPTY.EXPLANATION")
|
p(translate="EPICS.EMPTY.EXPLANATION")
|
||||||
a(
|
a(
|
||||||
translate="EPICS.EMPTY.HELP"
|
translate="EPICS.EMPTY.HELP"
|
||||||
href="https://tree.taiga.io/support/frequently-asked-questions/who-is-taiga-for/"
|
href="#TODO: Link to Epics section in taiga-support"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
ng-title="EPICS.EMPTY.HELP | translate"
|
ng-title="EPICS.EMPTY.HELP | translate"
|
||||||
)
|
)
|
||||||
button.create-epic.button-green(
|
button.create-epic.button-green(
|
||||||
|
ng-if="vm.canCreateEpics()"
|
||||||
translate="EPICS.DASHBOARD.ADD"
|
translate="EPICS.DASHBOARD.ADD"
|
||||||
title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}"
|
title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}"
|
||||||
ng-click="vm.onCreateEpic()"
|
ng-click="vm.onCreateEpic()"
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
# File: epics-sortable.directive.coffee
|
# File: epics-sortable.directive.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
EpicsSortableDirective = ($parse) ->
|
EpicsSortableDirective = ($parse, projectService) ->
|
||||||
link = (scope, el, attrs) ->
|
link = (scope, el, attrs) ->
|
||||||
|
return if not projectService.hasPermission("modify_epic")
|
||||||
|
|
||||||
callback = $parse(attrs.tgEpicsSortable)
|
callback = $parse(attrs.tgEpicsSortable)
|
||||||
|
|
||||||
drake = dragula([el[0]], {
|
drake = dragula([el[0]], {
|
||||||
|
@ -55,7 +57,8 @@ EpicsSortableDirective = ($parse) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
EpicsSortableDirective.$inject = [
|
EpicsSortableDirective.$inject = [
|
||||||
"$parse"
|
"$parse",
|
||||||
|
"tgProjectService"
|
||||||
]
|
]
|
||||||
|
|
||||||
angular.module("taigaComponents").directive("tgEpicsSortable", EpicsSortableDirective)
|
angular.module("taigaComponents").directive("tgEpicsSortable", EpicsSortableDirective)
|
||||||
|
|
|
@ -17,12 +17,16 @@
|
||||||
# File: epics-table.controller.coffee
|
# File: epics-table.controller.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module("taigaEpics")
|
taiga = @.taiga
|
||||||
|
|
||||||
|
|
||||||
class EpicsTableController
|
class EpicsTableController
|
||||||
@.$inject = []
|
@.$inject = [
|
||||||
|
"$tgConfirm",
|
||||||
|
"tgEpicsService"
|
||||||
|
]
|
||||||
|
|
||||||
constructor: () ->
|
constructor: (@confirm, @epicsService) ->
|
||||||
@.displayOptions = false
|
@.displayOptions = false
|
||||||
@.displayVotes = true
|
@.displayVotes = true
|
||||||
@.column = {
|
@.column = {
|
||||||
|
@ -35,15 +39,14 @@ class EpicsTableController
|
||||||
progress: true
|
progress: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@.permissions = {
|
taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics
|
||||||
canEdit: _.includes(@.project.my_permissions, 'modify_epic')
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleEpicTableOptions: () ->
|
toggleEpicTableOptions: () ->
|
||||||
@.displayOptions = !@.displayOptions
|
@.displayOptions = !@.displayOptions
|
||||||
|
|
||||||
reorderEpic: (epic, newIndex) ->
|
reorderEpic: (epic, newIndex) ->
|
||||||
console.log epic, newIndex
|
@epicsService.reorderEpic(epic, newIndex)
|
||||||
|
.then null, () => # on error
|
||||||
|
@confirm.notify("error")
|
||||||
|
|
||||||
|
angular.module("taigaEpics").controller("EpicsTableCtrl", EpicsTableController)
|
||||||
module.controller("EpicsTableCtrl", EpicsTableController)
|
|
||||||
|
|
|
@ -17,21 +17,13 @@
|
||||||
# File: epics-table.directive.coffee
|
# File: epics-table.directive.coffee
|
||||||
###
|
###
|
||||||
|
|
||||||
module = angular.module('taigaEpics')
|
|
||||||
|
|
||||||
EpicsTableDirective = () ->
|
EpicsTableDirective = () ->
|
||||||
return {
|
return {
|
||||||
templateUrl:"epics/dashboard/epics-table/epics-table.html",
|
templateUrl:"epics/dashboard/epics-table/epics-table.html",
|
||||||
controller: "EpicsTableCtrl",
|
controller: "EpicsTableCtrl",
|
||||||
controllerAs: "vm",
|
controllerAs: "vm",
|
||||||
bindToController: {
|
|
||||||
epics: "=",
|
|
||||||
project: "=",
|
|
||||||
onUpdateEpic: "&"
|
|
||||||
}
|
|
||||||
scope: {}
|
scope: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
EpicsTableDirective.$inject = []
|
|
||||||
|
|
||||||
module.directive("tgEpicsTable", EpicsTableDirective)
|
angular.module('taigaEpics').directive("tgEpicsTable", EpicsTableDirective)
|
||||||
|
|
|
@ -95,8 +95,5 @@ mixin epicSwitch(name, model)
|
||||||
)
|
)
|
||||||
tg-epic-row.e2e-epic(
|
tg-epic-row.e2e-epic(
|
||||||
epic="epic"
|
epic="epic"
|
||||||
project="vm.project"
|
|
||||||
column="vm.column"
|
column="vm.column"
|
||||||
on-update-epic="vm.onUpdateEpic()"
|
|
||||||
permissions="vm.permissions"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,13 +29,8 @@ class StoryRowController
|
||||||
if @.story.get('is_closed') == true
|
if @.story.get('is_closed') == true
|
||||||
@.percentage = "100%"
|
@.percentage = "100%"
|
||||||
else
|
else
|
||||||
tasks = @.story.get('tasks').toJS()
|
|
||||||
totalTasks = @.story.get('tasks').size
|
totalTasks = @.story.get('tasks').size
|
||||||
areTasksCompleted = _.map(tasks, 'is_closed')
|
totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size
|
||||||
totalTasksCompleted = _.pull(areTasksCompleted, false).length
|
|
||||||
@.percentage = "#{totalTasksCompleted * 100 / totalTasks}%"
|
@.percentage = "#{totalTasksCompleted * 100 / totalTasks}%"
|
||||||
|
|
||||||
onSelectAssignedTo: () ->
|
|
||||||
console.log 'ng-click="vm.onSelectAssignedTo()"'
|
|
||||||
|
|
||||||
module.controller("StoryRowCtrl", StoryRowController)
|
module.controller("StoryRowCtrl", StoryRowController)
|
||||||
|
|
|
@ -20,20 +20,15 @@
|
||||||
module = angular.module('taigaEpics')
|
module = angular.module('taigaEpics')
|
||||||
|
|
||||||
StoryRowDirective = () ->
|
StoryRowDirective = () ->
|
||||||
|
|
||||||
return {
|
return {
|
||||||
templateUrl:"epics/dashboard/story-row/story-row.html",
|
templateUrl:"epics/dashboard/story-row/story-row.html",
|
||||||
controller: "StoryRowCtrl",
|
controller: "StoryRowCtrl",
|
||||||
controllerAs: "vm",
|
controllerAs: "vm",
|
||||||
bindToController: true,
|
bindToController: true,
|
||||||
scope: {
|
scope: {
|
||||||
epic: '=',
|
|
||||||
story: '=',
|
story: '=',
|
||||||
project: '=',
|
|
||||||
column: '='
|
column: '='
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StoryRowDirective.$inject = []
|
|
||||||
|
|
||||||
module.directive("tgStoryRow", StoryRowDirective)
|
module.directive("tgStoryRow", StoryRowDirective)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.story-row(
|
.story-row(
|
||||||
ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}"
|
ng-class="{'is-blocked': vm.story.get('is_blocked'), 'is-closed': vm.story.get('is_closed')}"
|
||||||
)
|
)
|
||||||
.vote(
|
.vote(
|
||||||
ng-if="vm.column.votes"
|
ng-if="vm.column.votes"
|
||||||
|
@ -11,12 +11,12 @@
|
||||||
.name(ng-if="vm.column.name")
|
.name(ng-if="vm.column.name")
|
||||||
- var hash = "#";
|
- var hash = "#";
|
||||||
a(
|
a(
|
||||||
tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')"
|
tg-nav="project-userstories-detail:project=vm.story.getIn(['project_extra_info', 'slug']),ref=vm.story.get('ref')"
|
||||||
ng-attr-title="{{::vm.story.get('subject')}}"
|
ng-attr-title="{{::vm.story.get('subject')}}"
|
||||||
) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}}
|
) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}}
|
||||||
tg-belong-to-epics(
|
tg-belong-to-epics(
|
||||||
format="pill"
|
|
||||||
ng-if="vm.story.get('epics')"
|
ng-if="vm.story.get('epics')"
|
||||||
|
format="pill"
|
||||||
epics="vm.story.get('epics')"
|
epics="vm.story.get('epics')"
|
||||||
)
|
)
|
||||||
.project(
|
.project(
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# 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: epics.service.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
|
||||||
|
class EpicsService
|
||||||
|
@.$inject = [
|
||||||
|
"tgProjectService",
|
||||||
|
"tgAttachmentsService"
|
||||||
|
"tgResources",
|
||||||
|
"tgXhrErrorService",
|
||||||
|
"$q"
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) ->
|
||||||
|
@._epics = Immutable.List()
|
||||||
|
taiga.defineImmutableProperty @, "epics", () => return @._epics
|
||||||
|
|
||||||
|
clear: () ->
|
||||||
|
@._epics = Immutable.List()
|
||||||
|
|
||||||
|
fetchEpics: () ->
|
||||||
|
return @resources.epics.list(@projectService.project.get("id"))
|
||||||
|
.then (epics) =>
|
||||||
|
@._epics = epics
|
||||||
|
.catch (xhr) =>
|
||||||
|
@xhrError.response(xhr)
|
||||||
|
|
||||||
|
listRelatedUserStories: (epic) ->
|
||||||
|
return @resources.userstories.listInEpic(epic.get('id'))
|
||||||
|
|
||||||
|
createEpic: (epicData, attachments) ->
|
||||||
|
@.epicData.project = @projectsService.project.id
|
||||||
|
|
||||||
|
return @resources.epics.post(@.epicData)
|
||||||
|
.then (epic) =>
|
||||||
|
promises = _.map attachments.toJS(), (attachment) =>
|
||||||
|
@attachmentsService.upload(attachment.file, epic.get("id"), epic.get("project"), 'epic')
|
||||||
|
@q.all(promises).then () =>
|
||||||
|
@.fetchEpics()
|
||||||
|
|
||||||
|
reorderEpic: (epic, newIndex) ->
|
||||||
|
withoutMoved = @.epics.filter (it) => it.get("id") != epic.get("id")
|
||||||
|
beforeDestination = withoutMoved.slice(0, newIndex)
|
||||||
|
|
||||||
|
previous = beforeDestination.last()
|
||||||
|
newOrder = if !previous then 0 else epic.get("epics_order") + 1
|
||||||
|
|
||||||
|
previousWithTheSameOrder = beforeDestination.filter (it) =>
|
||||||
|
it.get("epics_order") == previous.get("epics_order")
|
||||||
|
setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) =>
|
||||||
|
[it.get('id'), it.get("epics_order")]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
order: newOrder,
|
||||||
|
version: epic.get("version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return @resources.epics.reorder(epic.get("id"), data, setOrders)
|
||||||
|
.then () =>
|
||||||
|
@.fetchEpics()
|
||||||
|
|
||||||
|
updateEpicStatus: (epic, statusId) ->
|
||||||
|
data = {
|
||||||
|
status: statusId,
|
||||||
|
version: epic.get("version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return @resources.epics.patch(epic.get("id"), data)
|
||||||
|
.then () =>
|
||||||
|
@.fetchEpics()
|
||||||
|
|
||||||
|
updateEpicAssignedTo: (epic, userId) ->
|
||||||
|
data = {
|
||||||
|
assigned_to: userId,
|
||||||
|
version: epic.get("version")
|
||||||
|
}
|
||||||
|
|
||||||
|
return @resources.epics.patch(epic.get("id"), data)
|
||||||
|
.then () =>
|
||||||
|
@.fetchEpics()
|
||||||
|
|
||||||
|
angular.module("taigaEpics").service("tgEpicsService", EpicsService)
|
|
@ -51,6 +51,13 @@ Resource = (urlsService, http) ->
|
||||||
|
|
||||||
return http.post(url, params)
|
return http.post(url, params)
|
||||||
|
|
||||||
|
service.reorder = (id, data, setOrders) ->
|
||||||
|
url = urlsService.resolve("epics") + "/#{id}"
|
||||||
|
|
||||||
|
options = {"headers": {"set-orders": JSON.stringify(setOrders)}}
|
||||||
|
|
||||||
|
return http.patch(url, data, null, options)
|
||||||
|
|
||||||
service.addRelatedUserstory = (epicId, userstoryId) ->
|
service.addRelatedUserstory = (epicId, userstoryId) ->
|
||||||
url = urlsService.resolve("epic-related-userstories", epicId)
|
url = urlsService.resolve("epic-related-userstories", epicId)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# 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: isolate-click.directive.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
IsolateClickDirective = () ->
|
||||||
|
link = (scope, el, attrs) ->
|
||||||
|
el.on 'click', (e) =>
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
return {link: link}
|
||||||
|
|
||||||
|
angular.module("taigaUtils").directive("tgIsolateClick", IsolateClickDirective)
|
|
@ -0,0 +1,20 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# 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: utils.module.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
module = angular.module("taigaUtils", [])
|
Loading…
Reference in New Issue