Refactor epics module (need tests)

stable
David Barragán Merino 2016-09-07 15:29:00 +02:00
parent da6cc67897
commit 99e04c369f
21 changed files with 286 additions and 195 deletions

View File

@ -812,6 +812,7 @@ modules = [
"taigaHistory", "taigaHistory",
"taigaWikiHistory", "taigaWikiHistory",
"taigaEpics", "taigaEpics",
"taigaUtils"
# template cache # template cache
"templates", "templates",

View File

@ -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",

View File

@ -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,25 +45,21 @@ 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) @.onCreateEpic()
promise.then (response) => .then null, (response) => # On error
@.onCreateEpic() @.setFormErrors(response.data)
promise.then null, (response) => if response.data._error_message
@.setFormErrors(response.data) @confirm.notify("error", response.data._error_message)
@.loading = false
if response.data._error_message
confirm.notify("error", response.data._error_message)
promise.finally () =>
@.loading = false
return promise
# Color selector # Color selector
selectColor: (color) -> selectColor: (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)

View File

@ -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)

View File

@ -33,11 +33,11 @@ tg-lightbox-close
) )
fieldset.tags-block fieldset.tags-block
tg-tag-line-common( tg-tag-line-common(
project="vm.project" project="vm.project"
tags="vm.newEpic.tags" tags="vm.newEpic.tags"
permissions="add_epic" permissions="add_epic"
on-add-tag="vm.addTag(name, color)" on-add-tag="vm.addTag(name, color)"
on-delete-tag="vm.deleteTag(tag)" on-delete-tag="vm.deleteTag(tag)"
) )
fieldset fieldset
textarea.e2e-create-epic-description( textarea.e2e-create-epic-description(

View File

@ -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 () =>
@confirm.notify('error')
onError = (data) => angular.module("taigaEpics").controller("EpicRowCtrl", EpicRowController)
@confirm.notify('error')
return @rs.epics.patch(id, patch).then(onSuccess, onError)
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)

View File

@ -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)

View File

@ -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"
) )

View File

@ -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
@errorHandlingService.permissionDenied()
@.project = project
@.loadEpics()
loadEpics: () -> taiga.defineImmutableProperty @, 'project', () => return @projectService.project
projectId = @.project.id taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics
return @resources.epics.list(projectId).then (epics) =>
@.epics = epics
_onCreateEpic: () -> @._loadInitialData()
@lightboxService.closeAll()
@confirm.notify("success") _loadInitialData: () ->
@.loadEpics() @epicsService.clear()
@projectService.setProjectBySlug(@params.pslug)
.then () =>
if not @.project.get("is_epics_activated") or not @projectService.hasPermission("view_epics")
@errorHandlingService.permissionDenied()
@epicsService.fetchEpics()
canCreateEpics: () ->
return @projectService.hasPermission("add_epic")
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)

View File

@ -4,23 +4,20 @@
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 }}",
ng-click="vm.onCreateEpic()" ng-click="vm.onCreateEpic()"
) )
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")
img( img(
src="/#{v}/images/epics-empty.png" src="/#{v}/images/epics-empty.png"
@ -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()"

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"
) )

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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", [])