diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index fb81877e..513ee377 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -812,6 +812,7 @@ modules = [ "taigaHistory", "taigaWikiHistory", "taigaEpics", + "taigaUtils" # template cache "templates", diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 38a18a64..097fcf39 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -405,6 +405,7 @@ }, "EPICS": { "TITLE": "EPICS", + "SECTION_NAME": "Epics", "EPIC": "EPIC", "DASHBOARD": { "ADD": "+ ADD EPIC", diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 570ab10c..119ec352 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -24,13 +24,19 @@ getRandomDefaultColor = taiga.getRandomDefaultColor class CreateEpicController @.$inject = [ - "tgResources" "$tgConfirm" - "tgAttachmentsService" - "$q" + "tgProjectService", + "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 = { color: getRandomDefaultColor() project: @.project.id @@ -39,25 +45,21 @@ class CreateEpicController } @.attachments = Immutable.List() + @.loading = false + createEpic: () -> return if not @.validateForm() @.loading = true - promise = @rs.epics.post(@.newEpic) - promise.then (response) => - @._createAttachments(response.data) - promise.then (response) => - @.onCreateEpic() - promise.then null, (response) => - @.setFormErrors(response.data) - - if response.data._error_message - confirm.notify("error", response.data._error_message) - promise.finally () => - @.loading = false - - return promise + @epicsService.createEpic(@.epic, @.attachments) + .then (response) => # On success + @.onCreateEpic() + .then null, (response) => # On error + @.setFormErrors(response.data) + if response.data._error_message + @confirm.notify("error", response.data._error_message) + @.loading = false # Color selector selectColor: (color) -> @@ -77,9 +79,4 @@ class CreateEpicController addAttachment: (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) diff --git a/app/modules/epics/create-epic/create-epic.directive.coffee b/app/modules/epics/create-epic/create-epic.directive.coffee index abb527a7..fda1525d 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -17,8 +17,6 @@ # File: create-epic.directive.coffee ### -module = angular.module('taigaEpics') - CreateEpicDirective = () -> link = (scope, el, attrs, ctrl) -> form = el.find("form").checksley() @@ -35,12 +33,9 @@ CreateEpicDirective = () -> controller: "CreateEpicCtrl", controllerAs: "vm", bindToController: { - project: '=', onCreateEpic: '&' }, scope: {} } -CreateEpicDirective.$inject = [] - -module.directive("tgCreateEpic", CreateEpicDirective) +angular.module('taigaEpics').directive("tgCreateEpic", CreateEpicDirective) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index da51d342..504c7216 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -33,11 +33,11 @@ tg-lightbox-close ) fieldset.tags-block tg-tag-line-common( - project="vm.project" - tags="vm.newEpic.tags" - permissions="add_epic" - on-add-tag="vm.addTag(name, color)" - on-delete-tag="vm.deleteTag(tag)" + project="vm.project" + tags="vm.newEpic.tags" + permissions="add_epic" + on-add-tag="vm.addTag(name, color)" + on-delete-tag="vm.deleteTag(tag)" ) fieldset textarea.e2e-create-epic-description( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 33c30bf4..06d79ca4 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -17,19 +17,23 @@ # File: epics-table.controller.coffee ### -module = angular.module("taigaEpics") - class EpicRowController @.$inject = [ - "tgResources", - "$tgConfirm" + "$tgConfirm", + "tgProjectService", + "tgEpicsService" ] - constructor: (@rs, @confirm) -> + constructor: (@confirm, @projectService, @epicsService) -> @.displayUserStories = false @.displayAssignedTo = false + @.displayStatusList = 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: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" @@ -42,68 +46,32 @@ class EpicRowController else @.percentage = "#{@.closed * 100 / @.total}%" - updateEpicStatus: (status) -> - @.loadingStatus = true - @.displayStatusList = false - patch = { - 'status': status, - 'version': @.epic.get('version') - } + canEditEpics: () -> + return @projectService.hasPermission("modify_epic") - onSuccess = => - @.loadingStatus = false - @.onUpdateEpic() - - onError = (data) => - @confirm.notify('error') - - return @rs.epics.patch(@.epic.get('id'), patch).then(onSuccess, onError) - - requestUserStories: (epic) -> + toggleUserStoryList: () -> if !@.displayUserStories - - onSuccess = (data) => - @.epicStories = data - @.displayUserStories = true - - onError = (data) => - @confirm.notify('error') - - return @rs.userstories.listInEpic(@.epic.get('id')).then(onSuccess, onError) + @epicsService.listRelatedUserStories(@.epic) + .then (userStories) => + @.epicStories = userStories + @.displayUserStories = true + .catch => + @confirm.notify('error') else @.displayUserStories = false - onRemoveAssigned: () -> - id = @.epic.get('id') - version = @.epic.get('version') - patch = { - 'assigned_to': null, - 'version': version - } + updateStatus: (statusId) -> + @.displayStatusList = false + @.loadingStatus = true + return @epicsService.updateEpicStatus(@.epic, statusId) + .catch () => + @confirm.notify('error') + .finally () => + @.loadingStatus = false - onSuccess = => - @.onUpdateEpic() + updateAssignedTo: (member) -> + return @epicsService.updateEpicAssignedTo(@.epic, member?.id) + .catch () => + @confirm.notify('error') - onError = (data) => - @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) +angular.module("taigaEpics").controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index 70fbb8e3..b1a6c85d 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -17,28 +17,16 @@ # File: epics-table.directive.coffee ### -module = angular.module('taigaEpics') - EpicRowDirective = () -> - - link = (scope, el, attrs, ctrl) -> - ctrl._calculateProgressBar() - return { - link: link, templateUrl:"epics/dashboard/epic-row/epic-row.html", controller: "EpicRowCtrl", controllerAs: "vm", bindToController: true, scope: { - project: '=', epic: '=', column: '=', - permissions: '=', - onUpdateEpic: "&" } } -EpicRowDirective.$inject = [] - -module.directive("tgEpicRow", EpicRowDirective) +angular.module('taigaEpics').directive("tgEpicRow", EpicRowDirective) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 2dc410b5..829ecf7a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,10 +1,11 @@ .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-click="vm.requestUserStories(vm.epic)" + ng-click="vm.toggleUserStoryList()" ) tg-svg.icon-drag( svg-icon="icon-drag" ) + .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -28,23 +29,26 @@ ) .project(ng-if="vm.column.project") - .sprint( - ng-if="vm.column.sprint" - ) - .assigned.e2e-assigned-to + + .sprint(ng-if="vm.column.sprint") + + .assigned.e2e-assigned-tio(ng-if="vm.column.assigned") tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" - on-remove-assigned="vm.onRemoveAssigned()" - on-assign-to="vm.onAssignTo(member)" + on-remove-assigned="vm.updateAssignedTo(null)" + on-assign-to="vm.updateAssignedTo(member)" + tg-isolate-click ) + .status( - ng-if="vm.column.status && !vm.permissions.canEdit" + ng-if="vm.column.status && !vm.canEditEpics()" ) span {{vm.epic.getIn(['status_extra_info', 'name'])}} .status( - ng-if="vm.column.status && vm.permissions.canEdit" + ng-if="vm.column.status && vm.canEditEpics()" ng-mouseleave="vm.displayStatusList = false" + tg-isolate-click ) button( ng-click="vm.displayStatusList = true" @@ -59,20 +63,19 @@ ul.epic-statuses(ng-if="vm.displayStatusList") li.e2e-edit-epic-status( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-click="vm.updateEpicStatus(status.id)" + ng-click="vm.updateStatus(status.id)" ) {{status.name}} + .progress(ng-if="vm.column.progress") .progress-bar .progress-status( ng-if="::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')") tg-story-row.e2e-story( - epic="vm.epic" story="story" - project="vm.project" column="vm.column" ) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index d6f23146..199927a6 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -17,48 +17,50 @@ # File: epics.dashboard.controller.coffee ### -module = angular.module("taigaEpics") +taiga = @.taiga + class EpicsDashboardController @.$inject = [ - "$tgResources", - "tgResources", "$routeParams", "tgErrorHandlingService", "tgLightboxFactory", "lightboxService", - "$tgConfirm" + "$tgConfirm", + "tgProjectService", + "tgEpicsService" ] - constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> - @.sectionName = "Epics" - @.createEpic = false + constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService, + @confirm, @projectService, @epicsService) -> - loadProject: () -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - if not project.is_epics_activated - @errorHandlingService.permissionDenied() - @.project = project - @.loadEpics() + @.sectionName = "EPICS.SECTION_NAME" - loadEpics: () -> - projectId = @.project.id - return @resources.epics.list(projectId).then (epics) => - @.epics = epics + taiga.defineImmutableProperty @, 'project', () => return @projectService.project + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - _onCreateEpic: () -> - @lightboxService.closeAll() - @confirm.notify("success") - @.loadEpics() + @._loadInitialData() + + _loadInitialData: () -> + @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: () -> @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" - "project": "project" "on-create-epic": "onCreateEpic()" }, { - "project": @.project - "onCreateEpic": @._onCreateEpic.bind(this) + "onCreateEpic": () => + @lightboxService.closeAll() + @confirm.notify("success") }) -module.controller("EpicsDashboardCtrl", EpicsDashboardController) +angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 69ff744d..f9747455 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -4,23 +4,20 @@ header.header-with-actions h1( tg-main-title - project-name="vm.project.name" - i18n-section-name="{{ vm.sectionName }}" + project-name="vm.project.get('name')" + 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( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", ng-click="vm.onCreateEpic()" ) - + tg-epics-table( - ng-if="vm.project && vm.epics.size" - project="vm.project" - epics="vm.epics" - on-update-epic="vm.loadEpics()" + ng-if="vm.epics.size" ) - + section.empty-epics(ng-if="!vm.epics.size") img( src="/#{v}/images/epics-empty.png" @@ -30,11 +27,12 @@ p(translate="EPICS.EMPTY.EXPLANATION") a( 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" ng-title="EPICS.EMPTY.HELP | translate" ) button.create-epic.button-green( + ng-if="vm.canCreateEpics()" translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}" ng-click="vm.onCreateEpic()" diff --git a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee index d9063f7a..53063281 100644 --- a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee +++ b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee @@ -17,8 +17,10 @@ # File: epics-sortable.directive.coffee ### -EpicsSortableDirective = ($parse) -> +EpicsSortableDirective = ($parse, projectService) -> link = (scope, el, attrs) -> + return if not projectService.hasPermission("modify_epic") + callback = $parse(attrs.tgEpicsSortable) drake = dragula([el[0]], { @@ -55,7 +57,8 @@ EpicsSortableDirective = ($parse) -> } EpicsSortableDirective.$inject = [ - "$parse" + "$parse", + "tgProjectService" ] angular.module("taigaComponents").directive("tgEpicsSortable", EpicsSortableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 37eb275b..45e41d45 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -17,12 +17,16 @@ # File: epics-table.controller.coffee ### -module = angular.module("taigaEpics") +taiga = @.taiga + class EpicsTableController - @.$inject = [] + @.$inject = [ + "$tgConfirm", + "tgEpicsService" + ] - constructor: () -> + constructor: (@confirm, @epicsService) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -35,15 +39,14 @@ class EpicsTableController progress: true } - @.permissions = { - canEdit: _.includes(@.project.my_permissions, 'modify_epic') - } + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions reorderEpic: (epic, newIndex) -> - console.log epic, newIndex + @epicsService.reorderEpic(epic, newIndex) + .then null, () => # on error + @confirm.notify("error") - -module.controller("EpicsTableCtrl", EpicsTableController) +angular.module("taigaEpics").controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index ceb094a6..f072a3e4 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -17,21 +17,13 @@ # File: epics-table.directive.coffee ### -module = angular.module('taigaEpics') - EpicsTableDirective = () -> return { templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", - bindToController: { - epics: "=", - project: "=", - onUpdateEpic: "&" - } scope: {} } -EpicsTableDirective.$inject = [] -module.directive("tgEpicsTable", EpicsTableDirective) +angular.module('taigaEpics').directive("tgEpicsTable", EpicsTableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index baaad4e6..31d29bb0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -95,8 +95,5 @@ mixin epicSwitch(name, model) ) tg-epic-row.e2e-epic( epic="epic" - project="vm.project" column="vm.column" - on-update-epic="vm.onUpdateEpic()" - permissions="vm.permissions" ) diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee index e93eca79..db82f81b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -29,13 +29,8 @@ class StoryRowController if @.story.get('is_closed') == true @.percentage = "100%" else - tasks = @.story.get('tasks').toJS() totalTasks = @.story.get('tasks').size - areTasksCompleted = _.map(tasks, 'is_closed') - totalTasksCompleted = _.pull(areTasksCompleted, false).length + totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" - onSelectAssignedTo: () -> - console.log 'ng-click="vm.onSelectAssignedTo()"' - module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.directive.coffee b/app/modules/epics/dashboard/story-row/story-row.directive.coffee index 338f676f..13195c0a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.directive.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.directive.coffee @@ -20,20 +20,15 @@ module = angular.module('taigaEpics') StoryRowDirective = () -> - return { templateUrl:"epics/dashboard/story-row/story-row.html", controller: "StoryRowCtrl", controllerAs: "vm", bindToController: true, scope: { - epic: '=', story: '=', - project: '=', column: '=' } } -StoryRowDirective.$inject = [] - module.directive("tgStoryRow", StoryRowDirective) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index d421ca0c..ae5bee27 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -1,5 +1,5 @@ .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( ng-if="vm.column.votes" @@ -11,12 +11,12 @@ .name(ng-if="vm.column.name") - var hash = "#"; 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')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} tg-belong-to-epics( - format="pill" ng-if="vm.story.get('epics')" + format="pill" epics="vm.story.get('epics')" ) .project( diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee new file mode 100644 index 00000000..0f6da542 --- /dev/null +++ b/app/modules/epics/epics.service.coffee @@ -0,0 +1,99 @@ +### +# Copyright (C) 2014-2016 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 . +# +# 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) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 82d48c11..8b39793e 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -51,6 +51,13 @@ Resource = (urlsService, http) -> 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) -> url = urlsService.resolve("epic-related-userstories", epicId) diff --git a/app/modules/utils/isolate-click.directive.coffee b/app/modules/utils/isolate-click.directive.coffee new file mode 100644 index 00000000..df262794 --- /dev/null +++ b/app/modules/utils/isolate-click.directive.coffee @@ -0,0 +1,27 @@ +### +# Copyright (C) 2014-2016 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 . +# +# 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) diff --git a/app/modules/utils/utils.module.coffee b/app/modules/utils/utils.module.coffee new file mode 100644 index 00000000..d39c4e08 --- /dev/null +++ b/app/modules/utils/utils.module.coffee @@ -0,0 +1,20 @@ +### +# Copyright (C) 2014-2016 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 . +# +# File: utils.module.coffee +### + +module = angular.module("taigaUtils", [])