From d61e5d33320ddb5d77c8b5ee6b2bd42df1b97244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 24 Jun 2016 13:11:25 +0200 Subject: [PATCH 001/137] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f28be9d..b823c12a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 3.0.0 ???? (Unreleased) ### Features +- Add Epics. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Errors (not found, server error, permissions and blocked project) don't change the current url. From 872755130627296c2c604b1fd2beae0b15453ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 20 Jul 2016 09:48:56 +0200 Subject: [PATCH 002/137] Epics layout --- app/coffee/app.coffee | 12 +++++++- app/coffee/modules/base.coffee | 1 + app/coffee/modules/epics.coffee | 25 +++++++++++++++++ app/locales/taiga/locale-en.json | 3 ++ .../components/project-menu/project-menu.jade | 16 +++++++++-- .../epics-dashboard.controller.coffee | 28 +++++++++++++++++++ .../epics/dashboard/epics-dashboard.jade | 1 + app/svg/sprite.svg | 6 +++- 8 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 app/coffee/modules/epics.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.controller.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ccc7247c..97c3efbd 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -124,7 +124,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) - $routeProvider.when("/blocked-project/:pslug/", { templateUrl: "projects/project/blocked-project.html", @@ -153,6 +152,16 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + $routeProvider.when("/project/:pslug/epics", + { + section: "epics", + templateUrl: "epics/dashboard/epics-dashboard.html", + loader: true, + controller: "EpicsDashboardCtrl", + controllerAs: "vm" + } + ) + $routeProvider.when("/project/:pslug/backlog", { templateUrl: "backlog/backlog.html", @@ -792,6 +801,7 @@ modules = [ "taigaDiscover", "taigaHistory", "taigaWikiHistory", + 'taigaEpics', # template cache "templates", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index c4448294..70d836c2 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -73,6 +73,7 @@ urls = { "project-taskboard": "/project/:project/taskboard/:sprint" "project-kanban": "/project/:project/kanban" "project-issues": "/project/:project/issues" + "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" "project-userstories-detail": "/project/:project/us/:ref" diff --git a/app/coffee/modules/epics.coffee b/app/coffee/modules/epics.coffee new file mode 100644 index 00000000..15941253 --- /dev/null +++ b/app/coffee/modules/epics.coffee @@ -0,0 +1,25 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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: modules/projects.coffee +### + +module = angular.module("taigaEpics", []) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index f67ddd47..0cbd64e7 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -391,6 +391,9 @@ "WATCHING_SECTION": "Watching", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS" + }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", "PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.", diff --git a/app/modules/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index e1bdd2d9..829854d8 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -15,15 +15,25 @@ nav.menu( tg-svg(svg-icon="icon-search") span.helper(translate="PROJECT.SECTION.SEARCH") - li#nav-timeline - a( + li#nav-timeline + a( tg-nav="project:project=vm.project.get('slug')" ng-class="{active: vm.active == 'project-timeline'}" aria-label="{{'PROJECT.SECTION.TIMELINE' | translate}}" tabindex="2" - ) + ) tg-svg(svg-icon="icon-timeline") span.helper(translate="PROJECT.SECTION.TIMELINE") + + li#nav-epics + a( + tg-nav="project-epics:project=vm.project.get('slug')" + ng-class="{active: vm.active == 'epics'}" + aria-label="{{'EPICS.TITLE' | translate}}" + tabindex="2" + ) + tg-svg(svg-icon="icon-epics") + span.helper(translate="EPICS.TITLE") li#nav-backlog( ng-if="vm.menu.get('backlog')" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee new file mode 100644 index 00000000..529cbbc1 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -0,0 +1,28 @@ +### +# Copyright (C) 2014-2015 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: history.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicsDashboardController + @.$inject = [] + + constructor: () -> + console.log 'Hola' + +module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade new file mode 100644 index 00000000..d164ccf0 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -0,0 +1 @@ +p Epics diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index 8fa6cc2a..f6f2022f 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -429,7 +429,6 @@ fill="#fff" d="M511.998 107.939c-222.856 0-404.061 181.204-404.061 404.061s181.205 404.061 404.061 404.061c222.856 0 404.061-181.203 404.061-404.061s-181.205-404.061-404.061-404.061zM511.998 158.447c88.671 0 169.621 32.484 231.616 86.222l-498.947 498.948c-53.74-61.998-86.223-142.945-86.223-231.617 0-195.561 157.992-353.553 353.553-353.553zM779.328 280.383c53.74 61.998 86.223 142.945 86.223 231.617 0 195.561-157.992 353.553-353.553 353.553-88.671 0-169.617-32.484-231.616-86.222l498.947-498.948z"> -<<<<<<< b929b5ecdaefcb0f2430ea5c8e41ce8fdcdbe761 Add user View more + Merge @@ -446,5 +446,9 @@ Fill + + Epics + + From 5a3185e69978a9bcabc34d11de4ffa99b5ac2bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 20 Jul 2016 15:46:41 +0200 Subject: [PATCH 003/137] Epics table --- app/locales/taiga/locale-en.json | 15 ++- .../epics-dashboard.controller.coffee | 22 ++++- .../epics/dashboard/epics-dashboard.jade | 20 +++- .../epics-table/epics-table.controller.coffee | 44 +++++++++ .../epics-table/epics-table.directive.coffee | 34 +++++++ .../dashboard/epics-table/epics-table.jade | 99 +++++++++++++++++++ .../dashboard/epics-table/epics-table.scss | 74 ++++++++++++++ 7 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.controller.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.directive.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.jade create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 0cbd64e7..b18c8d15 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -392,7 +392,20 @@ "DASHBOARD": "Projects Dashboard" }, "EPICS": { - "TITLE": "EPICS" + "TITLE": "EPICS", + "DASHBOARD": { + "ADD": "+ ADD EPIC" + }, + "TABLE": { + "VOTES": "Votes", + "NAME": "Name", + "PROJECT": "Project", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned to", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + } }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 529cbbc1..267b7f38 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -14,15 +14,29 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: history.controller.coffee +# File: epics.dashboard.controller.coffee ### module = angular.module("taigaEpics") class EpicsDashboardController - @.$inject = [] + @.$inject = [ + "$tgResources", + "$routeParams", + "tgErrorHandlingService" + ] - constructor: () -> - console.log 'Hola' + constructor: (@rs, @params, @errorHandlingService) -> + @.sectionName = "Epics" + @._loadProject() + + _loadProject: () -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + if not project.is_epics_activated + @errorHandlingService.permissionDenied() + @project = project + + addNewEpic: () -> + console.log 'Add new Epic' module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index d164ccf0..0ac4a42c 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1 +1,19 @@ -p Epics +doctype html + +.wrapper() + tg-project-menu + section.main(role="main") + header.header-with-actions + h1( + tg-main-title + project-name="vm.project.name" + i18n-section-name="{{ vm.sectionName }}" + ) + .action-buttons + button.button-green( + translate="EPICS.DASHBOARD.ADD" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", + ng-click="vm.addNewEpic()" + ) + + tg-epics-table diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee new file mode 100644 index 00000000..9c145472 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -0,0 +1,44 @@ +### +# Copyright (C) 2014-2015 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-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicsTableController + @.$inject = [] + + constructor: () -> + @.displayOptions = false + @.displayVotes = true + @.column = { + votes: true, + name: true, + project: true, + sprint: true, + assigned: true, + status: true, + progress: true + } + + toggleEpicTableOptions: () -> + @.displayOptions = !@.displayOptions + + updateEpicTableColumns: () -> + console.log @.column + +module.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 new file mode 100644 index 00000000..0ceef836 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -0,0 +1,34 @@ +### +# 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-table.directive.coffee +### + +module = angular.module('taigaEpics') + +EpicsTableDirective = () -> + + return { + templateUrl:"epics/dashboard/epics-table/epics-table.html", + controller: "EpicsTableCtrl", + controllerAs: "vm", + bindToController: true, + scope: {} + } + +EpicsTableDirective.$inject = [] + +module.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 new file mode 100644 index 00000000..aa0ecbd4 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -0,0 +1,99 @@ +mixin epicSwitch(name, model) + div.check + input.activate-input( + id= name + name= name + type="checkbox" + ng-checked= model + ng-model= model + ng-change="vm.updateEpicTableColumns()" + ) + div + span.check-text.check-yes(translate="COMMON.YES") + span.check-text.check-no(translate="COMMON.NO") + +.epics-table + .epics-table-header + .vote( + translate="EPICS.TABLE.VOTES" + ng-if="vm.column.votes" + ) + .name( + translate="EPICS.TABLE.NAME" + ng-if="vm.column.name" + ) + .project( + translate="EPICS.TABLE.PROJECT" + ng-if="vm.column.project" + ) + .sprint( + translate="EPICS.TABLE.SPRINT" + ng-if="vm.column.sprint" + ) + .assigned( + translate="EPICS.TABLE.ASSIGNED_TO" + ng-if="vm.column.assigned" + ) + .status( + translate="EPICS.TABLE.STATUS" + ng-if="vm.column.status" + ) + .progress( + translate="EPICS.TABLE.PROGRESS" + ng-if="vm.column.progress" + ) + .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") + button.epics-table-option-button(ng-click="vm.displayOptions = true") + span(translate="EPICS.TABLE.VIEW_OPTIONS") + tg-svg(svg-icon="icon-arrow-down") + form.epics-table-dropdown(ng-show="vm.displayOptions") + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.VOTES" + for="epicSwitch-votes" + ) + +epicSwitch('switch-votes', 'vm.column.votes') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.NAME" + for="switch-name" + ) + +epicSwitch('switch-name', 'vm.column.name') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.PROJECT" + for="switch-project" + ) + +epicSwitch('switch-project', 'vm.column.project') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.SPRINT" + for="switch-sprint" + ) + +epicSwitch('switch-sprint', 'vm.column.sprint') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.ASSIGNED_TO" + for="switch-assigned" + ) + +epicSwitch('switch-assigned', 'vm.column.assigned') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.STATUS" + for="switch-status" + ) + +epicSwitch('switch-status', 'vm.column.status') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.PROGRESS" + for="switch-progress" + ) + +epicSwitch('switch-progress', 'vm.column.progress') + .epics-table-body + .vote + .name + .project + .sprint + .assigned + .status + .progress diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss new file mode 100644 index 00000000..13e17061 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -0,0 +1,74 @@ +.epics-table { + margin-top: 2rem; +} + +.epics-table-header, +.epics-table-body { + display: flex; + .assigned, + .vote { + flex-basis: 100px; + flex-grow: 0; + flex-shrink: 0; + } + .status, + .sprint { + flex-basis: 200px; + flex-grow: 0; + flex-shrink: 0; + } + .name, + .project, + .progress { + flex: 1; + } + .progress { + position: relative; + } +} + +.epics-table-header { + @include font-type(bold); + border-bottom: 1px solid $gray-light; + padding: .5rem; + position: relative; + .epics-table-options { + @include font-type(text); + @include font-size(small); + } +} + +.epics-table-options-wrapper { + position: absolute; + right: .5rem; + top: .5rem; +} + +.epics-table-option-button { + @include font-type(light); + background: none; + .icon { + @include svg-size(.7rem); + } +} + +.epics-table-dropdown { + background: $white; + box-shadow: 3px 3px 2px rgba($black, .1); + padding: .5rem; + position: absolute; + right: 0; + top: 1.25rem; + width: 250px; + .fieldset { + @include font-size(small); + border-bottom: 1px solid $whitish; + color: $gray-light; + display: flex; + justify-content: space-between; + padding: .5rem 0; + &:last-child { + border: 0; + } + } +} From cc02221a2de51a50e72f5f089a60efa21df38076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 21 Jul 2016 16:41:51 +0200 Subject: [PATCH 004/137] Epics dasboard --- app/coffee/modules/resources.coffee | 3 ++ app/locales/taiga/locale-en.json | 2 +- .../epic-row/epic-row.controller.coffee | 29 +++++++++++++++ .../epic-row/epic-row.directive.coffee | 36 ++++++++++++++++++ .../epics/dashboard/epic-row/epic-row.jade | 29 +++++++++++++++ .../epics/dashboard/epic-row/epic-row.scss | 31 ++++++++++++++++ .../epics-dashboard.controller.coffee | 2 +- .../epics/dashboard/epics-dashboard.jade | 5 ++- .../epics-table/epics-table.controller.coffee | 14 +++++-- .../epics-table/epics-table.directive.coffee | 4 +- .../dashboard/epics-table/epics-table.jade | 12 ++---- .../dashboard/epics-table/epics-table.scss | 36 +++++++++++++----- .../resources/epics-resource.service.coffee | 37 +++++++++++++++++++ app/modules/resources/resources.coffee | 3 +- 14 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.controller.coffee create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.directive.coffee create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.jade create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.scss create mode 100644 app/modules/resources/epics-resource.service.coffee diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 59e8743a..df127c5e 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -92,6 +92,9 @@ urls = { # Milestones/Sprints "milestones": "/milestones" + # Epics + "epics": "/epics" + # User stories "userstories": "/userstories" "bulk-create-us": "/userstories/bulk_create" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index b18c8d15..e8b17d53 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -401,7 +401,7 @@ "NAME": "Name", "PROJECT": "Project", "SPRINT": "Sprint", - "ASSIGNED_TO": "Assigned to", + "ASSIGNED_TO": "Assigned", "STATUS": "Status", "PROGRESS": "Progress", "VIEW_OPTIONS": "View options" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee new file mode 100644 index 00000000..09ebcfd0 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -0,0 +1,29 @@ +### +# Copyright (C) 2014-2015 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-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicRowController + @.$inject = [ + ] + + constructor: () -> + console.log @.epic.toJS() + +module.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 new file mode 100644 index 00000000..cb80d678 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -0,0 +1,36 @@ +### +# 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-table.directive.coffee +### + +module = angular.module('taigaEpics') + +EpicRowDirective = () -> + + return { + templateUrl:"epics/dashboard/epic-row/epic-row.html", + controller: "EpicRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + epic: '=' + } + } + +EpicRowDirective.$inject = [] + +module.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 new file mode 100644 index 00000000..f14f6592 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -0,0 +1,29 @@ +.epic-row + .vote(ng-class="{'is-voter': vm.epic.get('is_voter')}") + tg-svg(svg-icon='icon-upvote') + span {{::vm.epic.get('total_voters')}} + + .name() {{::vm.epic.get('subject')}} + + .project() {{::vm.epic.get('project')}} + .sprint( + translate="EPICS.TABLE.SPRINT" + ) + .assigned( + ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ) + img( + ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" + alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" + ) + .assigned( + ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ) Unassigned + .status( + ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" + ) + span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + tg-svg(svg-icon="icon-arrow-down") + .progress + .progress-bar + .progress-status diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss new file mode 100644 index 00000000..2d3f294b --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -0,0 +1,31 @@ +.epic-row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + .progress-bar, + .progress-status { + height: 1.5rem; + left: 0; + position: absolute; + top: .25rem; + } + .progress-bar { + background: $mass-white; + max-width: 40vw; + width: 100%; + } + .progress-status { + background: $primary-light; + width: 10vw; + } + .vote { + color: $gray; + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } +} diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 267b7f38..91fb2ecd 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -34,7 +34,7 @@ class EpicsDashboardController return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_epics_activated @errorHandlingService.permissionDenied() - @project = project + @.project = project addNewEpic: () -> console.log 'Add new Epic' diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 0ac4a42c..38b45593 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -16,4 +16,7 @@ doctype html ng-click="vm.addNewEpic()" ) - tg-epics-table + tg-epics-table( + ng-if="vm.project" + project="vm.project" + ) 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 9c145472..717916dc 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -20,9 +20,11 @@ module = angular.module("taigaEpics") class EpicsTableController - @.$inject = [] + @.$inject = [ + "tgResources" + ] - constructor: () -> + constructor: (@rs) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -34,11 +36,15 @@ class EpicsTableController status: true, progress: true } + @._loadEpics() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - updateEpicTableColumns: () -> - console.log @.column + _loadEpics: () -> + projectId = @.project.id + params = {} + promise = @rs.epics.listAll(projectId, params).then (epics) => + @.epics = epics module.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 0ceef836..39c75a8f 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -26,7 +26,9 @@ EpicsTableDirective = () -> controller: "EpicsTableCtrl", controllerAs: "vm", bindToController: true, - scope: {} + scope: { + project: "=" + } } EpicsTableDirective.$inject = [] diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index aa0ecbd4..6bfe6eba 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -6,7 +6,6 @@ mixin epicSwitch(name, model) type="checkbox" ng-checked= model ng-model= model - ng-change="vm.updateEpicTableColumns()" ) div span.check-text.check-yes(translate="COMMON.YES") @@ -90,10 +89,7 @@ mixin epicSwitch(name, model) ) +epicSwitch('switch-progress', 'vm.column.progress') .epics-table-body - .vote - .name - .project - .sprint - .assigned - .status - .progress + .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") + tg-epic-row( + epic="epic" + ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 13e17061..69b94f75 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -4,23 +4,40 @@ .epics-table-header, .epics-table-body { - display: flex; .assigned, + .project, + .vote, + .status, + .sprint, + .name, + .progress { + padding: 1rem .5rem; + } + + .assigned, + .project, .vote { - flex-basis: 100px; + flex-basis: 80px; flex-grow: 0; flex-shrink: 0; + flex-wrap: wrap; + text-align: center; } .status, .sprint { - flex-basis: 200px; + flex-basis: 150px; flex-grow: 0; flex-shrink: 0; + flex-wrap: wrap; } .name, - .project, .progress { flex: 1; + max-width: 40vw; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; } .progress { position: relative; @@ -30,12 +47,9 @@ .epics-table-header { @include font-type(bold); border-bottom: 1px solid $gray-light; + display: flex; padding: .5rem; position: relative; - .epics-table-options { - @include font-type(text); - @include font-size(small); - } } .epics-table-options-wrapper { @@ -46,6 +60,7 @@ .epics-table-option-button { @include font-type(light); + @include font-size(small); background: none; .icon { @include svg-size(.7rem); @@ -54,11 +69,14 @@ .epics-table-dropdown { background: $white; + border-bottom: 1px solid rgba($black, .1); + border-left: 1px solid rgba($black, .1); + border-right: 1px solid rgba($black, .1); box-shadow: 3px 3px 2px rgba($black, .1); padding: .5rem; position: absolute; right: 0; - top: 1.25rem; + top: 1.3rem; width: 250px; .fieldset { @include font-size(small); diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee new file mode 100644 index 00000000..d8c039c9 --- /dev/null +++ b/app/modules/resources/epics-resource.service.coffee @@ -0,0 +1,37 @@ +### +# 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-resource.service.coffee +### + +Resource = (urlsService, http) -> + service = {} + + service.listAll = (params) -> + url = urlsService.resolve("epics") + + httpOptions = {} + + return http.get(url, params, httpOptions).then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"epics": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgEpicsResource", Resource) diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index 5fafa0bf..f55dd7cc 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -27,7 +27,8 @@ services = [ "tgExternalAppsResource", "tgAttachmentsResource", "tgStatsResource", - "tgWikiHistory" + "tgWikiHistory", + "tgEpicsResource" ] Resources = ($injector) -> From 7b124d597b509cc6ee051b2cff8532d00ea469ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 08:37:37 +0200 Subject: [PATCH 005/137] Hide columns --- .../epic-row/epic-row.controller.coffee | 6 +++ .../epic-row/epic-row.directive.coffee | 4 +- .../epics/dashboard/epic-row/epic-row.jade | 40 ++++++++++++++----- .../epics/dashboard/epic-row/epic-row.scss | 20 ++++++++++ .../dashboard/epics-table/epics-table.jade | 2 + .../dashboard/epics-table/epics-table.scss | 7 +++- 6 files changed, 66 insertions(+), 13 deletions(-) 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 09ebcfd0..b943cc53 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -25,5 +25,11 @@ class EpicRowController constructor: () -> console.log @.epic.toJS() + @._calculateProgressBar() + + _calculateProgressBar: () -> + totalUs = @.epic.getIn(['user_stories_counts', 'closed']) + totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) + @.percentage = (totalUs * 100) / totalUsCompleted module.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 cb80d678..3332f917 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -27,7 +27,9 @@ EpicRowDirective = () -> controllerAs: "vm", bindToController: true, scope: { - epic: '=' + project: '=', + epic: '=', + column: '=' } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index f14f6592..e3bfffdd 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,29 +1,47 @@ -.epic-row - .vote(ng-class="{'is-voter': vm.epic.get('is_voter')}") +.epic-row( + ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" +) + .vote( + ng-if="vm.column.votes" + ng-class="{'is-voter': vm.epic.get('is_voter')}" + ) tg-svg(svg-icon='icon-upvote') span {{::vm.epic.get('total_voters')}} - .name() {{::vm.epic.get('subject')}} + .name(ng-if="vm.column.name") + a( + tg-nav="project-epic-detail:project=vm.project.get('slug')" + ng-attr-title="{{::vm.epic.get('subject')}}" + ) {{::vm.epic.get('subject')}} - .project() {{::vm.epic.get('project')}} + .project(ng-if="vm.column.project") {{::vm.epic.get('project')}} .sprint( + ng-if="vm.column.sprint" translate="EPICS.TABLE.SPRINT" ) .assigned( - ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) img( ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" ) .assigned( - ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) Unassigned .status( + ng-if="vm.column.status" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" - ) - span {{::vm.epic.getIn(['status_extra_info', 'name'])}} - tg-svg(svg-icon="icon-arrow-down") - .progress + ng-mouseleave="displayStatusList = false" + ) + button( + ng-click="displayStatusList = true" + ) + span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + tg-svg(svg-icon="icon-arrow-down") + .progress(ng-if="vm.column.progress") .progress-bar - .progress-status + .progress-status( + ng-if="::vm.percentage" + ng-attr-width="::vm.percentage" + ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 2d3f294b..1998624c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -3,6 +3,26 @@ align-items: center; border-bottom: 1px solid $whitish; display: flex; + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name { + color: $gray-light; + text-decoration: line-through; + } + } + .status { + cursor: pointer; + button { + background: none; + } + .icon { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .1rem; + } + } .progress-bar, .progress-status { height: 1.5rem; diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 6bfe6eba..2f64a270 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -92,4 +92,6 @@ mixin epicSwitch(name, model) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") tg-epic-row( epic="epic" + project="vm.project" + column="vm.column" ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 69b94f75..8aff604d 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -29,11 +29,15 @@ flex-grow: 0; flex-shrink: 0; flex-wrap: wrap; + max-width: 150px; } .name, .progress { flex: 1; max-width: 40vw; + } + .name, + .sprint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -53,9 +57,9 @@ } .epics-table-options-wrapper { + bottom: 1rem; position: absolute; right: .5rem; - top: .5rem; } .epics-table-option-button { @@ -78,6 +82,7 @@ right: 0; top: 1.3rem; width: 250px; + z-index: 99; .fieldset { @include font-size(small); border-bottom: 1px solid $whitish; From 9c1a7013b80e012c65d083df4b72bb1224f6ac5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 12:20:20 +0200 Subject: [PATCH 006/137] Epic statuses --- .../epic-row/epic-row.controller.coffee | 1 + .../epics/dashboard/epic-row/epic-row.jade | 13 +++++++-- .../epics/dashboard/epic-row/epic-row.scss | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) 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 b943cc53..640be052 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -25,6 +25,7 @@ class EpicRowController constructor: () -> console.log @.epic.toJS() + console.log @.project @._calculateProgressBar() _calculateProgressBar: () -> diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index e3bfffdd..20ccb2e1 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,6 +1,7 @@ .epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" ) + tg-svg(svg-icon="icon-drag") .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -31,14 +32,22 @@ ) Unassigned .status( ng-if="vm.column.status" - ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ng-mouseleave="displayStatusList = false" ) button( ng-click="displayStatusList = true" + ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ) span {{::vm.epic.getIn(['status_extra_info', 'name'])}} - tg-svg(svg-icon="icon-arrow-down") + tg-svg( + svg-icon="icon-arrow-down" + ) + + ul.epic-statuses(ng-show="displayStatusList") + li( + ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" + ng-click="vm.updateEpicStatus(status.name)" + ) {{status.name}} .progress(ng-if="vm.column.progress") .progress-bar .progress-status( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 1998624c..2e5076cd 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -12,8 +12,12 @@ text-decoration: line-through; } } + .icon-drag { + fill: $gray-light; + } .status { cursor: pointer; + position: relative; button { background: none; } @@ -48,4 +52,29 @@ margin-right: .25rem; vertical-align: middle; } + .epic-statuses { + @include font-type(light); + @include font-size(small); + background: rgba($blackish, .9); + border-bottom: 1px solid $grayer; + box-shadow: 3px 3px 2px rgba($black, .1); + color: $white; + left: 0; + list-style-type: none; + margin: 0; + position: absolute; + top: 2.5rem; + width: 200px; + z-index: 99; + &:last-child { + border: 0; + } + li { + padding: .5rem; + &:hover { + color: $primary-light; + transition: color .3s linear; + } + } + } } From 6e6257f03954aa9b0fadaf0f8937c3ef6f620f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 14:03:12 +0200 Subject: [PATCH 007/137] Update Epic Status --- .../epic-row/epic-row.controller.coffee | 25 ++++++++++++++++--- .../epic-row/epic-row.directive.coffee | 4 ++- .../epics/dashboard/epic-row/epic-row.jade | 14 ++++++++--- .../epics/dashboard/epic-row/epic-row.scss | 12 ++++++++- .../epics-table/epics-table.controller.coffee | 14 ++++++++--- .../dashboard/epics-table/epics-table.jade | 2 ++ .../resources/epics-resource.service.coffee | 13 ++++++++++ 7 files changed, 70 insertions(+), 14 deletions(-) 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 640be052..f1719faf 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -21,16 +21,33 @@ module = angular.module("taigaEpics") class EpicRowController @.$inject = [ + "tgResources", + "$tgConfirm" ] - constructor: () -> - console.log @.epic.toJS() - console.log @.project + constructor: (@rs, @confirm) -> @._calculateProgressBar() _calculateProgressBar: () -> totalUs = @.epic.getIn(['user_stories_counts', 'closed']) totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) - @.percentage = (totalUs * 100) / totalUsCompleted + @.percentage = totalUs * 100 / totalUsCompleted + + updateEpicStatus: (status) -> + id = @.epic.get('id') + version = @.epic.get('version') + patch = { + 'status': status, + 'version': version + } + + onSuccess = => + @.onUpdateEpicStatus() + + onError = (data) => + console.log data + @confirm.notify('error') + + return @rs.epics.patch(id, patch).then(onSuccess, onError) module.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 3332f917..14b224c8 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -29,7 +29,9 @@ EpicRowDirective = () -> scope: { project: '=', epic: '=', - column: '=' + column: '=', + permissions: '=', + onUpdateEpicStatus: "&" } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 20ccb2e1..8e7adee3 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,7 +1,9 @@ .epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" ) - tg-svg(svg-icon="icon-drag") + tg-svg( + svg-icon="icon-drag" + ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -31,14 +33,18 @@ ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) Unassigned .status( - ng-if="vm.column.status" + ng-if="vm.column.status && !vm.permissions.canEdit" + ) + span {{vm.epic.getIn(['status_extra_info', 'name'])}} + .status( + ng-if="vm.column.status && vm.permissions.canEdit" ng-mouseleave="displayStatusList = false" ) button( ng-click="displayStatusList = true" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ) - span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + span {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) @@ -46,7 +52,7 @@ ul.epic-statuses(ng-show="displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-click="vm.updateEpicStatus(status.name)" + ng-click="vm.updateEpicStatus(status.id)" ) {{status.name}} .progress(ng-if="vm.column.progress") .progress-bar diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 2e5076cd..540450aa 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -2,7 +2,14 @@ @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; + cursor: pointer; display: flex; + &:hover { + .icon-drag { + cursor: move; + opacity: 1; + } + } &.is-blocked { background: rgba($red-light, .5); } @@ -13,7 +20,10 @@ } } .icon-drag { - fill: $gray-light; + @include svg-size(.75rem); + fill: $whitish; + opacity: 0; + transition: opacity .1s; } .status { cursor: pointer; 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 717916dc..8d498366 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -36,15 +36,21 @@ class EpicsTableController status: true, progress: true } - @._loadEpics() + @.loadEpics() + @._checkPermissions() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - _loadEpics: () -> + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, 'modify_epic') + } + + loadEpics: () -> projectId = @.project.id - params = {} - promise = @rs.epics.listAll(projectId, params).then (epics) => + promise = @rs.epics.list(projectId).then (epics) => @.epics = epics + console.log @.epics module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 2f64a270..6761b966 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,4 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" + on-update-epic-status="vm.loadEpics()" + permissions="vm.permissions" ) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index d8c039c9..ddc4fe40 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -28,6 +28,19 @@ Resource = (urlsService, http) -> return http.get(url, params, httpOptions).then (result) -> return Immutable.fromJS(result.data) + service.list = (projectId) -> + url = urlsService.resolve("epics") + + params = {project: projectId} + + return http.get(url, params) + .then (result) -> Immutable.fromJS(result.data) + + service.patch = (id, patch) -> + url = urlsService.resolve("epics") + "/#{id}" + + return http.patch(url, patch) + return () -> return {"epics": service} From ad0b59f0a917b091ff5e912aa0cbfc66f242bf21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 26 Jul 2016 11:09:40 +0200 Subject: [PATCH 008/137] Epics table layout --- app/locales/taiga/locale-en.json | 3 ++- .../epics/dashboard/epic-row/epic-row.jade | 16 ++++++++++++---- .../epics/dashboard/epic-row/epic-row.scss | 8 ++++++++ .../epics/dashboard/epics-table/epics-table.scss | 13 ++++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index e8b17d53..cfd6397d 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -394,7 +394,8 @@ "EPICS": { "TITLE": "EPICS", "DASHBOARD": { - "ADD": "+ ADD EPIC" + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Unassigned" }, "TABLE": { "VOTES": "Votes", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 8e7adee3..d260d601 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -23,15 +23,23 @@ translate="EPICS.TABLE.SPRINT" ) .assigned( - ng-if="vm.column.assigned && vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && vm.epic.get('assigned_to')" ) img( + ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" + alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + img( + ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( - ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ) Unassigned + ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" + ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" + translate="EPICS.DASHBOARD.UNASSIGNED" + ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 540450aa..23b55494 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -56,12 +56,20 @@ .vote { color: $gray; } + .assigned { + img { + width: 40px; + } + } .icon-upvote { @include svg-size(.75rem); fill: $gray; margin-right: .25rem; vertical-align: middle; } + .is-unassigned { + color: $gray-light; + } .epic-statuses { @include font-type(light); @include font-size(small); diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 8aff604d..9342d0cd 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -4,7 +4,9 @@ .epics-table-header, .epics-table-body { - .assigned, + .assigned { + padding: .5rem; + } .project, .vote, .status, @@ -17,7 +19,7 @@ .assigned, .project, .vote { - flex-basis: 80px; + flex-basis: 100px; flex-grow: 0; flex-shrink: 0; flex-wrap: wrap; @@ -33,7 +35,9 @@ } .name, .progress { - flex: 1; + flex-basis: 20vw; + flex-grow: 1; + flex-shrink: 2; max-width: 40vw; } .name, @@ -54,6 +58,9 @@ display: flex; padding: .5rem; position: relative; + .assigned { + padding: 1rem .5rem; + } } .epics-table-options-wrapper { From c4d52616eefc651a085316bfb6c11aa4aaa01258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 26 Jul 2016 12:39:17 +0200 Subject: [PATCH 009/137] Sort Epics --- .../epics/dashboard/epic-row/epic-row.scss | 7 ++-- .../epic-sortable.directive.coffee | 35 +++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 4 ++- .../dashboard/epics-table/epics-table.jade | 2 +- .../dashboard/epics-table/epics-table.scss | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 23b55494..a3d952e0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -1,12 +1,14 @@ .epic-row { @include font-size(small); align-items: center; + background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; + cursor: move; display: flex; + transition: background .2s; &:hover { + background: rgba($primary-light, .05); .icon-drag { - cursor: move; opacity: 1; } } @@ -21,6 +23,7 @@ } .icon-drag { @include svg-size(.75rem); + cursor: move; fill: $whitish; opacity: 0; transition: opacity .1s; diff --git a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee new file mode 100644 index 00000000..b0d70468 --- /dev/null +++ b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee @@ -0,0 +1,35 @@ +### +# 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: epic-sortable.directive.coffee +### + +EpicSortableDirective = ($parse) -> + link = (scope, el, attrs) -> + + drake = dragula([el[0]]) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +EpicSortableDirective.$inject = [] + +angular.module("taigaComponents").directive("tgEpicSortable", EpicSortableDirective) 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 8d498366..fb22e4ed 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -51,6 +51,8 @@ class EpicsTableController projectId = @.project.id promise = @rs.epics.list(projectId).then (epics) => @.epics = epics - console.log @.epics + + reorderEpics: (epic, index) -> + console.log epic, index module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 6761b966..ef17423b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -88,7 +88,7 @@ mixin epicSwitch(name, model) for="switch-progress" ) +epicSwitch('switch-progress', 'vm.column.progress') - .epics-table-body + .epics-table-body(tg-epic-sortable) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") tg-epic-row( epic="epic" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 9342d0cd..5e43af45 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -3,7 +3,7 @@ } .epics-table-header, -.epics-table-body { +.epic-row { .assigned { padding: .5rem; } From 4344b72f7a8f85e6eddafcc66e39a5986b93e4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 11:02:04 +0200 Subject: [PATCH 010/137] User Story row --- app/locales/taiga/locale-en.json | 1 + .../epic-row/epic-row.controller.coffee | 18 +++++ .../epics/dashboard/epic-row/epic-row.jade | 44 +++++++--- .../epics/dashboard/epic-row/epic-row.scss | 37 +++++++-- .../dashboard/epics-table/epics-table.scss | 53 +----------- .../story-row/story-row.controller.coffee | 35 ++++++++ .../story-row/story-row.directive.coffee | 39 +++++++++ .../epics/dashboard/story-row/story-row.jade | 54 +++++++++++++ .../epics/dashboard/story-row/story-row.scss | 80 +++++++++++++++++++ .../userstories-resource.service.coffee | 12 +++ .../dependencies/mixins/epics-dashboard.scss | 57 +++++++++++++ 11 files changed, 361 insertions(+), 69 deletions(-) create mode 100644 app/modules/epics/dashboard/story-row/story-row.controller.coffee create mode 100644 app/modules/epics/dashboard/story-row/story-row.directive.coffee create mode 100644 app/modules/epics/dashboard/story-row/story-row.jade create mode 100644 app/modules/epics/dashboard/story-row/story-row.scss create mode 100644 app/styles/dependencies/mixins/epics-dashboard.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index cfd6397d..012bb8a5 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -393,6 +393,7 @@ }, "EPICS": { "TITLE": "EPICS", + "EPIC": "EPIC", "DASHBOARD": { "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" 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 f1719faf..cb1e5989 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -26,6 +26,7 @@ class EpicRowController ] constructor: (@rs, @confirm) -> + @.displayUserStories = false @._calculateProgressBar() _calculateProgressBar: () -> @@ -50,4 +51,21 @@ class EpicRowController return @rs.epics.patch(id, patch).then(onSuccess, onError) + requestUserStories: (epic) -> + if @.displayUserStories == false + id = @.epic.get('id') + + onSuccess = (data) => + @.epicStories = data + console.log @.epicStories.toJS() + @.displayUserStories = true + @confirm.notify('success') + + onError = (data) => + @confirm.notify('error') + + return @rs.userstories.listInEpics(id).then(onSuccess, onError) + else + @.displayUserStories = false + module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index d260d601..1eadbc19 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,40 +1,49 @@ .epic-row( - ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" + 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)" ) - tg-svg( + tg-svg.icon-drag( svg-icon="icon-drag" ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" - ) + ) tg-svg(svg-icon='icon-upvote') span {{::vm.epic.get('total_voters')}} - - .name(ng-if="vm.column.name") + + .name(ng-if="vm.column.name") + - var hash = "#"; a( tg-nav="project-epic-detail:project=vm.project.get('slug')" ng-attr-title="{{::vm.epic.get('subject')}}" - ) {{::vm.epic.get('subject')}} - - .project(ng-if="vm.column.project") {{::vm.epic.get('project')}} + ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} + span.epic-pill( + ng-style="::{'background-color': vm.epic.get('color')}" + translate="EPICS.EPIC" + ) + tg-svg( + svg-icon="icon-arrow-down" + ng-if="vm.epic.getIn(['user_stories_counts', 'opened']) || vm.epic.getIn(['user_stories_counts', 'closed'])" + ) + + .project(ng-if="vm.column.project") .sprint( ng-if="vm.column.sprint" - translate="EPICS.TABLE.SPRINT" ) .assigned( ng-if="vm.column.assigned && vm.epic.get('assigned_to')" - ) + ) img( ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) + ) img( ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) + ) .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" @@ -56,7 +65,7 @@ tg-svg( svg-icon="icon-arrow-down" ) - + ul.epic-statuses(ng-show="displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" @@ -68,3 +77,12 @@ ng-if="::vm.percentage" ng-attr-width="::vm.percentage" ) +.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( + epic="vm.epic" + story="story" + project="vm.project" + column="vm.column" + ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index a3d952e0..7760f8e0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -1,9 +1,12 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + .epic-row { + @include epics-table; @include font-size(small); align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: move; + cursor: pointer; display: flex; transition: background .2s; &:hover { @@ -21,6 +24,19 @@ text-decoration: line-through; } } + &.unfold { + .name { + .icon { + transform: rotate(0deg); + } + } + } + .name { + .icon { + transform: rotate(180deg); + transition: all .2s; + } + } .icon-drag { @include svg-size(.75rem); cursor: move; @@ -28,17 +44,26 @@ opacity: 0; transition: opacity .1s; } + .epic-pill { + @include font-type(light); + @include font-size(xsmall); + background: $grayer; + border-radius: .25rem; + color: $white; + margin: 0 .5rem; + padding: .1rem .25rem; + } .status { cursor: pointer; position: relative; button { background: none; } - .icon { - @include svg-size(.7rem); - fill: $gray-light; - margin-left: .1rem; - } + } + .icon-arrow-down { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .1rem; } .progress-bar, .progress-status { diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 5e43af45..74e50c1a 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -1,58 +1,11 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + .epics-table { margin-top: 2rem; } -.epics-table-header, -.epic-row { - .assigned { - padding: .5rem; - } - .project, - .vote, - .status, - .sprint, - .name, - .progress { - padding: 1rem .5rem; - } - - .assigned, - .project, - .vote { - flex-basis: 100px; - flex-grow: 0; - flex-shrink: 0; - flex-wrap: wrap; - text-align: center; - } - .status, - .sprint { - flex-basis: 150px; - flex-grow: 0; - flex-shrink: 0; - flex-wrap: wrap; - max-width: 150px; - } - .name, - .progress { - flex-basis: 20vw; - flex-grow: 1; - flex-shrink: 2; - max-width: 40vw; - } - .name, - .sprint { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 90%; - } - .progress { - position: relative; - } -} - .epics-table-header { + @include epics-table; @include font-type(bold); border-bottom: 1px solid $gray-light; display: flex; diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee new file mode 100644 index 00000000..42990d67 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2015 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-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class StoryRowController + @.$inject = [] + + constructor: () -> + @._calculateProgressBar() + + _calculateProgressBar: () -> + tasks = @.story.get('tasks').toJS() + totalTasks = @.story.get('tasks').size + areTasksCompleted = _.map(tasks, 'is_closed') + totalTasksCompleted = _.pull(areTasksCompleted, false).length + @.percentage = totalTasksCompleted * 100 / totalTasks + +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 new file mode 100644 index 00000000..338f676f --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.directive.coffee @@ -0,0 +1,39 @@ +### +# 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-table.directive.coffee +### + +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 new file mode 100644 index 00000000..39a8dda4 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -0,0 +1,54 @@ +.story-row( + ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}" +) + tg-svg.icon-drag( + svg-icon="icon-drag" + ) + .vote( + ng-if="vm.column.votes" + ng-class="{'is-voter': vm.story.get('is_voter')}" + ) + tg-svg(svg-icon='icon-upvote') + span {{::vm.story.get('total_voters')}} + + .name(ng-if="vm.column.name") + - var hash = "#"; + a( + tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" + ng-attr-title="{{::vm.story.get('subject')}}" + ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} + .story-pill(ng-style="::{'background-color': vm.epic.get('color')}") + .project( + ng-if="vm.column.project" + tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" + ) + img( + tg-project-logo-small-src="::vm.story.get('project_extra_info')" + alt="{{::vm.story.getIn(['project_extra_info', 'name'])}}" + ) + .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} + .assigned( + ng-if="vm.column.assigned && vm.story.get('assigned_to')" + ) + img( + ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="{{vm.story.getIn(['assigned_to_extra_info', 'photo'])}}" + alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + img( + ng-if="!vm.story.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="https://www.gravatar.com/avatar/{{vm.story.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + .assigned( + ng-if="vm.column.assigned && !vm.story.get('assigned_to')" + ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" + translate="EPICS.DASHBOARD.UNASSIGNED" + ) + .status(ng-if="vm.column.status") {{vm.story.getIn(['status_extra_info', 'name'])}} + .progress(ng-if="vm.column.progress") + .progress-bar + .progress-status( + ng-if="::vm.percentage" + ng-attr-width="::vm.percentage" + ) diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss new file mode 100644 index 00000000..0098b6fd --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -0,0 +1,80 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + +.story-row { + @include font-size(small); + @include epics-table; + align-items: center; + background: $white; + border-bottom: 1px solid $whitish; + cursor: pointer; + display: flex; + margin-left: 2rem; + transition: background .2s; + &:hover { + background: rgba($primary-light, .05); + .icon-drag { + opacity: 1; + } + } + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name { + color: $gray-light; + text-decoration: line-through; + } + } + .icon-drag { + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; + } + .name { + flex-basis: 18vw; + } + .story-pill { + background: $grayer; + border-radius: 50%; + display: inline-block; + height: .5rem; + margin-left: .25rem; + width: .5rem; + } + .progress-bar, + .progress-status { + height: 1.5rem; + left: 0; + position: absolute; + top: .25rem; + } + .progress-bar { + background: $mass-white; + max-width: 40vw; + width: 100%; + } + .progress-status { + background: $primary-light; + width: 10vw; + } + .vote { + color: $gray; + } + .project, + .assigned { + img { + width: 40px; + } + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } + .is-unassigned { + color: $gray-light; + } +} diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index ce2b7cd4..f5b13f79 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,6 +33,18 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.listInEpics = (ids) -> + url = urlsService.resolve("userstories") + + params = { + 'epics': ids, + 'include_tasks': true + } + + return http.get(url, params) + .then (result) -> + return Immutable.fromJS(result.data) + return () -> return {"userstories": service} diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss new file mode 100644 index 00000000..b472653d --- /dev/null +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -0,0 +1,57 @@ +@mixin epics-table { + .assigned { + padding: .5rem; + } + .project, + .vote, + .status, + .sprint, + .name, + .progress { + padding: 1rem .5rem; + } + .vote { + flex-basis: 60px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + text-align: center; + } + .assigned, + .project { + flex-basis: 100px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + text-align: center; + } + .status, + .sprint { + flex-basis: 150px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + max-width: 150px; + text-align: center; + } + .name, + .progress { + flex-basis: 20vw; + flex-grow: 1; + flex-shrink: 1; + max-width: 40vw; + } + .progress { + flex-shrink: 3; + } + .name, + .sprint { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; + } + .progress { + position: relative; + } +} From 99683684d68b9d8dac5673274416c780c16fbde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 16:55:42 +0200 Subject: [PATCH 011/137] Empty epics --- app/images/epics-empty.png | Bin 0 -> 3573 bytes app/locales/taiga/locale-en.json | 5 ++++ .../epic-row/epic-row.controller.coffee | 1 - .../epics-dashboard.controller.coffee | 9 +++++- .../epics/dashboard/epics-dashboard.jade | 24 ++++++++++++++-- .../epics/dashboard/epics-dashboard.scss | 26 ++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 12 ++------ .../epics-table/epics-table.directive.coffee | 1 + 8 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 app/images/epics-empty.png create mode 100644 app/modules/epics/dashboard/epics-dashboard.scss diff --git a/app/images/epics-empty.png b/app/images/epics-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..28363733acda0918a81fc6e69e7cc7ca99045647 GIT binary patch literal 3573 zcmV;Za0udnNWjr?byi0mr*>=ipWoW5 z8QmH!%VX9H1Q9{G1VKPZ-XAP@H|d}pA&0}~w=^N?=F8L9)BQBXaU6#cnuAt410(w5 zO$*0y92$*=(%BdadZjZkEQ-YJ3!;Pd%# zeSM8utp)(NzP^Ur?Z))FFtAu^0d#lgVIgY>fB$#l;0Y9uFvrQX+ej z^CsH3N~MB$Jl?+MI1VnCi+4|{R6;tPmOE=h<4p*6adCnFj%tiXBf{Y@A)fF)Eu(qi z9v&WWb92K>V;BZ{y&m)P^TfTWR7%e54Gql;cXoEhvm%qpgxT3y0D#?Y$JEpm@BZ`i z^EY92nilT<{vNltx4g9Z`FZGcy7u+^_wRY1S1J|4LbZsdg*!bxnFRj)4)H0eCE|<$8m&@_e=I7@{yZgenu3D`QHE(jDS>f{eJnwpZd>me{SM=VA zi3tP(f%Y}SFhk9o98g;I4ipLn?Ck7-<2XT0zb5{bk>&y*9@3&(LBB9RC$ zZE9+2;BlMPYQ?*E?|5mENJKMMr+VSUtSuA@N%HJ)ILzyi)a!MgCtb~`S~#(`O$uvk zVX;^UtGm3s)P&WkTDY5=8 zh{3nXWRmn9!$8#oaAB1d4u^;Gp2fvQP!xrShX?q4K8%l#59K{lqUuTJY&MJi{e3hV z4TM4=$*V~%qiW%R=MDR@pA)cO|6w6B|RAq6=WD-Y5M*yB@ObiACv9wNZ8jS`n zFE5GpnM$J?i{m&B=jZ3VG~wV|E`;N{SS*Hmy{<@JI-wfjuCK50`1r_66K>j&i(oLw z>%}&k&4K)X!$1|niFp8~r>7Mg2pf$?!fh0f$5mx@st_)bND$UGtXYz7Y3=xF(QGym zjYbvB5g)~c<2a7^LA+kC;?qtBg8_4MbG)>8Jg%DM>xv5}v^I*O6t}h(b8~Y%t7BPK zHEUB86;90Brl+Tg{o6`mFc=UF26<^p^>l?ODqOi-R;{(Qn46nxFHE8+N;T^*6&-+^ z&1M)3hW5;a*Xvb{wYAXe_4xhw-;vAZV6)iSh)yXED*p3^!-7eSbC55JG!s^8Sm_yBj94|?y z(?p@vHYeFDe!rivI^myln8+fWus|-COS9J2f~IMrs!ylWvhDO4x^TH%4#i@Tmp15b zQnldo`FMkGolYlP=-{CXCpN}8K0a=5#Md%3O=E3s4R*U76B83yU0szea>LLknd|jB zzJC2G>hcc+0$5mBkbNvyEEW-u$5E+N5C{Z125(a{vUmrY%_eqsc8Dqy0BklJe*N`V zVn$*ZWHK2FH_Jguj>F zA9r_m*PgEw;&3?d>C-0|42FT<(`+`8NF)%8#a?v3*zI;~ZEdv$$>nnR=bwK>?=_pv z@caFkot=fzXcWEY>go!ySPT}61!fEEsi zCE38Zy}gY>q0sU3v9U2Q41<}O8J-ofEQ{^!ZP9AI1x?fNdc6pRLa^Cv?eucFjKBZ> zyL~-1HHD3hjR8v{&QRPsdwYBBUgO=}9adLYd7m4NMtu7835i4k|Ni?gZ|oz*Ax4!- zrK4z0rxU?ou;XSDnx^sP%NHb*NyOuE(dw0DStOH5+}+*b&p-cc*AT)nTqcu2p->pg zm{oG&a=9F@tN&71LBHP*r_+hU!$airdDv_=Vi9UTm`o-ZjmCC$Oi>g(9uF9X>1y*o zP1ErE{qXz!$mjEj$Kyz+(?r<&r>7^BN~L!DI6gj(v9U2;T$c0mb8K#I4qPT7k_#uC zY<=yAvRbX!+S zt)y*Sp-{l~_BJnVeSN*}J#Lvy277yZU|AMpV`ErZSwW>z5$z#<{P>XwHZu%Zmc{Mu zEvnTjyk0M_^TDz#zJC2mL_%^po%sCuv!wZu)Z)(0&Un{iG5>oZ9*=`%Spd(+t9<+R zt>e9|`-V-cVpU46+t$|BB#TyXb#;Z4lM^(X&7OORN>Qm)u)DjIH;eCbx$xU>zagK`V`^$j(m$LeRpryu6ON9KP%IWPGc%*Q&=PNv(WVcLGTQVZ zGxzuR$Ye4DpBkyE$U<+ONF>r8&$(Q#f#xv#wz!*{8+`lr4g351s*3-iX8s%THk-{3 zbi8$6!j(!T92^|r{{9}RR0=02Cw*t&XF}{y`F^X+zJwFLaAa}aAzb}jDwX;^O{-_& z9v&WumuYQ$r4$Z_gNRSo@A1|>3n#QTv2$uhbVlq@`F`5Eo`frx%fw5Bar;NSjNNXR z#Oiwb#f8>pHk)N}UcNOJ78ZEd<#M^bxT;&a7EbKpBWvpqF$Z(6Cuw#4t6yGT64s`c z@JL!@P$zTO!U@ltVHjp)Z38elIf;dZ1?cs9*lf0*qfc}_<>hob+fG5N)e67g-&;;c z5HJiwjP1ImYwtj75oo<$htKEJ#KA#tnUPyZql~-*DxtN9Je$qJ<#N5cir_`zSe8Y( zTz+xc*$8R4xw&btl*{MyFdB`Rot^D?e@EeJwHkJIc2KEQU^bhvwY8;4u(6+oTCK*r zjzl8x`FtIF?j8H{kw~Pyf4f?(;`;hp!fcKZ;_-NhL*-u@l#2={_9d-_Zqgbyn~m_Y zymY*^sBqC}l(!b&U@(kaH!?6kKi~DC@`P};T8*d{gTWv#{{D#JV6|Ea7g;nK6}37- zIN^RnXu^s1i}eVo!9I2l5J8L(LF+| ztJmv9Jj}Lm9LEvXCOlhaggjU*7GCpYHk*lk2W?fF&F0A3RK()qBF4tXP^;D8^ZAIG zE8ZmY+1VK)kqBs-#xKA8GIErv7{WVHuh&6QlqAu8Mhp*%d*;=^Fgl&?MUQu*(RgvC z5=BujwwKLj^VJRbV&ODe{0wENR;w@=3^1F`(5F%<9336KT4;)*5DW&16-tkfkNE!m zdsk7w?RGoX*VlR0+&YV(n{&U7Mk7{NSBdBa$z&48$H%WmXKlqH5$@j;+xGiX88$XH zcmi@9hl7IyVrOorGOVqw5&Oiiudngr$B(Ycu(Y%U`p1tSU4>z49nvQ(+~wtE*P+`B zg#s~{%;)o6AJ$&4*G2a!2*XNsqUG}v24xc~S0@UE0@3@w+KQ{k?7(miqg>K7*nt;{INL1L61OE(1-|pt}vtr`PMTu&_Y*78gUjVYAQ0&4 v4`8#|u(-HLJa>6{xvO literal 0 HcmV?d00001 diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 012bb8a5..15c3bf2e 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -398,6 +398,11 @@ "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, "TABLE": { "VOTES": "Votes", "NAME": "Name", 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 cb1e5989..c1f034b2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -59,7 +59,6 @@ class EpicRowController @.epicStories = data console.log @.epicStories.toJS() @.displayUserStories = true - @confirm.notify('success') onError = (data) => @confirm.notify('error') diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 91fb2ecd..477c288e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -22,11 +22,12 @@ module = angular.module("taigaEpics") class EpicsDashboardController @.$inject = [ "$tgResources", + "tgResources", "$routeParams", "tgErrorHandlingService" ] - constructor: (@rs, @params, @errorHandlingService) -> + constructor: (@rs, @resources, @params, @errorHandlingService) -> @.sectionName = "Epics" @._loadProject() @@ -35,6 +36,12 @@ class EpicsDashboardController if not project.is_epics_activated @errorHandlingService.permissionDenied() @.project = project + @._loadEpics() + + _loadEpics: () -> + projectId = @.project.id + return @resources.epics.list(projectId).then (epics) => + @.epics = epics addNewEpic: () -> console.log 'Add new Epic' diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 38b45593..788cbdce 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -9,7 +9,7 @@ doctype html project-name="vm.project.name" i18n-section-name="{{ vm.sectionName }}" ) - .action-buttons + .action-buttons(ng-if="vm.epics.size") button.button-green( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", @@ -17,6 +17,26 @@ doctype html ) tg-epics-table( - ng-if="vm.project" + ng-if="vm.project && vm.epics.size" project="vm.project" + epics="vm.epics" ) + + section.empty-epics(ng-if="!vm.epics.size") + img( + src="/#{v}/images/epics-empty.png" + ng-title="EPICS.EMPTY.HELP | translate" + ) + h1.title(translate="EPICS.EMPTY.TITLE") + p(translate="EPICS.EMPTY.EXPLANATION") + a( + translate="EPICS.EMPTY.HELP" + href="https://tree.taiga.io/support/frequently-asked-questions/who-is-taiga-for/" + target="_blank" + ng-title="EPICS.EMPTY.HELP | translate" + ) + button.create-epic.button-green( + translate="EPICS.DASHBOARD.ADD" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", + ng-click="vm.addNewEpic()" + ) diff --git a/app/modules/epics/dashboard/epics-dashboard.scss b/app/modules/epics/dashboard/epics-dashboard.scss new file mode 100644 index 00000000..b52027dc --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.scss @@ -0,0 +1,26 @@ +.empty-epics { + margin: 0 auto; + padding: 5vh; + text-align: center; + width: 650px; + .title { + @include font-type(normal); + @include font-size(larger); + color: $gray-light; + margin-bottom: .5rem; + text-transform: none; + } + img { + margin: 2rem auto; + text-align: center; + width: 6rem; + } + p { + color: $gray-light; + } + a { + color: $primary; + display: block; + margin-bottom: 2rem; + } +} 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 fb22e4ed..6f02f06f 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -20,11 +20,9 @@ module = angular.module("taigaEpics") class EpicsTableController - @.$inject = [ - "tgResources" - ] + @.$inject = [] - constructor: (@rs) -> + constructor: () -> @.displayOptions = false @.displayVotes = true @.column = { @@ -36,7 +34,6 @@ class EpicsTableController status: true, progress: true } - @.loadEpics() @._checkPermissions() toggleEpicTableOptions: () -> @@ -47,11 +44,6 @@ class EpicsTableController canEdit: _.includes(@.project.my_permissions, 'modify_epic') } - loadEpics: () -> - projectId = @.project.id - promise = @rs.epics.list(projectId).then (epics) => - @.epics = epics - reorderEpics: (epic, index) -> console.log epic, index 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 39c75a8f..bc4793cb 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -27,6 +27,7 @@ EpicsTableDirective = () -> controllerAs: "vm", bindToController: true, scope: { + epics: "=" project: "=" } } From f973b752a435848a7056cebcd1aab6d630b3fad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 17:41:16 +0200 Subject: [PATCH 012/137] Epics unassigned --- app/modules/epics/dashboard/epic-row/epic-row.jade | 5 ++++- app/modules/epics/dashboard/epics-table/epics-table.scss | 1 + app/modules/epics/dashboard/story-row/story-row.jade | 5 ++++- app/styles/dependencies/mixins/epics-dashboard.scss | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1eadbc19..c5eaab92 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -47,8 +47,11 @@ .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" - translate="EPICS.DASHBOARD.UNASSIGNED" ) + img( + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 74e50c1a..2651dde4 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -11,6 +11,7 @@ display: flex; padding: .5rem; position: relative; + .project, .assigned { padding: 1rem .5rem; } diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 39a8dda4..05e93e09 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -43,8 +43,11 @@ .assigned( ng-if="vm.column.assigned && !vm.story.get('assigned_to')" ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" - translate="EPICS.DASHBOARD.UNASSIGNED" ) + img( + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ) .status(ng-if="vm.column.status") {{vm.story.getIn(['status_extra_info', 'name'])}} .progress(ng-if="vm.column.progress") .progress-bar diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index b472653d..6465fda3 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -1,8 +1,8 @@ @mixin epics-table { + .project, .assigned { padding: .5rem; } - .project, .vote, .status, .sprint, From 04103e6120f972b9c208622bd9799ec65f5262aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 27 Jul 2016 19:25:52 +0200 Subject: [PATCH 013/137] Now user stories inside epics are sorted --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 3 +-- app/modules/resources/userstories-resource.service.coffee | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 c1f034b2..4d6eb592 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -57,13 +57,12 @@ class EpicRowController onSuccess = (data) => @.epicStories = data - console.log @.epicStories.toJS() @.displayUserStories = true onError = (data) => @confirm.notify('error') - return @rs.userstories.listInEpics(id).then(onSuccess, onError) + return @rs.userstories.listInEpic(id).then(onSuccess, onError) else @.displayUserStories = false diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index f5b13f79..7b7f9b90 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,11 +33,12 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) - service.listInEpics = (ids) -> + service.listInEpic = (epicIid) -> url = urlsService.resolve("userstories") params = { - 'epics': ids, + 'epic': epicIid, + 'order_by': 'epic_order', 'include_tasks': true } From f60b4119346fbe3c18c2f074171b2fbe37e004ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 11:48:42 +0200 Subject: [PATCH 014/137] Display Stories style fixes --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 3 +++ app/modules/epics/dashboard/epic-row/epic-row.jade | 2 ++ .../epics/dashboard/story-row/story-row.controller.coffee | 3 +++ app/modules/epics/dashboard/story-row/story-row.jade | 2 ++ app/modules/epics/dashboard/story-row/story-row.scss | 4 ++-- 5 files changed, 12 insertions(+), 2 deletions(-) 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 4d6eb592..221d6088 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -66,4 +66,7 @@ class EpicRowController else @.displayUserStories = false + onSelectAssignedTo: () -> + console.log 'onSelectAssignedTo' + module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c5eaab92..d0eab6a2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -33,6 +33,7 @@ ) .assigned( ng-if="vm.column.assigned && vm.epic.get('assigned_to')" + ng-click="vm.onSelectAssignedTo()" ) img( ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" @@ -47,6 +48,7 @@ .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" + ng-click="vm.onSelectAssignedTo()" ) img( src="/#{v}/images/unnamed.png" 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 42990d67..d2b2f68b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -32,4 +32,7 @@ class StoryRowController totalTasksCompleted = _.pull(areTasksCompleted, false).length @.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.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 05e93e09..379e917b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -29,6 +29,7 @@ .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} .assigned( ng-if="vm.column.assigned && vm.story.get('assigned_to')" + ng-click="vm.onSelectAssignedTo()" ) img( ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" @@ -43,6 +44,7 @@ .assigned( ng-if="vm.column.assigned && !vm.story.get('assigned_to')" ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" + ng-click="vm.onSelectAssignedTo()" ) img( src="/#{v}/images/unnamed.png" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 0098b6fd..ec31c14d 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -8,7 +8,7 @@ border-bottom: 1px solid $whitish; cursor: pointer; display: flex; - margin-left: 2rem; + margin-left: 5rem; transition: background .2s; &:hover { background: rgba($primary-light, .05); @@ -33,7 +33,7 @@ transition: opacity .1s; } .name { - flex-basis: 18vw; + flex-basis: 16vw; } .story-pill { background: $grayer; From f5db7a7a4055359a3c16fe94aa75082de6666f13 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 28 Jul 2016 12:01:30 +0200 Subject: [PATCH 015/137] search epics --- app/coffee/modules/base.coffee | 1 + app/coffee/modules/common/components.coffee | 10 ++++++++++ app/coffee/modules/search.coffee | 5 ++++- app/locales/taiga/locale-en.json | 1 + .../includes/modules/search-filter.jade | 9 +++++++++ .../includes/modules/search-result-table.jade | 19 +++++++++++++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 70d836c2..708dd70e 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -76,6 +76,7 @@ urls = { "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" + "project-epic-detail": "/project/:project/epic/:ref" "project-userstories-detail": "/project/:project/us/:ref" "project-tasks-detail": "/project/:project/task/:ref" "project-issues-detail": "/project/:project/issue/:ref" diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a358dde4..a6f4fffb 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -765,6 +765,16 @@ module.directive("tgEditableWysiwyg", ["tgAttachmentsService", "tgAttachmentsFul ## completely bindonce, they only serves for visualization of data. ############################################################################# +ListItemEpicStatusDirective = -> + link = ($scope, $el, $attrs) -> + epic = $scope.$eval($attrs.tgListitemEpicStatus) + bindOnce $scope, "epicStatusById", (epicStatusById) -> + $el.html(epicStatusById[epic.status].name) + + return {link:link} + +module.directive("tgListitemEpicStatus", ListItemEpicStatusDirective) + ListItemUsStatusDirective = -> link = ($scope, $el, $attrs) -> us = $scope.$eval($attrs.tgListitemUsStatus) diff --git a/app/coffee/modules/search.coffee b/app/coffee/modules/search.coffee index 895a7d55..37cf51bc 100644 --- a/app/coffee/modules/search.coffee +++ b/app/coffee/modules/search.coffee @@ -88,6 +88,8 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin) return @rs.projects.getBySlug(@params.pslug).then (project) => @scope.project = project @scope.$emit('project:loaded', project) + + @scope.epicStatusById = groupBy(project.epic_statuses, (x) -> x.id) @scope.issueStatusById = groupBy(project.issue_statuses, (x) -> x.id) @scope.taskStatusById = groupBy(project.task_statuses, (x) -> x.id) @scope.severityById = groupBy(project.severities, (x) -> x.id) @@ -194,7 +196,7 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) -> return selectedSection if data - for name in ["userstories", "issues", "tasks", "wikipages"] + for name in ["userstories", "epics", "issues", "tasks", "wikipages"] value = data[name] if value.length > maxVal @@ -222,6 +224,7 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) -> activeSectionName = section.name templates = { + epics: $templatecache.get("search-epics") issues: $templatecache.get("search-issues") tasks: $templatecache.get("search-tasks") userstories: $templatecache.get("search-userstories") diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 15c3bf2e..3d8eff98 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1388,6 +1388,7 @@ "SEARCH": { "PAGE_TITLE": "Search - {{projectName}}", "PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User Stories", "FILTER_ISSUES": "Issues", "FILTER_TASKS": "Tasks", diff --git a/app/partials/includes/modules/search-filter.jade b/app/partials/includes/modules/search-filter.jade index 2cf10b90..9f7834a5 100644 --- a/app/partials/includes/modules/search-filter.jade +++ b/app/partials/includes/modules/search-filter.jade @@ -1,4 +1,13 @@ ul.search-filter + li.epics(data-name="epics") + a( + href="#" + title="{{ 'SEARCH.FILTER_EPICS' | translate }}" + ) + tg-svg(svg-icon="icon-epics") + span.num + span.name(translate="SEARCH.FILTER_EPICS") + li.userstories(data-name="userstories") a.active( href="#" diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index 4b5df259..da3ac97b 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -21,6 +21,25 @@ script(type="text/ng-template", id="search-issues") div.empty-large(ng-class="{'hidden': issues.length}") include ../components/empty-search-results +script(type="text/ng-template", id="search-epics") + div.search-result-table-container(ng-class="{'hidden': !epics.length}", tg-bind-scope) + div.search-result-table-header + div.row.title + div.ref(translate="COMMON.FIELDS.REF") + div.user-stories(translate="SEARCH.FILTER_EPICS") + div.status(translate="COMMON.FIELDS.STATUS") + div.search-result-table-body + div.row.table-main(ng-repeat="epic in epics track by epic.id") + div.ref(tg-bo-ref="epic.ref") + div.user-stories + div.user-story-name + a(href="", tg-nav="project-epic-detail:project=project.slug,ref=epic.ref", + tg-bo-bind="epic.subject") + div.status(tg-listitem-epic-status="epic") + + div.empty-search-results(ng-class="{'hidden': epics.length}") + include ../components/empty-search-results + script(type="text/ng-template", id="search-userstories") div.search-result-table-container(ng-class="{'hidden': !userstories.length}", tg-bind-scope) From 01b5717783ed22fea81a334b9104cf7576e7dbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 13:12:37 +0200 Subject: [PATCH 016/137] Add depending epics --- .../epics/dashboard/story-row/story-row.jade | 4 ++- .../epics/dashboard/story-row/story-row.scss | 28 +++++++++++++++++-- .../dependencies/mixins/epics-dashboard.scss | 1 - 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 379e917b..c851e7b2 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -17,7 +17,9 @@ tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} - .story-pill(ng-style="::{'background-color': vm.epic.get('color')}") + .story-pill-wrapper(tg-repeat="pill in vm.story.get('epics')") + .story-pill(ng-style="{'background': pill.get('color')}") + .story-pill-data #{hash}{{pill.get('id')}} {{pill.get('subject')}} .project( ng-if="vm.column.project" tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index ec31c14d..320e93f0 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -35,13 +35,35 @@ .name { flex-basis: 16vw; } + .story-pill-wrapper { + display: inline-block; + position: relative; + &:hover { + .story-pill-data { + display: block; + } + } + } .story-pill { - background: $grayer; + background-color: $grayer; border-radius: 50%; display: inline-block; - height: .5rem; + height: .75rem; margin-left: .25rem; - width: .5rem; + position: relative; + width: .75rem; + } + .story-pill-data { + animation: dropdownFade .2s; + background: rgba($black, .9); + bottom: 1.25rem; + color: $white; + display: none; + left: -100px; + padding: .5rem 1rem; + position: absolute; + width: 200px; + z-index: 99; } .progress-bar, .progress-status { diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index 6465fda3..c7e83fa4 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -44,7 +44,6 @@ .progress { flex-shrink: 3; } - .name, .sprint { overflow: hidden; text-overflow: ellipsis; From ebe8c159f6b426b2c785e83c46c5f23fc61dafde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 16:16:50 +0200 Subject: [PATCH 017/137] Create Epic --- app/locales/taiga/locale-en.json | 8 ++ .../create-epic/create-epic.controller.coffee | 38 ++++++++ .../create-epic/create-epic.directive.coffee | 37 ++++++++ .../epics/create-epic/create-epic.jade | 90 +++++++++++++++++++ .../epics/create-epic/create-epic.scss | 67 ++++++++++++++ .../epic-row/epic-row.controller.coffee | 3 +- .../epics-dashboard.controller.coffee | 17 +++- .../epics/dashboard/epics-dashboard.jade | 8 +- .../resources/epics-resource.service.coffee | 5 ++ 9 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 app/modules/epics/create-epic/create-epic.controller.coffee create mode 100644 app/modules/epics/create-epic/create-epic.directive.coffee create mode 100644 app/modules/epics/create-epic/create-epic.jade create mode 100644 app/modules/epics/create-epic/create-epic.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 3d8eff98..dc285a85 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -412,6 +412,14 @@ "STATUS": "Status", "PROGRESS": "Progress", "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Blocked", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" } }, "PROJECTS": { diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee new file mode 100644 index 00000000..ea0f1b3e --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2015 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: create-epic.controller.coffee +### + +module = angular.module("taigaEpics") + +class CreateEpicController + @.$inject = [ + "tgResources", + "$tgConfirm", + ] + + constructor: (@rs, @confirm) -> + @.attachments = Immutable.List() + + createEpic: () -> + @.newEpic.project = @.project.id + return @rs.epics.post(@.newEpic).then () => + @confirm.notify("success") + @.onReloadEpics() + + +module.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 new file mode 100644 index 00000000..6fd7883a --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -0,0 +1,37 @@ +### +# 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: create-epic.directive.coffee +### + +module = angular.module('taigaEpics') + +CreateEpicDirective = () -> + + return { + templateUrl:"epics/create-epic/create-epic.html", + controller: "CreateEpicCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + project: '=', + onReloadEpics: '&' + } + } + +CreateEpicDirective.$inject = [] + +module.directive("tgCreateEpic", CreateEpicDirective) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade new file mode 100644 index 00000000..645ca696 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.jade @@ -0,0 +1,90 @@ +tg-lightbox-close + +.create-epic-container + h2.title(translate="EPICS.CREATE.TITLE") + form( + ng-submit="vm.createEpic()" + ) + fieldset + input( + type="text" + name="subject" + maxlength="140" + ng-model="vm.newEpic.subject" + tg-auto-select + placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" + required + ) + fieldset + select( + id="epic-status" + name="status" + ng-model="vm.newEpic.status" + ) + option( + ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" + ng-value="status.id" + ng-selected="vm.project.default_epic_status" + ) {{status.name}} + fieldset.tags-block( + tg-lb-tag-line + ng-model="vm.newEpic.tags" + ) + fieldset + textarea( + ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" + ng-model="vm.newEpic.description" + ) + fieldset + tg-attachments-simple( + attachments="vm.attachments" + ) + .settings + fieldset.team-requirement + input( + type="checkbox" + name="team_requirement" + ng-model="vm.newEpic.teamRequirement" + id="team-requirement" + ) + label.requirement.trans-button( + for="team-requirement" + translate="EPICS.CREATE.TEAM_REQUIREMENT" + ) + fieldset.client-requirement + input( + type="checkbox" + name="client_requirement" + ng-model="vm.newEpic.clientRequirement" + id="client-requirement" + ) + label.requirement.trans-button( + for="client-requirement" + translate="EPICS.CREATE.CLIENT_REQUIREMENT" + ) + fieldset + input( + type="checkbox" + name="blocked" + ng-model="vm.newEpic.isBlocked" + id="blocked" + ng-click="displayBlockedReason = !displayBlockedReason" + ) + label.requirement.trans-button.blocked( + for="blocked" + translate="EPICS.CREATE.BLOCKED" + ) + fieldset(ng-if="displayBlockedReason") + input( + type="text" + name="blocked_note" + maxlength="140" + ng-model="vm.newEpic.blocked_note" + placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" + ) + fieldset + input.button-green.create-epic-button( + type="submit" + translate="EPICS.CREATE.CREATE_EPIC" + ) + diff --git a/app/modules/epics/create-epic/create-epic.scss b/app/modules/epics/create-epic/create-epic.scss new file mode 100644 index 00000000..ad2090c4 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.scss @@ -0,0 +1,67 @@ +.lightbox-create-epic { + align-items: center; + display: flex; + justify-content: center; + opacity: 1; + .create-epic-container { + max-width: 700px; + width: 90%; + } + .attachments { + margin-bottom: 0; + } + .settings { + display: flex; + justify-content: center; + fieldset { + margin-right: .5rem; + &:hover { + color: $white; + transition: all .2s ease-in; + transition-delay: .2s; + } + &:last-child { + margin: 0; + } + } + input { + display: none; + &:checked+label { + background: $primary; + border: 1px solid $primary; + color: $white; + } + &:checked+.blocked { + background: $red; + border: 1px solid $red; + color: $white; + } + } + } + label { + @include font-size(small); + background: $mass-white; + border: 1px solid $gray-light; + color: $gray-light; + cursor: pointer; + display: block; + padding: .5rem 3rem; + text-transform: none; + transition: all .2s ease-in; + &:hover { + background: $primary-light; + border: 1px solid $primary; + color: $white; + } + &.blocked { + &:hover { + background: $red-light; + border: 1px solid $red; + } + } + } + .create-epic-button { + display: block; + width: 100%; + } +} 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 221d6088..ba338e5b 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -28,6 +28,7 @@ class EpicRowController constructor: (@rs, @confirm) -> @.displayUserStories = false @._calculateProgressBar() + @.displayAssignedTo = false _calculateProgressBar: () -> totalUs = @.epic.getIn(['user_stories_counts', 'closed']) @@ -67,6 +68,6 @@ class EpicRowController @.displayUserStories = false onSelectAssignedTo: () -> - console.log 'onSelectAssignedTo' + console.log 'Assigned to' module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 477c288e..90066000 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -24,12 +24,14 @@ class EpicsDashboardController "$tgResources", "tgResources", "$routeParams", - "tgErrorHandlingService" + "tgErrorHandlingService", + "tgLightboxFactory", ] - constructor: (@rs, @resources, @params, @errorHandlingService) -> + constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory) -> @.sectionName = "Epics" @._loadProject() + @.createEpic = false _loadProject: () -> return @rs.projects.getBySlug(@params.pslug).then (project) => @@ -43,7 +45,14 @@ class EpicsDashboardController return @resources.epics.list(projectId).then (epics) => @.epics = epics - addNewEpic: () -> - console.log 'Add new Epic' + onCreateEpic: () -> + @lightboxFactory.create('tg-create-epic', { + "class": "lightbox lightbox-create-epic" + "project": "project" + "on-reload-epics": "onReloadEpics" + }, { + "project": @.project + "onReloadEpics": @_loadEpics + }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 788cbdce..c2f22f05 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,5 +1,3 @@ -doctype html - .wrapper() tg-project-menu section.main(role="main") @@ -13,7 +11,7 @@ doctype html button.button-green( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", - ng-click="vm.addNewEpic()" + ng-click="vm.onCreateEpic()" ) tg-epics-table( @@ -37,6 +35,6 @@ doctype html ) button.create-epic.button-green( translate="EPICS.DASHBOARD.ADD" - title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", - ng-click="vm.addNewEpic()" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}" + ng-click="vm.onCreateEpic()" ) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index ddc4fe40..ed06dcb9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -41,6 +41,11 @@ Resource = (urlsService, http) -> return http.patch(url, patch) + service.post = (params) -> + url = urlsService.resolve("epics") + + return http.post(url, params) + return () -> return {"epics": service} From 9423912ccdbb754d409b8c51032bfbc1a917f77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 29 Jul 2016 18:34:39 +0200 Subject: [PATCH 018/137] Show animals avatars --- app/modules/epics/dashboard/epic-row/epic-row.jade | 8 +------- app/modules/epics/dashboard/story-row/story-row.jade | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index d0eab6a2..b62a32e6 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -36,13 +36,7 @@ ng-click="vm.onSelectAssignedTo()" ) img( - ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - img( - ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + tg-avatar="vm.epic.get('assigned_to_extra_info')" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index c851e7b2..fefac319 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -34,13 +34,7 @@ ng-click="vm.onSelectAssignedTo()" ) img( - ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="{{vm.story.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - img( - ng-if="!vm.story.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="https://www.gravatar.com/avatar/{{vm.story.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + tg-avatar="vm.story.get('assigned_to_extra_info')" alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( From 209e33b6477d78628c0596175ef366c3a97da0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 10:42:08 +0200 Subject: [PATCH 019/137] Update epics list on create --- .../create-epic/create-epic.controller.coffee | 7 ++----- .../dashboard/epics-dashboard.controller.coffee | 15 +++++++++++---- .../epics/dashboard/story-row/story-row.jade | 3 --- .../epics/dashboard/story-row/story-row.scss | 14 ++------------ 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index ea0f1b3e..c80ef89c 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -21,18 +21,15 @@ module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ - "tgResources", - "$tgConfirm", + "tgResources" ] - constructor: (@rs, @confirm) -> + constructor: (@rs) -> @.attachments = Immutable.List() createEpic: () -> @.newEpic.project = @.project.id return @rs.epics.post(@.newEpic).then () => - @confirm.notify("success") @.onReloadEpics() - module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 90066000..1c9d8b6e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -26,9 +26,11 @@ class EpicsDashboardController "$routeParams", "tgErrorHandlingService", "tgLightboxFactory", + "lightboxService", + "$tgConfirm" ] - constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory) -> + constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> @.sectionName = "Epics" @._loadProject() @.createEpic = false @@ -45,14 +47,19 @@ class EpicsDashboardController return @resources.epics.list(projectId).then (epics) => @.epics = epics + _onCreateEpic: () -> + @lightboxService.closeAll() + @confirm.notify("success") + @._loadEpics() + onCreateEpic: () -> @lightboxFactory.create('tg-create-epic', { - "class": "lightbox lightbox-create-epic" + "class": "lightbox lightbox-create-epic open" "project": "project" - "on-reload-epics": "onReloadEpics" + "on-reload-epics": "reloadEpics()" }, { "project": @.project - "onReloadEpics": @_loadEpics + "reloadEpics": @._onCreateEpic.bind(this) }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index fefac319..9c8c513a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -1,9 +1,6 @@ .story-row( ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}" ) - tg-svg.icon-drag( - svg-icon="icon-drag" - ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.story.get('is_voter')}" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 320e93f0..4eaf597e 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -8,13 +8,10 @@ border-bottom: 1px solid $whitish; cursor: pointer; display: flex; - margin-left: 5rem; + margin-left: 4rem; transition: background .2s; &:hover { background: rgba($primary-light, .05); - .icon-drag { - opacity: 1; - } } &.is-blocked { background: rgba($red-light, .5); @@ -25,15 +22,8 @@ text-decoration: line-through; } } - .icon-drag { - @include svg-size(.75rem); - cursor: move; - fill: $whitish; - opacity: 0; - transition: opacity .1s; - } .name { - flex-basis: 16vw; + flex-basis: 17.5vw; } .story-pill-wrapper { display: inline-block; From 8e23148920302a3b3e13e20372a7df2908ca3db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 1 Aug 2016 13:13:56 +0200 Subject: [PATCH 020/137] Fix create form and progress bars --- app/modules/epics/create-epic/create-epic.jade | 8 ++++---- .../dashboard/epic-row/epic-row.controller.coffee | 13 ++++++++++--- app/modules/epics/dashboard/epic-row/epic-row.jade | 2 +- .../dashboard/story-row/story-row.controller.coffee | 13 ++++++++----- .../epics/dashboard/story-row/story-row.jade | 6 +++--- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 645ca696..44367e04 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -44,7 +44,7 @@ tg-lightbox-close input( type="checkbox" name="team_requirement" - ng-model="vm.newEpic.teamRequirement" + ng-model="vm.newEpic.team_requirement" id="team-requirement" ) label.requirement.trans-button( @@ -55,7 +55,7 @@ tg-lightbox-close input( type="checkbox" name="client_requirement" - ng-model="vm.newEpic.clientRequirement" + ng-model="vm.newEpic.client_requirement" id="client-requirement" ) label.requirement.trans-button( @@ -66,7 +66,7 @@ tg-lightbox-close input( type="checkbox" name="blocked" - ng-model="vm.newEpic.isBlocked" + ng-model="vm.newEpic.is_blocked" id="blocked" ng-click="displayBlockedReason = !displayBlockedReason" ) @@ -87,4 +87,4 @@ tg-lightbox-close type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) - + 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 ba338e5b..d840e567 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -31,9 +31,16 @@ class EpicRowController @.displayAssignedTo = false _calculateProgressBar: () -> - totalUs = @.epic.getIn(['user_stories_counts', 'closed']) - totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) - @.percentage = totalUs * 100 / totalUsCompleted + if @.epic.getIn(['status_extra_info', 'is_closed']) == true + @.percentage = "100%" + else + opened = @.epic.getIn(['user_stories_counts', 'opened']) + closed = @.epic.getIn(['user_stories_counts', 'closed']) + total = opened + closed + if total == 0 + @.percentage = "0%" + else + @.percentage = "#{closed * 100 / total}%" updateEpicStatus: (status) -> id = @.epic.get('id') diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index b62a32e6..c7c9d94a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -74,7 +74,7 @@ .progress-bar .progress-status( ng-if="::vm.percentage" - ng-attr-width="::vm.percentage" + ng-style="{'width':vm.percentage}" ) .epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") 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 d2b2f68b..e93eca79 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -26,11 +26,14 @@ class StoryRowController @._calculateProgressBar() _calculateProgressBar: () -> - tasks = @.story.get('tasks').toJS() - totalTasks = @.story.get('tasks').size - areTasksCompleted = _.map(tasks, 'is_closed') - totalTasksCompleted = _.pull(areTasksCompleted, false).length - @.percentage = totalTasksCompleted * 100 / totalTasks + 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 + @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" onSelectAssignedTo: () -> console.log 'ng-click="vm.onSelectAssignedTo()"' diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 9c8c513a..0783c479 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -4,10 +4,10 @@ .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.story.get('is_voter')}" - ) + ) tg-svg(svg-icon='icon-upvote') span {{::vm.story.get('total_voters')}} - + .name(ng-if="vm.column.name") - var hash = "#"; a( @@ -48,5 +48,5 @@ .progress-bar .progress-status( ng-if="::vm.percentage" - ng-attr-width="::vm.percentage" + ng-style="{'width':vm.percentage}" ) From 3e5ab894a650f35609b142028ce3e460853238bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 13:37:53 +0200 Subject: [PATCH 021/137] Create Assigned to component --- .../assigned-to-selector.directive.coffee | 40 +++++++++++++++++++ .../assigned-to-selector.jade | 4 ++ .../assigned-to/assigned-to.controller.coffee | 37 +++++++++++++++++ .../assigned-to/assigned-to.directive.coffee | 35 ++++++++++++++++ .../components/assigned-to/assigned-to.jade | 13 ++++++ .../epics/create-epic/create-epic.jade | 2 + .../epic-row/epic-row.controller.coffee | 1 - .../epics/dashboard/epic-row/epic-row.jade | 20 ++-------- .../epics-dashboard.controller.coffee | 6 +-- .../epics/dashboard/epics-dashboard.jade | 1 + .../epics-table/epics-table.directive.coffee | 5 ++- .../dashboard/epics-table/epics-table.jade | 2 +- 12 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade create mode 100644 app/modules/components/assigned-to/assigned-to.controller.coffee create mode 100644 app/modules/components/assigned-to/assigned-to.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-to.jade diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee new file mode 100644 index 00000000..b13be4d0 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -0,0 +1,40 @@ +### +# 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: assigned-to-selector.directive.coffee +### + +AssignedToSelectorDirective = () -> + + link = (scope, el, attrs) -> + console.log scope.assigned.toJS() + console.log scope.project.toJS() + + return { + # controller: "AssignedToSelectorCtrl", + # controllerAs: "vm", + # bindToController: true, + templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", + scope: { + assigned: "=", + project: "=" + }, + link: link + } + +AssignedToSelectorDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedToSelector", AssignedToSelectorDirective) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade new file mode 100644 index 00000000..29de6158 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -0,0 +1,4 @@ +tg-lightbox-close + +.assigned-to-container + h2.title(translate="COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT") diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee new file mode 100644 index 00000000..d7b76bc4 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 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: attchment.controller.coffee +### + +class AssignedToController + @.$inject = [ + "tgLightboxFactory" + ] + + constructor: (@lightboxFactory) -> + + onSelectAssignedTo: (assigned, project) -> + @lightboxFactory.create('tg-assigned-to-selector', { + "class": "lightbox lightbox-assigned-to-selector open" + "assigned": "assigned" + "project": "project" + }, { + "assigned": assigned + "project": project + }) + +angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee new file mode 100644 index 00000000..bc596b3c --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -0,0 +1,35 @@ +### +# 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: assigned-to.directive.coffee +### + +AssignedToDirective = () -> + + return { + controller: "AssignedToCtrl", + controllerAs: "vm", + bindToController: true, + templateUrl: "components/assigned-to/assigned-to.html", + scope: { + assignedTo: "=" + project: "=" + } + } + +AssignedToDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedToComponent", AssignedToDirective) diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade new file mode 100644 index 00000000..e1570c69 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -0,0 +1,13 @@ +img.assigned-to( + ng-if="vm.assignedTo" + tg-avatar="vm.assignedTo" + alt="{{vm.assignedTo.get('full_name_display')}}" + title="{{vm.assignedTo.get('full_name_display')}}" + ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" +) +img.assigned-to( + ng-if="!vm.assignedTo" + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" +) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 44367e04..c7ccf9c2 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -6,6 +6,8 @@ tg-lightbox-close ng-submit="vm.createEpic()" ) fieldset + // TODO ADD COLOR SELECTOR + //- tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") input( type="text" name="subject" 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 d840e567..ffe2898a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -54,7 +54,6 @@ class EpicRowController @.onUpdateEpicStatus() onError = (data) => - console.log data @confirm.notify('error') return @rs.epics.patch(id, patch).then(onSuccess, onError) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c7c9d94a..627579ce 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -31,22 +31,10 @@ .sprint( ng-if="vm.column.sprint" ) - .assigned( - ng-if="vm.column.assigned && vm.epic.get('assigned_to')" - ng-click="vm.onSelectAssignedTo()" - ) - img( - tg-avatar="vm.epic.get('assigned_to_extra_info')" - alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - .assigned( - ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" - ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" - ng-click="vm.onSelectAssignedTo()" - ) - img( - src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + .assigned + tg-assigned-to-component( + assigned-to="vm.epic.get('assigned_to_extra_info')" + project="vm.project" ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 1c9d8b6e..39c94575 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -40,9 +40,9 @@ class EpicsDashboardController if not project.is_epics_activated @errorHandlingService.permissionDenied() @.project = project - @._loadEpics() + @.loadEpics() - _loadEpics: () -> + loadEpics: () -> projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics @@ -50,7 +50,7 @@ class EpicsDashboardController _onCreateEpic: () -> @lightboxService.closeAll() @confirm.notify("success") - @._loadEpics() + @.loadEpics() onCreateEpic: () -> @lightboxFactory.create('tg-create-epic', { diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index c2f22f05..3657a406 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,6 +18,7 @@ ng-if="vm.project && vm.epics.size" project="vm.project" epics="vm.epics" + on-update-epic-status="vm.loadEpics()" ) section.empty-epics(ng-if="!vm.epics.size") 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 bc4793cb..e6b273d3 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -27,8 +27,9 @@ EpicsTableDirective = () -> controllerAs: "vm", bindToController: true, scope: { - epics: "=" - project: "=" + epics: "=", + project: "=", + onUpdateEpicStatus: "&" } } diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index ef17423b..8dec9f2d 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,6 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" - on-update-epic-status="vm.loadEpics()" + on-update-epic-status="vm.onUpdateEpicStatus()" permissions="vm.permissions" ) From 9e4ac74cfe2a7e883e1ee34d3e82db5a7a9fc1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 15:39:26 +0200 Subject: [PATCH 022/137] Assigned to lightbox component --- .../assigned-item.directive.coffee | 37 +++++++++++++++++++ .../assigned-item/assigned-item.jade | 1 + .../assigned-to-selector.controller.coffee | 25 +++++++++++++ .../assigned-to-selector.directive.coffee | 13 ++----- .../assigned-to-selector.jade | 15 +++++++- .../assigned-to/assigned-to.controller.coffee | 7 ++-- .../assigned-to/assigned-to.directive.coffee | 2 +- .../components/assigned-to/assigned-to.jade | 15 +++++++- .../epics/dashboard/story-row/story-row.jade | 11 +----- .../epics/dashboard/story-row/story-row.scss | 3 -- 10 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.jade create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee new file mode 100644 index 00000000..42bcf36d --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee @@ -0,0 +1,37 @@ +### +# 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: assigned-to-selector.directive.coffee +### + +AssignedItemDirective = () -> + + link = (scope, el, attrs) -> + + return { + # controller: "AssignedToSelectorCtrl", + # controllerAs: "vm", + # bindToController: true, + templateUrl: "components/assigned-to/assigned-item/assigned-item.html", + scope: { + member: "=", + }, + link: link + } + +AssignedItemDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedItem", AssignedItemDirective) diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade new file mode 100644 index 00000000..d3c97e4f --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -0,0 +1 @@ +.member {{member}} diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee new file mode 100644 index 00000000..6d05b878 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -0,0 +1,25 @@ +### +# Copyright (C) 2014-2015 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: assigned-to-selector.controller.coffee +### + +class AssignedToSelectorController + @.$inject = [] + + constructor: () -> + +angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee index b13be4d0..33b828d0 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -19,20 +19,15 @@ AssignedToSelectorDirective = () -> - link = (scope, el, attrs) -> - console.log scope.assigned.toJS() - console.log scope.project.toJS() - return { - # controller: "AssignedToSelectorCtrl", - # controllerAs: "vm", - # bindToController: true, + controller: "AssignedToSelectorCtrl", + controllerAs: "vm", + bindToController: true, templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", scope: { assigned: "=", project: "=" - }, - link: link + } } AssignedToSelectorDirective.$inject = [] diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index 29de6158..e14d5c8c 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -1,4 +1,17 @@ tg-lightbox-close .assigned-to-container - h2.title(translate="COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT") + h2.title(translate="LIGHTBOX.ASSIGNED_TO.SELECT") + input.assign-input( + type="text" + placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}" + autofocus + ng-model="vm.assignToMember.name" + ng-model-options="{debounce: 200}" + ) + ul.tags-dropdown + li(ng-repeat="member in vm.project.members | filter: vm.assignToMember.name") + tg-assigned-item.assigned-members-option( + member="member" + ng-click="vm.onAddTag(tag[0], tag[1], vm.project)" + ) diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index d7b76bc4..9a04e103 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -23,15 +23,16 @@ class AssignedToController ] constructor: (@lightboxFactory) -> + @.has_permissions = _.includes(@.project.my_permissions, 'modify_epic') onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { "class": "lightbox lightbox-assigned-to-selector open" - "assigned": "assigned" + "assignedTo": "assignedTo" "project": "project" }, { - "assigned": assigned - "project": project + "assignedTo": @.assignedTo + "project": @.project }) angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee index bc596b3c..ae0683ce 100644 --- a/app/modules/components/assigned-to/assigned-to.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -25,7 +25,7 @@ AssignedToDirective = () -> bindToController: true, templateUrl: "components/assigned-to/assigned-to.html", scope: { - assignedTo: "=" + assignedTo: "=", project: "=" } } diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade index e1570c69..3eb7fe76 100644 --- a/app/modules/components/assigned-to/assigned-to.jade +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -1,13 +1,24 @@ img.assigned-to( - ng-if="vm.assignedTo" + ng-if="vm.assignedTo && vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) img.assigned-to( - ng-if="!vm.assignedTo" + ng-if="vm.assignedTo && !vm.has_permissions" + tg-avatar="vm.assignedTo" + alt="{{vm.assignedTo.get('full_name_display')}}" + title="{{vm.assignedTo.get('full_name_display')}}" +) +img.assigned-to( + ng-if="!vm.assignedTo && vm.has_permissions" src="/#{v}/images/unnamed.png" alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) +img.assigned-to( + ng-if="!vm.assignedTo && !vm.has_permissions" + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" +) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 0783c479..ba9580ef 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -26,19 +26,12 @@ alt="{{::vm.story.getIn(['project_extra_info', 'name'])}}" ) .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} - .assigned( - ng-if="vm.column.assigned && vm.story.get('assigned_to')" - ng-click="vm.onSelectAssignedTo()" - ) + .assigned(ng-if="vm.column.assigned && vm.story.get('assigned_to')") img( tg-avatar="vm.story.get('assigned_to_extra_info')" alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) - .assigned( - ng-if="vm.column.assigned && !vm.story.get('assigned_to')" - ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" - ng-click="vm.onSelectAssignedTo()" - ) + .assigned(ng-if="vm.column.assigned && !vm.story.get('assigned_to')") img( src="/#{v}/images/unnamed.png" alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 4eaf597e..ad742a2a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -86,7 +86,4 @@ margin-right: .25rem; vertical-align: middle; } - .is-unassigned { - color: $gray-light; - } } From 1a76aa5d1ada7ebd9de95a670b4d5e4495179abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 16:53:00 +0200 Subject: [PATCH 023/137] Filter Current assigned --- .../assigned-item/assigned-item.jade | 4 +++- .../assigned-item/assigned-item.scss | 20 +++++++++++++++++++ .../assigned-to-selector.controller.coffee | 9 +++++++++ .../assigned-to-selector.jade | 6 +++--- .../assigned-to-selector.scss | 7 +++++++ .../assigned-to/assigned-to.controller.coffee | 6 +++--- 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.scss create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade index d3c97e4f..28564e61 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.jade +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -1 +1,3 @@ -.member {{member}} +.assignable-member-single(ng-click="onSelectMember()") + img.assignable-member-avatar(tg-avatar="member") + .assignable-member-name {{member.full_name}} diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.scss b/app/modules/components/assigned-to/assigned-item/assigned-item.scss new file mode 100644 index 00000000..a4865088 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.scss @@ -0,0 +1,20 @@ +.assignable-member-single { + align-items: center; + background: $white; + border-bottom: 1px solid $whitish; + display: flex; + padding: .25rem 0; + &:hover { + background: rgba($primary-light, .05); + cursor: pointer; + } + .assignable-member-avatar { + flex-basis: 3rem; + margin-right: .5rem; + max-height: 3rem; + max-width: 3rem; + } + .assignable-member-name { + flex: 1; + } +} diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee index 6d05b878..65bf4b42 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -21,5 +21,14 @@ class AssignedToSelectorController @.$inject = [] constructor: () -> + @._filterAssignedMember() + + _filterAssignedMember: () -> + @.nonAssignedMembers = _.filter(@.project.members, (member) => + return member.id != @.assigned.get('id') + ) + + onAssignTo: (member) -> + console.log member angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index e14d5c8c..12c02a7c 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -9,9 +9,9 @@ tg-lightbox-close ng-model="vm.assignToMember.name" ng-model-options="{debounce: 200}" ) - ul.tags-dropdown - li(ng-repeat="member in vm.project.members | filter: vm.assignToMember.name") + ul.assignable-member-list + li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" - ng-click="vm.onAddTag(tag[0], tag[1], vm.project)" + ng-click="vm.onAssignTo(member)" ) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss new file mode 100644 index 00000000..c5b1aec3 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss @@ -0,0 +1,7 @@ +.assigned-to-container { + width: 600px; +} + +.assignable-member-list { + margin-top: 1rem; +} diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index 9a04e103..d3e1c878 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -27,11 +27,11 @@ class AssignedToController onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { - "class": "lightbox lightbox-assigned-to-selector open" - "assignedTo": "assignedTo" + "class": "lightbox lightbox-assigned-to-selector open", + "assigned": "assigned", "project": "project" }, { - "assignedTo": @.assignedTo + "assigned": @.assignedTo, "project": @.project }) From 1891191ebc9f38377ea95f88738dc59b16fbcca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 2 Aug 2016 10:24:33 +0200 Subject: [PATCH 024/137] Update assigned --- .../assigned-item.directive.coffee | 5 +-- .../assigned-item/assigned-item.jade | 2 +- .../assigned-item/assigned-item.scss | 8 +++-- .../assigned-to-selector.controller.coffee | 17 ++++++--- .../assigned-to-selector.directive.coffee | 4 ++- .../assigned-to-selector.jade | 12 ++++++- .../assigned-to-selector.scss | 20 +++++++++++ .../assigned-to/assigned-to.controller.coffee | 23 +++++++++--- .../assigned-to/assigned-to.directive.coffee | 4 ++- .../epic-row/epic-row.controller.coffee | 35 +++++++++++++++++-- .../epics/dashboard/epic-row/epic-row.jade | 2 ++ 11 files changed, 109 insertions(+), 23 deletions(-) diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee index 42bcf36d..709ba6cc 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee @@ -22,12 +22,9 @@ AssignedItemDirective = () -> link = (scope, el, attrs) -> return { - # controller: "AssignedToSelectorCtrl", - # controllerAs: "vm", - # bindToController: true, templateUrl: "components/assigned-to/assigned-item/assigned-item.html", scope: { - member: "=", + member: "=" }, link: link } diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade index 28564e61..b0c06515 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.jade +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -1,3 +1,3 @@ -.assignable-member-single(ng-click="onSelectMember()") +.assignable-member-single img.assignable-member-avatar(tg-avatar="member") .assignable-member-name {{member.full_name}} diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.scss b/app/modules/components/assigned-to/assigned-item/assigned-item.scss index a4865088..132d34aa 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.scss +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.scss @@ -1,12 +1,14 @@ .assignable-member-single { align-items: center; - background: $white; - border-bottom: 1px solid $whitish; display: flex; padding: .25rem 0; + .assigned-members-option & { + background: $white; + border-bottom: 1px solid $whitish; + cursor: pointer; + } &:hover { background: rgba($primary-light, .05); - cursor: pointer; } .assignable-member-avatar { flex-basis: 3rem; diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee index 65bf4b42..4e70615b 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -21,14 +21,21 @@ class AssignedToSelectorController @.$inject = [] constructor: () -> + if @.assigned + @._getAssignedMember() @._filterAssignedMember() - _filterAssignedMember: () -> - @.nonAssignedMembers = _.filter(@.project.members, (member) => - return member.id != @.assigned.get('id') + _getAssignedMember: () -> + @.assignedMember = _.filter(@.project.members, (member) => + return member.id == @.assigned.get('id') ) - onAssignTo: (member) -> - console.log member + _filterAssignedMember: () -> + if @.assigned + @.nonAssignedMembers = _.filter(@.project.members, (member) => + return member.id != @.assigned.get('id') + ) + else + @.nonAssignedMembers = @.project.members angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee index 33b828d0..b840e856 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -26,7 +26,9 @@ AssignedToSelectorDirective = () -> templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", scope: { assigned: "=", - project: "=" + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" } } diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index 12c02a7c..e74b5bb3 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -10,8 +10,18 @@ tg-lightbox-close ng-model-options="{debounce: 200}" ) ul.assignable-member-list + li.assigned-member( + ng-repeat="member in vm.assignedMember" + ng-if="vm.assigned" + ) + tg-assigned-item(member="member") + tg-svg.unassign-epic( + svg-icon="icon-close" + svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED" + ng-click="vm.onRemoveAssigned()" + ) li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" - ng-click="vm.onAssignTo(member)" + ng-click="vm.onAssignTo({'member': member})" ) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss index c5b1aec3..ac54aa3a 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss @@ -4,4 +4,24 @@ .assignable-member-list { margin-top: 1rem; + .assigned-member { + align-items: center; + background: rgba($primary-light, .05); + border-bottom: 1px solid $whitish; + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + } + .unassign-epic { + cursor: pointer; + margin-right: 1rem; + } + .icon { + fill: $red-light; + transition: fill .2s; + &:hover { + cursor: pointer; + fill: $red; + } + } } diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index d3e1c878..dc69b30e 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -14,25 +14,38 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: attchment.controller.coffee +# File: assigned-to.controller.coffee ### class AssignedToController @.$inject = [ - "tgLightboxFactory" + "tgLightboxFactory", + "lightboxService", ] - constructor: (@lightboxFactory) -> + constructor: (@lightboxFactory, @lightboxService) -> @.has_permissions = _.includes(@.project.my_permissions, 'modify_epic') + _closeAndRemoveAssigned: () -> + @lightboxService.closeAll() + @.onRemoveAssigned() + + _closeAndAssign: (member) -> + @lightboxService.closeAll() + @.onAssignTo({'member': member}) + onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { "class": "lightbox lightbox-assigned-to-selector open", "assigned": "assigned", - "project": "project" + "project": "project", + "on-remove-assigned": "onRemoveAssigned()" + "on-assign-to": "assignTo(member)" }, { "assigned": @.assignedTo, - "project": @.project + "project": @.project, + "onRemoveAssigned": @._closeAndRemoveAssigned.bind(this), + "assignTo": @._closeAndAssign.bind(this) }) angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee index ae0683ce..a6ec47aa 100644 --- a/app/modules/components/assigned-to/assigned-to.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -26,7 +26,9 @@ AssignedToDirective = () -> templateUrl: "components/assigned-to/assigned-to.html", scope: { assignedTo: "=", - project: "=" + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" } } 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 ffe2898a..7b26a8d2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -73,7 +73,38 @@ class EpicRowController else @.displayUserStories = false - onSelectAssignedTo: () -> - console.log 'Assigned to' + onRemoveAssigned: () -> + id = @.epic.get('id') + version = @.epic.get('version') + patch = { + 'assigned_to': null, + 'version': version + } + + onSuccess = => + @.onUpdateEpicStatus() + @confirm.notify('success') + + 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 = => + @.onUpdateEpicStatus() + @confirm.notify('success') + + onError = (data) => + @confirm.notify('error') + + return @rs.epics.patch(id, patch).then(onSuccess, onError) module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 627579ce..4e778250 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -35,6 +35,8 @@ 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)" ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" From 901fad6229c2e79bd332bd8dce63ff2fc441f35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 08:53:45 +0200 Subject: [PATCH 025/137] Update Epics --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 6 +++--- .../epics/dashboard/epic-row/epic-row.directive.coffee | 2 +- .../epics/dashboard/epics-dashboard.controller.coffee | 1 + app/modules/epics/dashboard/epics-dashboard.jade | 2 +- .../dashboard/epics-table/epics-table.directive.coffee | 2 +- app/modules/epics/dashboard/epics-table/epics-table.jade | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) 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 7b26a8d2..ae0d0cc0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -51,7 +51,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() onError = (data) => @confirm.notify('error') @@ -82,7 +82,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() @confirm.notify('success') onError = (data) => @@ -99,7 +99,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() @confirm.notify('success') onError = (data) => 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 14b224c8..ecf94d4d 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -31,7 +31,7 @@ EpicRowDirective = () -> epic: '=', column: '=', permissions: '=', - onUpdateEpicStatus: "&" + onUpdateEpic: "&" } } diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 39c94575..9e2e6a36 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -43,6 +43,7 @@ class EpicsDashboardController @.loadEpics() loadEpics: () -> + console.log 'reload' projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 3657a406..50abb268 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,7 +18,7 @@ ng-if="vm.project && vm.epics.size" project="vm.project" epics="vm.epics" - on-update-epic-status="vm.loadEpics()" + on-update-epic="vm.loadEpics()" ) section.empty-epics(ng-if="!vm.epics.size") 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 e6b273d3..6d89296e 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -29,7 +29,7 @@ EpicsTableDirective = () -> scope: { epics: "=", project: "=", - onUpdateEpicStatus: "&" + onUpdateEpic: "&" } } diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 8dec9f2d..e82bdab0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,6 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" - on-update-epic-status="vm.onUpdateEpicStatus()" + on-update-epic="vm.onUpdateEpic()" permissions="vm.permissions" ) From 596d854f76d3e0a06f614461c5680d449334308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 09:34:14 +0200 Subject: [PATCH 026/137] Addremove epic module --- app/locales/taiga/locale-en.json | 2 ++ .../project-menu.controller.coffee | 4 ++++ .../components/project-menu/project-menu.jade | 4 ++-- app/partials/admin/admin-project-modules.jade | 20 ++++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index dc285a85..a13aef93 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -492,6 +492,8 @@ "TITLE": "Modules", "ENABLE": "Enable", "DISABLE": "Disable", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Manage your user stories to maintain an organized view of upcoming and prioritized work.", "NUMBER_SPRINTS": "Expected number of sprints", diff --git a/app/modules/components/project-menu/project-menu.controller.coffee b/app/modules/components/project-menu/project-menu.controller.coffee index 23fc3591..9ef67946 100644 --- a/app/modules/components/project-menu/project-menu.controller.coffee +++ b/app/modules/components/project-menu/project-menu.controller.coffee @@ -52,12 +52,16 @@ class ProjectMenuController _setMenuPermissions: () -> @.menu = Immutable.Map({ + epics: false, backlog: false, kanban: false, issues: false, wiki: false }) + if @.project.get("is_epics_activated") && @.project.get("my_permissions").indexOf("view_epics") != -1 + @.menu = @.menu.set("epics", true) + if @.project.get("is_backlog_activated") && @.project.get("my_permissions").indexOf("view_us") != -1 @.menu = @.menu.set("backlog", true) diff --git a/app/modules/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index 829854d8..62fc2b9c 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -24,8 +24,8 @@ nav.menu( ) tg-svg(svg-icon="icon-timeline") span.helper(translate="PROJECT.SECTION.TIMELINE") - - li#nav-epics + + li#nav-epics(ng-if="vm.menu.get('epics')") a( tg-nav="project-epics:project=vm.project.get('slug')" ng-class="{active: vm.active == 'epics'}" diff --git a/app/partials/admin/admin-project-modules.jade b/app/partials/admin/admin-project-modules.jade index 09f80d3a..037a231e 100644 --- a/app/partials/admin/admin-project-modules.jade +++ b/app/partials/admin/admin-project-modules.jade @@ -17,12 +17,30 @@ div.wrapper( include ../includes/components/mainTitle form.module-container + .module.module-epics(ng-class="{true:'active', false:''}[project.is_epics_activated]") + .module-icon + tg-svg(svg-icon="icon-epics") + .module-name(translate="ADMIN.MODULES.EPICS") + .module-desc + p(translate="ADMIN.MODULES.EPICS_DESCRIPTION") + .module-activation.module-direct-active + div.check + input.activate-input( + id="functionality-epics" + name="functionality-epics" + type="checkbox" + ng-checked="project.is_epics_activated" + ng-model="project.is_epics_activated" + ) + div + span.check-text.check-yes(translate="COMMON.YES") + span.check-text.check-no(translate="COMMON.NO") .module.module-scrum(ng-class="{true:'active', false:''}[project.is_backlog_activated]") .module-icon tg-svg(svg-icon="icon-scrum") .module-name(translate="ADMIN.MODULES.BACKLOG") .module-desc - p(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION") + p(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION") .module-desc-options(ng-if="project.is_backlog_activated") fieldset label(for="total-sprints") {{ 'ADMIN.MODULES.NUMBER_SPRINTS' | translate }} From 1946d25e8713dc27e9f9e4223d81320890d414a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 15:40:30 +0200 Subject: [PATCH 027/137] Add epics to user dashboard --- app/coffee/modules/base.coffee | 2 +- app/locales/taiga/locale-en.json | 1 + .../epics/dashboard/epic-row/epic-row.jade | 2 +- app/modules/home/duties/duty.directive.coffee | 2 + app/modules/home/home.service.coffee | 33 +++++++++++++-- app/modules/home/home.service.spec.coffee | 41 ++++++++++++++++++- .../working-on/working-on.controller.coffee | 6 ++- .../resources/epics-resource.service.coffee | 13 ++++-- .../includes/modules/search-result-table.jade | 2 +- 9 files changed, 88 insertions(+), 14 deletions(-) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 708dd70e..5cfa7dd4 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -76,7 +76,7 @@ urls = { "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" - "project-epic-detail": "/project/:project/epic/:ref" + "project-epics-detail": "/project/:project/epic/:ref" "project-userstories-detail": "/project/:project/us/:ref" "project-tasks-detail": "/project/:project/task/:ref" "project-issues-detail": "/project/:project/issue/:ref" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index a13aef93..2c16e946 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -119,6 +119,7 @@ "USER_STORY": "User story", "TASK": "Task", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Enter tag", "DELETE": "Delete tag", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 4e778250..c6ff107a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -15,7 +15,7 @@ .name(ng-if="vm.column.name") - var hash = "#"; a( - tg-nav="project-epic-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=vm.project.get('slug')" ng-attr-title="{{::vm.epic.get('subject')}}" ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} span.epic-pill( diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 798db53c..e4f40268 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -25,6 +25,8 @@ DutyDirective = (navurls, $translate) -> scope.vm.getDutyType = () -> if scope.vm.duty + if scope.vm.duty.get('_name') == "epics" + return $translate.instant("COMMON.EPIC") if scope.vm.duty.get('_name') == "userstories" return $translate.instant("COMMON.USER_STORY") if scope.vm.duty.get('_name') == "tasks" diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 74bb9e5f..862e6b68 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -57,6 +57,10 @@ class HomeService extends taiga.Service assignedTo = workInProgress.get("assignedTo") + if assignedTo.get("epics") + _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("epics"), "epics") + assignedTo = assignedTo.set("epics", _duties) + if assignedTo.get("userStories") _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("userStories"), "userstories") assignedTo = assignedTo.set("userStories", _duties) @@ -65,7 +69,6 @@ class HomeService extends taiga.Service _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("tasks"), "tasks") assignedTo = assignedTo.set("tasks", _duties) - if assignedTo.get("issues") _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("issues"), "issues") assignedTo = assignedTo.set("issues", _duties) @@ -73,6 +76,10 @@ class HomeService extends taiga.Service watching = workInProgress.get("watching") + if watching.get("epics") + _duties = _getValidDutiesAndAttachProjectInfo(watching.get("epics"), "epics") + watching = watching.set("epics", _duties) + if watching.get("userStories") _duties = _getValidDutiesAndAttachProjectInfo(watching.get("userStories"), "userstories") watching = watching.set("userStories", _duties) @@ -106,6 +113,14 @@ class HomeService extends taiga.Service assigned_to: userId } + params_epics = { + is_closed: false + assigned_to: userId + } + + assignedEpicsPromise = @rs.epics.listInAllProjects(params_epics).then (epics) -> + assignedTo = assignedTo.set("epics", epics) + assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) -> assignedTo = assignedTo.set("userStories", userstories) @@ -125,8 +140,16 @@ class HomeService extends taiga.Service watchers: userId } + params_epics = { + is_closed: false + watchers: userId + } + watching = Immutable.Map() + watchingEpicsPromise = @rs.epics.listInAllProjects(params_epics).then (epics) -> + watching = watching.set("epics", epics) + watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) -> watching = watching.set("userStories", userstories) @@ -139,12 +162,14 @@ class HomeService extends taiga.Service workInProgress = Immutable.Map() Promise.all([ - projectsPromise + projectsPromise, + assignedEpicsPromise, + watchingEpicsPromise, assignedUserStoriesPromise, - assignedTasksPromise, - assignedIssuesPromise, watchingUserStoriesPromise, + assignedTasksPromise, watchingTasksPromise, + assignedIssuesPromise, watchingIssuesPromise ]).then => workInProgress = workInProgress.set("assignedTo", assignedTo) diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index f1ef832f..000477e9 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -24,10 +24,12 @@ describe "tgHome", -> _mockResources = () -> mocks.resources = {} + mocks.resources.epics = {} mocks.resources.userstories = {} mocks.resources.tasks = {} mocks.resources.issues = {} + mocks.resources.epics.listInAllProjects = sinon.stub() mocks.resources.userstories.listInAllProjects = sinon.stub() mocks.resources.tasks.listInAllProjects = sinon.stub() mocks.resources.issues.listInAllProjects = sinon.stub() @@ -70,7 +72,7 @@ describe "tgHome", -> _setup() _inject() - it "get work in progress by user", (done) -> + it.only "get work in progress by user", (done) -> userId = 3 project1 = {id: 1, name: "fake1", slug: "project-1"} @@ -83,6 +85,25 @@ describe "tgHome", -> project2 ])) + mocks.resources.epics.listInAllProjects + .withArgs(sinon.match({ + is_closed: false + assigned_to: userId + })) + .promise() + .resolve(Immutable.fromJS([{id: 4, ref: 4, project: "1"}])) + + mocks.resources.epics.listInAllProjects + .withArgs(sinon.match({ + is_closed: false + watchers: userId + })) + .promise() + .resolve(Immutable.fromJS([ + {id: 4, ref: 4, project: "1"}, + {id: 5, ref: 5, project: "10"} # the user is not member of this project + ])) + mocks.resources.userstories.listInAllProjects .withArgs(sinon.match({ is_closed: false @@ -109,6 +130,10 @@ describe "tgHome", -> .resolve(Immutable.fromJS([{id: 3, ref: 3, project: "1"}])) # mock urls + mocks.tgNavUrls.resolve + .withArgs("project-epics-detail", {project: "project-1", ref: 4}) + .returns("/testing-project/epic/1") + mocks.tgNavUrls.resolve .withArgs("project-userstories-detail", {project: "project-1", ref: 1}) .returns("/testing-project/us/1") @@ -125,6 +150,13 @@ describe "tgHome", -> .then (workInProgress) -> expect(workInProgress.toJS()).to.be.eql({ assignedTo: { + epics: [{ + id: 4, + ref: 4, + url: '/testing-project/epic/1', + project: project1, + _name: 'epics' + }] userStories: [{ id: 1, ref: 1, @@ -148,6 +180,13 @@ describe "tgHome", -> }] } watching: { + epics: [{ + id: 4, + ref: 4, + url: '/testing-project/epic/1', + project: project1, + _name: 'epics' + }] userStories: [{ id: 1, ref: 1, diff --git a/app/modules/home/working-on/working-on.controller.coffee b/app/modules/home/working-on/working-on.controller.coffee index dba27fc7..b02d341d 100644 --- a/app/modules/home/working-on/working-on.controller.coffee +++ b/app/modules/home/working-on/working-on.controller.coffee @@ -27,20 +27,22 @@ class WorkingOnController @.watching = Immutable.Map() _setAssignedTo: (workInProgress) -> + epics = workInProgress.get("assignedTo").get("epics") userStories = workInProgress.get("assignedTo").get("userStories") tasks = workInProgress.get("assignedTo").get("tasks") issues = workInProgress.get("assignedTo").get("issues") - @.assignedTo = userStories.concat(tasks).concat(issues) + @.assignedTo = userStories.concat(tasks).concat(issues).concat(epics) if @.assignedTo.size > 0 @.assignedTo = @.assignedTo.sortBy((elem) -> elem.get("modified_date")).reverse() _setWatching: (workInProgress) -> + epics = workInProgress.get("watching").get("epics") userStories = workInProgress.get("watching").get("userStories") tasks = workInProgress.get("watching").get("tasks") issues = workInProgress.get("watching").get("issues") - @.watching = userStories.concat(tasks).concat(issues) + @.watching = userStories.concat(tasks).concat(issues).concat(epics) if @.watching.size > 0 @.watching = @.watching.sortBy((elem) -> elem.get("modified_date")).reverse() diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index ed06dcb9..21129745 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -20,13 +20,18 @@ Resource = (urlsService, http) -> service = {} - service.listAll = (params) -> + service.listInAllProjects = (params) -> url = urlsService.resolve("epics") - httpOptions = {} + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } - return http.get(url, params, httpOptions).then (result) -> - return Immutable.fromJS(result.data) + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) service.list = (projectId) -> url = urlsService.resolve("epics") diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index da3ac97b..f794f480 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -33,7 +33,7 @@ script(type="text/ng-template", id="search-epics") div.ref(tg-bo-ref="epic.ref") div.user-stories div.user-story-name - a(href="", tg-nav="project-epic-detail:project=project.slug,ref=epic.ref", + a(href="", tg-nav="project-epics-detail:project=project.slug,ref=epic.ref", tg-bo-bind="epic.subject") div.status(tg-listitem-epic-status="epic") From 866c576e9efdeb75d886d8e8f514d0002bfd4c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 4 Aug 2016 11:34:17 +0200 Subject: [PATCH 028/137] Add some confirm messages --- app/coffee/modules/backlog/main.coffee | 31 +++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 831d24cd..c742d74f 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -117,11 +117,13 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @scope.$on "usform:bulk:success", => @.loadUserstories(true) @.loadProjectStats() + @confirm.notify("success") @analytics.trackEvent("userstory", "create", "bulk create userstory on backlog", 1) @scope.$on "sprintform:create:success", => @.loadSprints() @.loadProjectStats() + @confirm.notify("success") @analytics.trackEvent("sprint", "create", "create sprint on backlog", 1) @scope.$on "usform:new:success", => @@ -129,6 +131,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @.loadProjectStats() @rootscope.$broadcast("filters:update") + @confirm.notify("success") @analytics.trackEvent("userstory", "create", "create userstory on backlog", 1) @scope.$on "sprintform:edit:success", => @@ -331,14 +334,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]}) # --move us api behavior-- - # if your are moving multiples USs you must use the bulk api - # if there is only one US you must use patch (repo.save) - # the new US position is the position of the previous US + 1 - # if the previous US has a position value that it is equal to + # If your are moving multiples USs you must use the bulk api + # If there is only one US you must use patch (repo.save) + # + # The new US position is the position of the previous US + 1. + # If the previous US has a position value that it is equal to # other USs, you must send all the USs with that position value # only if they are before of the target position with this USs # if it's a patch you must add them to the header, if is a bulk - # you must send them with the other USs. + # you must send them with the other USs moveUs: (ctx, usList, newUsIndex, newSprintId) -> oldSprintId = usList[0].milestone project = usList[0].project @@ -408,17 +412,18 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F else if previous startIndex = orderList[previous.id] + 1 - previousWithTheSameOrder = _.filter beforeDestination, (it) -> - return it[orderField] == orderList[previous.id] + previousWithTheSameOrder = _.filter(beforeDestination, (it) -> + it[orderField] == orderList[previous.id] + ) - # we must send the USs previous to the dropped USs to - # tell the backend which USs are before the dropped - # USs, if they have the same value to order, the backend - # doens't know after which one do you want to drop + # we must send the USs previous to the dropped USs to tell the backend + # which USs are before the dropped USs, if they have the same value to + # order, the backend doens't know after which one do you want to drop # the USs if previousWithTheSameOrder.length > 1 - setPreviousOrders = _.map previousWithTheSameOrder, (it) -> - return {us_id: it.id, order: orderList[it.id]} + setPreviousOrders = _.map(previousWithTheSameOrder, (it) -> + {us_id: it.id, order: orderList[it.id]} + ) modifiedUs = [] From 479d6f02e6bbe5e2c67a110c9576af6599811daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 4 Aug 2016 13:29:34 +0200 Subject: [PATCH 029/137] Use milestone_id instead of sprint_id in the API --- app/coffee/modules/resources/tasks.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index ba27fec7..4c47ad6e 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -62,7 +62,7 @@ resourceProvider = ($repo, $http, $urls, $storage) -> service.bulkCreate = (projectId, sprintId, usId, data) -> url = $urls.resolve("bulk-create-tasks") - params = {project_id: projectId, sprint_id: sprintId, us_id: usId, bulk_tasks: data} + params = {project_id: projectId, milestone_id: sprintId, us_id: usId, bulk_tasks: data} return $http.post(url, params).then (result) -> return result.data From 9ed2e3abf2f89f30d043c5d08e5df5fc6683c0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 4 Aug 2016 17:59:51 +0200 Subject: [PATCH 030/137] Tests EPICS dashboard --- .../project-menu.controller.spec.coffee | 7 +- .../epic-row/epic-row.controller.coffee | 27 +- .../epic-row/epic-row.controller.spec.coffee | 240 ++++++++++++++++++ .../epic-row/epic-row.directive.coffee | 4 + .../epics/dashboard/epic-row/epic-row.jade | 7 +- .../epics/dashboard/epic-row/epic-row.scss | 4 +- .../epics-dashboard.controller.coffee | 4 +- .../epics-dashboard.controller.spec.coffee | 142 +++++++++++ .../epics/dashboard/epics-dashboard.jade | 2 +- .../epics-table/epics-table.controller.coffee | 1 - .../epics-table.controller.spec.coffee | 64 +++++ .../epics-table/epics-table.directive.coffee | 4 + app/modules/home/home.service.spec.coffee | 2 +- 13 files changed, 482 insertions(+), 26 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee diff --git a/app/modules/components/project-menu/project-menu.controller.spec.coffee b/app/modules/components/project-menu/project-menu.controller.spec.coffee index 2e3e1829..65601c88 100644 --- a/app/modules/components/project-menu/project-menu.controller.spec.coffee +++ b/app/modules/components/project-menu/project-menu.controller.spec.coffee @@ -111,6 +111,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: false, backlog: false, kanban: false, issues: false, @@ -119,11 +120,12 @@ describe "ProjectMenu", -> it "all options enabled", () -> project = Immutable.fromJS({ + is_epics_activated: true, is_backlog_activated: true, is_kanban_activated: true, is_issues_activated: true, is_wiki_activated: true, - my_permissions: ["view_us", "view_issues", "view_wiki_pages"] + my_permissions: ["view_epics", "view_us", "view_issues", "view_wiki_pages"] }) mocks.projectService.project = project @@ -136,6 +138,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: true, backlog: true, kanban: true, issues: true, @@ -144,6 +147,7 @@ describe "ProjectMenu", -> it "all options disabled because the user doesn't have permissions", () -> project = Immutable.fromJS({ + is_epics_activated: true, is_backlog_activated: true, is_kanban_activated: true, is_issues_activated: true, @@ -161,6 +165,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: false, backlog: false, kanban: false, issues: false, 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 ae0d0cc0..33c30bf4 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -27,40 +27,40 @@ class EpicRowController constructor: (@rs, @confirm) -> @.displayUserStories = false - @._calculateProgressBar() @.displayAssignedTo = false + @.loadingStatus = false _calculateProgressBar: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" else - opened = @.epic.getIn(['user_stories_counts', 'opened']) - closed = @.epic.getIn(['user_stories_counts', 'closed']) - total = opened + closed - if total == 0 + @.opened = @.epic.getIn(['user_stories_counts', 'opened']) + @.closed = @.epic.getIn(['user_stories_counts', 'closed']) + @.total = @.opened + @.closed + if @.total == 0 @.percentage = "0%" else - @.percentage = "#{closed * 100 / total}%" + @.percentage = "#{@.closed * 100 / @.total}%" updateEpicStatus: (status) -> - id = @.epic.get('id') - version = @.epic.get('version') + @.loadingStatus = true + @.displayStatusList = false patch = { 'status': status, - 'version': version + 'version': @.epic.get('version') } onSuccess = => + @.loadingStatus = false @.onUpdateEpic() onError = (data) => @confirm.notify('error') - return @rs.epics.patch(id, patch).then(onSuccess, onError) + return @rs.epics.patch(@.epic.get('id'), patch).then(onSuccess, onError) requestUserStories: (epic) -> - if @.displayUserStories == false - id = @.epic.get('id') + if !@.displayUserStories onSuccess = (data) => @.epicStories = data @@ -69,7 +69,7 @@ class EpicRowController onError = (data) => @confirm.notify('error') - return @rs.userstories.listInEpic(id).then(onSuccess, onError) + return @rs.userstories.listInEpic(@.epic.get('id')).then(onSuccess, onError) else @.displayUserStories = false @@ -83,7 +83,6 @@ class EpicRowController onSuccess = => @.onUpdateEpic() - @confirm.notify('success') onError = (data) => @confirm.notify('error') diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee new file mode 100644 index 00000000..6205a6df --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -0,0 +1,240 @@ +### +# Copyright (C) 2014-2015 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: epic-row.controller.spec.coffee +### + +describe "EpicRow", -> + epicRowCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + patch: sinon.stub() + }, + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgConfirm() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = false + EpicRowCtrl.displayAssignedTo = false + EpicRowCtrl.loadingStatus = false + + it "calculate progress bar in open US", () -> + + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 10, + closed: 10 + } + }) + + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.opened).to.be.equal(10) + expect(EpicRowCtrl.closed).to.be.equal(10) + expect(EpicRowCtrl.total).to.be.equal(20) + expect(EpicRowCtrl.percentage).to.be.equal("50%") + + it "calculate progress bar in zero US", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 0, + closed: 0 + } + }) + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.opened).to.be.equal(0) + expect(EpicRowCtrl.closed).to.be.equal(0) + expect(EpicRowCtrl.total).to.be.equal(0) + expect(EpicRowCtrl.percentage).to.be.equal("0%") + + it "calculate progress bar in zero US", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: true + } + }) + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.percentage).to.be.equal("100%") + + it "Update Epic Status Success", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + + EpicRowCtrl.patch = { + 'status': 'new', + 'version': EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.loadingStatus = true + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + + status = "new" + EpicRowCtrl.updateEpicStatus(status).then () -> + expect(EpicRowCtrl.loadingStatus).to.be.false + expect(EpicRowCtrl.displayStatusList).to.be.false + expect(EpicRowCtrl.onUpdateEpic).to.be.called + done() + + it "Update Epic Status Error", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + + EpicRowCtrl.patch = { + 'status': 'new', + 'version': EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.loadingStatus = true + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().reject(new Error('error')) + + status = "new" + EpicRowCtrl.updateEpicStatus(status).then () -> + expect(mocks.tgConfirm.notify).have.been.calledWith('error') + done() + + it "display User Stories", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.displayUserStories = false + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + data = true + + promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().resolve(data) + + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + expect(EpicRowCtrl.displayUserStories).to.be.true + expect(EpicRowCtrl.epicStories).is.equal(data) + done() + + it "display User Stories error", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = false + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + + promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().reject(new Error('error')) + + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + expect(mocks.tgConfirm.notify).have.been.calledWith('error') + done() + + it "DO NOT display User Stories", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = true + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic) + expect(EpicRowCtrl.displayUserStories).to.be.false + + it "On remove assigned", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + EpicRowCtrl.patch = { + 'assigned_to': null, + 'version': EpicRowCtrl.epic.get('version') + } + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + + EpicRowCtrl.onRemoveAssigned().then () -> + expect(EpicRowCtrl.onUpdateEpic).to.have.been.called + + it "On assign to", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + id = EpicRowCtrl.epic.get('id') + version = EpicRowCtrl.epic.get('version') + member = { + id: 1 + } + EpicRowCtrl.patch = { + assigned_to: member.id + version: EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(id, EpicRowCtrl.patch).promise().resolve(member) + EpicRowCtrl.onAssignTo(member).then () -> + expect(EpicRowCtrl.onUpdateEpic).to.have.been.called + expect(mocks.tgConfirm.notify).have.been.calledWith('success') + done() 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 ecf94d4d..70fbb8e3 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -21,7 +21,11 @@ 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", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c6ff107a..1644ab12 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -44,18 +44,19 @@ span {{vm.epic.getIn(['status_extra_info', 'name'])}} .status( ng-if="vm.column.status && vm.permissions.canEdit" - ng-mouseleave="displayStatusList = false" + ng-mouseleave="vm.displayStatusList = false" ) button( - ng-click="displayStatusList = true" + ng-click="vm.displayStatusList = true" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" + tg-loading="vm.loadingStatus" ) span {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) - ul.epic-statuses(ng-show="displayStatusList") + ul.epic-statuses(ng-if="vm.displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" ng-click="vm.updateEpicStatus(status.id)" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 7760f8e0..3cf55907 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -19,8 +19,8 @@ background: rgba($red-light, .5); } &.is-closed { - .name { - color: $gray-light; + .name a { + color: lighten($gray-light, 15%); text-decoration: line-through; } } diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 9e2e6a36..26c2edbf 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -32,10 +32,9 @@ class EpicsDashboardController constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> @.sectionName = "Epics" - @._loadProject() @.createEpic = false - _loadProject: () -> + loadProject: () -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_epics_activated @errorHandlingService.permissionDenied() @@ -43,7 +42,6 @@ class EpicsDashboardController @.loadEpics() loadEpics: () -> - console.log 'reload' projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee new file mode 100644 index 00000000..12b98e64 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -0,0 +1,142 @@ +### +# Copyright (C) 2014-2015 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: epic-row.controller.spec.coffee +### + +describe "EpicsDashboard", -> + EpicsDashboardCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + projects: { + getBySlug: sinon.stub() + } + } + + provide.value "$tgResources", mocks.tgResources + + _mockTgResourcesNew = () -> + mocks.tgResourcesNew = { + epics: { + list: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResourcesNew + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockRouteParams = () -> + mocks.routeparams = { + pslug: sinon.stub() + } + + provide.value "$routeParams", mocks.routeparams + + _mockTgErrorHandlingService = () -> + mocks.tgErrorHandlingService = { + permissionDenied: sinon.stub() + } + + provide.value "tgErrorHandlingService", mocks.tgErrorHandlingService + + _mockTgLightboxFactory = () -> + mocks.tgLightboxFactory = { + create: sinon.stub() + } + + provide.value "tgLightboxFactory", mocks.tgLightboxFactory + + _mockLightboxService = () -> + mocks.lightboxService = { + closeAll: sinon.stub() + } + + provide.value "lightboxService", mocks.lightboxService + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgResourcesNew() + _mockRouteParams() + _mockTgErrorHandlingService() + _mockTgLightboxFactory() + _mockLightboxService() + _mockTgConfirm() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + + it "load projects", (done) -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + params = mocks.routeparams.pslug + EpicsDashboardCtrl.loadEpics = sinon.stub() + project = { + is_epics_activated: false + } + promise = mocks.tgResources.projects.getBySlug.withArgs(params).promise().resolve(project) + EpicsDashboardCtrl.loadProject().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).have.been.called + expect(EpicsDashboardCtrl.project).is.equal(project) + expect(EpicsDashboardCtrl.loadEpics).have.been.called + done() + + it "load epics", (done) -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + EpicsDashboardCtrl.project = { + id: 1 + } + epics = { + id: 1 + } + promise = mocks.tgResourcesNew.epics.list.withArgs(EpicsDashboardCtrl.project.id).promise().resolve(epics) + EpicsDashboardCtrl.loadEpics().then () -> + expect(EpicsDashboardCtrl.epics).is.equal(epics) + done() + + it "on create epic", () -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + EpicsDashboardCtrl.loadEpics = sinon.stub() + EpicsDashboardCtrl._onCreateEpic() + expect(mocks.lightboxService.closeAll).have.been.called + expect(mocks.tgConfirm.notify).have.been.calledWith("success") + expect(EpicsDashboardCtrl.loadEpics).have.been.called diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 50abb268..dd9f88ee 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,4 +1,4 @@ -.wrapper() +.wrapper(ng-init="vm.loadProject()") tg-project-menu section.main(role="main") header.header-with-actions 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 6f02f06f..ecc9ae69 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -34,7 +34,6 @@ class EpicsTableController status: true, progress: true } - @._checkPermissions() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee new file mode 100644 index 00000000..95f19644 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -0,0 +1,64 @@ +### +# Copyright (C) 2014-2015 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: epic-row.controller.spec.coffee +### + +describe "EpicTable", -> + epicTableCtrl = null + provide = null + controller = null + mocks = {} + + _mocks = () -> + module ($provide) -> + provide = $provide + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "toggle table options", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.displayOptions = true + epicTableCtrl.toggleEpicTableOptions() + expect(epicTableCtrl.displayOptions).to.be.false + + it "can edit", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.project = { + my_permissions: [ + 'modify_epic' + ] + } + epicTableCtrl._checkPermissions() + expect(epicTableCtrl.permissions.canEdit).to.be.true + + it "can NOT edit", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.project = { + my_permissions: [ + 'modify_us' + ] + } + epicTableCtrl._checkPermissions() + expect(epicTableCtrl.permissions.canEdit).to.be.false 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 6d89296e..39827f77 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -21,7 +21,11 @@ module = angular.module('taigaEpics') EpicsTableDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl._checkPermissions() + return { + link: link, templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 000477e9..a547feb8 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -72,7 +72,7 @@ describe "tgHome", -> _setup() _inject() - it.only "get work in progress by user", (done) -> + it "get work in progress by user", (done) -> userId = 3 project1 = {id: 1, name: "fake1", slug: "project-1"} From 0816e134bf5b3e1015664143acf4183ef2d44c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 11:09:38 +0200 Subject: [PATCH 031/137] Fix working on tests --- .../working-on.controller.spec.coffee | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/app/modules/home/working-on/working-on.controller.spec.coffee b/app/modules/home/working-on/working-on.controller.spec.coffee index 39d4c1e5..d255ab18 100644 --- a/app/modules/home/working-on/working-on.controller.spec.coffee +++ b/app/modules/home/working-on/working-on.controller.spec.coffee @@ -51,14 +51,32 @@ describe "WorkingOn", -> workInProgress = Immutable.fromJS({ assignedTo: { - userStories: [{id: 1, modified_date: "2015-01-01"}, {id: 2, modified_date: "2015-01-04"}], - tasks: [{id: 3, modified_date: "2015-01-02"}, {id: 4, modified_date: "2015-01-05"}], - issues: [{id: 5, modified_date: "2015-01-03"}, {id: 6, modified_date: "2015-01-06"}] + epics: [ + {id: 7, modified_date: "2015-01-08"}, + {id: 8, modified_date: "2015-01-07"}], + userStories: [ + {id: 1, modified_date: "2015-01-01"}, + {id: 2, modified_date: "2015-01-04"}], + tasks: [ + {id: 3, modified_date: "2015-01-02"}, + {id: 4, modified_date: "2015-01-05"}], + issues: [ + {id: 5, modified_date: "2015-01-03"}, + {id: 6, modified_date: "2015-01-06"}] }, watching: { - userStories: [{id: 7, modified_date: "2015-01-01"}, {id: 8, modified_date: "2015-01-04"}], - tasks: [{id: 9, modified_date: "2015-01-02"}, {id: 10, modified_date: "2015-01-05"}], - issues: [{id: 11, modified_date: "2015-01-03"}, {id: 12, modified_date: "2015-01-06"}] + epics: [ + {id: 13, modified_date: "2015-01-07"}, + {id: 14, modified_date: "2015-01-08"}], + userStories: [ + {id: 7, modified_date: "2015-01-01"}, + {id: 8, modified_date: "2015-01-04"}], + tasks: [ + {id: 9, modified_date: "2015-01-02"}, + {id: 10, modified_date: "2015-01-05"}], + issues: [ + {id: 11, modified_date: "2015-01-03"}, + {id: 12, modified_date: "2015-01-06"}] } }) @@ -68,6 +86,8 @@ describe "WorkingOn", -> ctrl.getWorkInProgress(userId).then () -> expect(ctrl.assignedTo.toJS()).to.be.eql([ + {id: 7, modified_date: '2015-01-08'}, + {id: 8, modified_date: '2015-01-07'}, {id: 6, modified_date: '2015-01-06'}, {id: 4, modified_date: '2015-01-05'}, {id: 2, modified_date: '2015-01-04'}, @@ -77,6 +97,8 @@ describe "WorkingOn", -> ]) expect(ctrl.watching.toJS()).to.be.eql([ + {id: 14, modified_date: '2015-01-08'}, + {id: 13, modified_date: '2015-01-07'}, {id: 12, modified_date: '2015-01-06'}, {id: 10, modified_date: '2015-01-05'}, {id: 8, modified_date: '2015-01-04'}, From 1a42030e80562a737a263c18a38e6dd0c69fdb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 12:29:04 +0200 Subject: [PATCH 032/137] Belong to epic component --- .../belong-to-epics.directive.coffee | 33 +++++++++++++++++++ .../belong-to-epics/belong-to-epics.jade | 4 +++ .../belong-to-epics/belong-to-epics.scss | 30 +++++++++++++++++ .../epics/dashboard/story-row/story-row.jade | 7 ++-- .../epics/dashboard/story-row/story-row.scss | 30 ----------------- 5 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.directive.coffee create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.jade create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.scss diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee new file mode 100644 index 00000000..0752ef40 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -0,0 +1,33 @@ +### +# 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: belong-to-epics.directive.coffee +### + +module = angular.module('taigaEpics') + +BelongToEpicsDirective = () -> + + return { + templateUrl:"components/belong-to-epics/belong-to-epics.html", + scope: { + epics: '=' + } + } + +BelongToEpicsDirective.$inject = [] + +module.directive("tgBelongToEpics", BelongToEpicsDirective) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics.jade new file mode 100644 index 00000000..d492581e --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.jade @@ -0,0 +1,4 @@ +- var hash = "#"; +.belong-to-epic-pill-wrapper(tg-repeat="epic in epics") + .belong-to-epic-pill(ng-style="{'background': epic.get('color')}") + .belong-to-epic-pill-data #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss new file mode 100644 index 00000000..65ad4f2c --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -0,0 +1,30 @@ +.belong-to-epic-pill-wrapper { + display: inline-block; + position: relative; + &:hover { + .belong-to-epic-pill-data { + display: block; + } + } +} +.belong-to-epic-pill { + background-color: $grayer; + border-radius: 50%; + display: inline-block; + height: .75rem; + margin-left: .25rem; + position: relative; + width: .75rem; +} +.belong-to-epic-pill-data { + animation: dropdownFade .2s; + background: rgba($black, .9); + bottom: 1.25rem; + color: $white; + display: none; + left: -100px; + padding: .5rem 1rem; + position: absolute; + width: 200px; + z-index: 99; +} diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index ba9580ef..4010abde 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -14,9 +14,10 @@ tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} - .story-pill-wrapper(tg-repeat="pill in vm.story.get('epics')") - .story-pill(ng-style="{'background': pill.get('color')}") - .story-pill-data #{hash}{{pill.get('id')}} {{pill.get('subject')}} + tg-belong-to-epics( + ng-if="vm.story.get('epics')" + epics="vm.story.get('epics')" + ) .project( ng-if="vm.column.project" tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index ad742a2a..51aa9fcd 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -25,36 +25,6 @@ .name { flex-basis: 17.5vw; } - .story-pill-wrapper { - display: inline-block; - position: relative; - &:hover { - .story-pill-data { - display: block; - } - } - } - .story-pill { - background-color: $grayer; - border-radius: 50%; - display: inline-block; - height: .75rem; - margin-left: .25rem; - position: relative; - width: .75rem; - } - .story-pill-data { - animation: dropdownFade .2s; - background: rgba($black, .9); - bottom: 1.25rem; - color: $white; - display: none; - left: -100px; - padding: .5rem 1rem; - position: absolute; - width: 200px; - z-index: 99; - } .progress-bar, .progress-status { height: 1.5rem; From 93645cee8cdc2897739c277e142ded4f0b38d321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 16:22:52 +0200 Subject: [PATCH 033/137] Display epics in backlog and sprints --- .../belong-to-epics.directive.coffee | 5 +++++ .../includes/components/backlog-row.jade | 18 +++++++++++------- app/partials/includes/modules/sprint.jade | 4 ++++ app/styles/modules/backlog/backlog-table.scss | 2 +- app/styles/modules/backlog/sprints.scss | 3 --- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 0752ef40..ea4485ed 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -21,7 +21,12 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> + link = (scope, el, attrs) -> + if !scope.epics.isIterable + scope.epics = Immutable.fromJS(scope.epics) + return { + link: link, templateUrl:"components/belong-to-epics/belong-to-epics.html", scope: { epics: '=' diff --git a/app/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index 0ed659b9..a569ad99 100644 --- a/app/partials/includes/components/backlog-row.jade +++ b/app/partials/includes/components/backlog-row.jade @@ -1,23 +1,23 @@ -div.row.us-item-row( +.row.us-item-row( ng-repeat="us in userstories track by us.id" tg-bind-scope ng-class="{blocked: us.is_blocked}" tg-class-permission="{'readonly': '!modify_us'}" ) - div.input(tg-check-permission="modify_us") + .input(tg-check-permission="modify_us") input( type="checkbox" name="" ) - div.votes( + .votes( ng-class="{'inactive': !us.total_voters, 'is-voted': us.is_voter}" title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:us.total_voters||0}:'messageformat' }}" ) tg-svg(svg-icon="icon-upvote") span {{ ::us.total_voters }} - div.user-stories - div.tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog") - div.user-story-name + .user-stories + .tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog") + .user-story-name a.clickable( href="" tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" @@ -26,7 +26,11 @@ div.row.us-item-row( ) span(tg-bo-ref="us.ref") span(ng-bind="us.subject") - div.us-settings + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) + .us-settings a.e2e-edit.edit-story( href="" tg-check-permission="modify_us" diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index d66c5ca7..373ff370 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -19,6 +19,10 @@ div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_ ng-class="{closed: us.is_closed, blocked: us.is_blocked}") span(tg-bo-ref="us.ref") span(tg-bo-bind="us.subject") + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) div.column-points.width-1(tg-bo-bind="us.total_points", ng-class="{closed: us.is_closed, blocked: us.is_blocked}") diff --git a/app/styles/modules/backlog/backlog-table.scss b/app/styles/modules/backlog/backlog-table.scss index 99f22b12..a7124d28 100644 --- a/app/styles/modules/backlog/backlog-table.scss +++ b/app/styles/modules/backlog/backlog-table.scss @@ -28,7 +28,7 @@ flex-shrink: 0; } .user-stories { - overflow: hidden; + // overflow: hidden; width: 100%; } .status { diff --git a/app/styles/modules/backlog/sprints.scss b/app/styles/modules/backlog/sprints.scss index ae95465b..82deb18a 100644 --- a/app/styles/modules/backlog/sprints.scss +++ b/app/styles/modules/backlog/sprints.scss @@ -47,14 +47,12 @@ a { @include font-size(normal); @include font-type(text); - @include ellipsis($width: 90%); display: inline-block; margin-right: .5rem; } } .sprint { margin-bottom: 2rem; - overflow: hidden; header { position: relative; } @@ -182,7 +180,6 @@ padding: 0 4px; } .us-name { - @include ellipsis(230px); display: block; &.closed { color: lighten($gray-light, 5%); From fcecb72b40401a45fc6b5d6cf4044a36b2736a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 9 Aug 2016 08:59:22 +0200 Subject: [PATCH 034/137] Epics in taskboard and kanban --- .../belong-to-epics/belong-to-epics.jade | 6 ++++-- .../belong-to-epics/belong-to-epics.scss | 17 ++--------------- .../card/card-templates/card-title.jade | 4 ++++ .../includes/modules/taskboard-table.jade | 4 ++++ 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics.jade index d492581e..0bcdb61a 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics.jade @@ -1,4 +1,6 @@ - var hash = "#"; .belong-to-epic-pill-wrapper(tg-repeat="epic in epics") - .belong-to-epic-pill(ng-style="{'background': epic.get('color')}") - .belong-to-epic-pill-data #{hash}{{epic.get('id')}} {{epic.get('subject')}} + .belong-to-epic-pill( + ng-style="{'background': epic.get('color')}" + title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" + ) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index 65ad4f2c..e2cbde9c 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -11,20 +11,7 @@ background-color: $grayer; border-radius: 50%; display: inline-block; - height: .75rem; - margin-left: .25rem; + height: .7rem; position: relative; - width: .75rem; -} -.belong-to-epic-pill-data { - animation: dropdownFade .2s; - background: rgba($black, .9); - bottom: 1.25rem; - color: $white; - display: none; - left: -100px; - padding: .5rem 1rem; - position: absolute; - width: 200px; - z-index: 99; + width: .7rem; } diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade index ad5434d8..76783ecf 100644 --- a/app/modules/components/card/card-templates/card-title.jade +++ b/app/modules/components/card/card-templates/card-title.jade @@ -7,3 +7,7 @@ h2.card-title ) span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}} span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}} + tg-belong-to-epics( + ng-if="vm.item.getIn(['model', 'epics'])" + epics="vm.item.getIn(['model', 'epics'])" + ) diff --git a/app/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 3ad5325b..91950ac1 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -54,6 +54,10 @@ div.taskboard-table( tg-bo-title="'#' + us.ref + ' ' + us.subject") span.us-ref(tg-bo-ref="us.ref") span(ng-bind="us.subject") + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) p.points-value span(ng-bind="us.total_points") span(translate="TASKBOARD.TABLE.FIELD_POINTS") From 8e901b0067208302e43ad54e8ce24c125b082c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 9 Aug 2016 13:58:04 +0200 Subject: [PATCH 035/137] belong to epic multitemplate --- ...to-epics.jade => belong-to-epics-pill.jade} | 2 +- .../belong-to-epics/belong-to-epics-text.jade | 10 ++++++++++ .../belong-to-epics.directive.coffee | 12 +++++++++--- .../belong-to-epics/belong-to-epics.scss | 9 +++++++++ .../card/card-templates/card-title.jade | 1 + .../epics/dashboard/story-row/story-row.jade | 1 + .../includes/components/backlog-row.jade | 1 + app/partials/includes/modules/sprint.jade | 1 + .../includes/modules/taskboard-table.jade | 1 + app/partials/us/us-detail.jade | 18 +++++++++++++++--- app/styles/layout/ticket-detail.scss | 8 ++++++++ 11 files changed, 57 insertions(+), 7 deletions(-) rename app/modules/components/belong-to-epics/{belong-to-epics.jade => belong-to-epics-pill.jade} (67%) create mode 100644 app/modules/components/belong-to-epics/belong-to-epics-text.jade diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade similarity index 67% rename from app/modules/components/belong-to-epics/belong-to-epics.jade rename to app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 0bcdb61a..61670efa 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,5 +1,5 @@ - var hash = "#"; -.belong-to-epic-pill-wrapper(tg-repeat="epic in epics") +.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( ng-style="{'background': epic.get('color')}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade new file mode 100644 index 00000000..f8b935d0 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -0,0 +1,10 @@ +- var hash = "#"; +span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id')") + .belong-to-epic-pill( + ng-style="{'background': epic.get('color')}" + title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" + ) + a.belong-to-epic-text( + href="" + tg-nav="project-epics-detail:project=vm.project.get('slug')" + ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index ea4485ed..455adc1d 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -22,15 +22,21 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> link = (scope, el, attrs) -> - if !scope.epics.isIterable + if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) + if scope.project && !scope.project.isIterable + scope.project = Immutable.fromJS(scope.project) + + scope.getTemplateUrl = () -> + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return { link: link, - templateUrl:"components/belong-to-epics/belong-to-epics.html", scope: { epics: '=' - } + }, + template : '' } BelongToEpicsDirective.$inject = [] diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e2cbde9c..fd487f3c 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -7,6 +7,7 @@ } } } + .belong-to-epic-pill { background-color: $grayer; border-radius: 50%; @@ -15,3 +16,11 @@ position: relative; width: .7rem; } + +.belong-to-epic-text-wrapper { + margin-right: 1rem; +} + +.belong-to-epic-text { + margin-left: .25rem; +} diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade index 76783ecf..94df8825 100644 --- a/app/modules/components/card/card-templates/card-title.jade +++ b/app/modules/components/card/card-templates/card-title.jade @@ -8,6 +8,7 @@ h2.card-title span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}} span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}} tg-belong-to-epics( + format="pill" ng-if="vm.item.getIn(['model', 'epics'])" epics="vm.item.getIn(['model', 'epics'])" ) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 4010abde..d421ca0c 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -15,6 +15,7 @@ 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')" epics="vm.story.get('epics')" ) diff --git a/app/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index a569ad99..4a3a6143 100644 --- a/app/partials/includes/components/backlog-row.jade +++ b/app/partials/includes/components/backlog-row.jade @@ -27,6 +27,7 @@ span(tg-bo-ref="us.ref") span(ng-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index 373ff370..f85bc3d6 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -20,6 +20,7 @@ div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_ span(tg-bo-ref="us.ref") span(tg-bo-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 91950ac1..1b7bad37 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -55,6 +55,7 @@ div.taskboard-table( span.us-ref(tg-bo-ref="us.ref") span(ng-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 8baf2bec..eb3a0b5f 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -29,10 +29,22 @@ div.wrapper( div.us-title(ng-class="{blocked: us.is_blocked}") h2.us-title-text span.us-number(tg-bo-ref="us.ref") - span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us") + span.us-name( + tg-editable-subject + ng-model="us" + required-perm="modify_us" + ) + + p.belong-to-epics-wrapper(ng-if="us.epics") + span This User Story belongs to + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + format="text" + project="project" + ) - p.us-related-task(ng-if="us.origin_issue") - | {{ 'US.PROMOTED'|translate }} + p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} a( href="" tg-check-permission="view_us" diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 7021eca6..5daa3f6a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -142,6 +142,14 @@ margin-right: 5rem; } } + .belong-to-epics-wrapper { + @include font-size(small); + color: $gray-light; + margin-top: .5rem; + a:hover { + color: $primary; + } + } .loading-spinner { @include loading-spinner; max-height: 1.5rem; From 58a19f1425a854e38e2a7ec2207021641893646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 11 Aug 2016 16:51:50 +0200 Subject: [PATCH 036/137] Header detail WIP --- app/coffee/modules/common/components.coffee | 168 ++++++------ .../belong-to-epics/belong-to-epics.scss | 1 + .../header/story-header.controller.coffee | 67 +++++ .../header/story-header.directive.coffee | 42 +++ app/modules/stories/header/story-header.jade | 76 ++++++ app/modules/stories/header/story-header.scss | 251 ++++++++++++++++++ app/partials/us/us-detail.jade | 98 ++++--- app/styles/layout/ticket-detail.scss | 180 ------------- 8 files changed, 567 insertions(+), 316 deletions(-) create mode 100644 app/modules/stories/header/story-header.controller.coffee create mode 100644 app/modules/stories/header/story-header.directive.coffee create mode 100644 app/modules/stories/header/story-header.jade create mode 100644 app/modules/stories/header/story-header.scss diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a6f4fffb..d1c0cea5 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -493,90 +493,90 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) -> module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective]) -############################################################################# -## Editable subject directive -############################################################################# - -EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> - template = $template.get("common/components/editable-subject.html") - - link = ($scope, $el, $attrs, $model) -> - - $scope.$on "object:updated", () -> - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - isEditable = -> - return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - - save = (subject) -> - currentLoading = $loading() - .target($el.find('.save-container')) - .start() - - transform = $modelTransform.save (item) -> - item.subject = subject - - return item - - transform.then => - $confirm.notify("success") - $rootscope.$broadcast("object:updated") - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - transform.then null, -> - $confirm.notify("error") - - transform.finally -> - currentLoading.finish() - - return transform - - $el.click -> - return if not isEditable() - $el.find('.edit-subject').show() - $el.find('.view-subject').hide() - $el.find('input').focus() - - $el.on "click", ".save", (e) -> - e.preventDefault() - - subject = $scope.item.subject - save(subject) - - $el.on "keyup", "input", (event) -> - if event.keyCode == 13 - subject = $scope.item.subject - save(subject) - else if event.keyCode == 27 - $scope.$apply () => $model.$modelValue.revert() - - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - $el.find('.edit-subject').hide() - - $scope.$watch $attrs.ngModel, (value) -> - return if not value - $scope.item = value - - if not isEditable() - $el.find('.view-subject .edit').remove() - - $scope.$on "$destroy", -> - $el.off() - - - return { - link: link - restrict: "EA" - require: "ngModel" - template: template - } - -module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", - "$tgTemplate", EditableSubjectDirective]) +# ############################################################################# +# ## Editable subject directive +# ############################################################################# +# +# EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> +# template = $template.get("common/components/editable-subject.html") +# +# link = ($scope, $el, $attrs, $model) -> +# +# $scope.$on "object:updated", () -> +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# isEditable = -> +# return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 +# +# save = (subject) -> +# currentLoading = $loading() +# .target($el.find('.save-container')) +# .start() +# +# transform = $modelTransform.save (item) -> +# item.subject = subject +# +# return item +# +# transform.then => +# $confirm.notify("success") +# $rootscope.$broadcast("object:updated") +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# transform.then null, -> +# $confirm.notify("error") +# +# transform.finally -> +# currentLoading.finish() +# +# return transform +# +# $el.click -> +# return if not isEditable() +# $el.find('.edit-subject').show() +# $el.find('.view-subject').hide() +# $el.find('input').focus() +# +# $el.on "click", ".save", (e) -> +# e.preventDefault() +# +# subject = $scope.item.subject +# save(subject) +# +# $el.on "keyup", "input", (event) -> +# if event.keyCode == 13 +# subject = $scope.item.subject +# save(subject) +# else if event.keyCode == 27 +# $scope.$apply () => $model.$modelValue.revert() +# +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# $el.find('.edit-subject').hide() +# +# $scope.$watch $attrs.ngModel, (value) -> +# return if not value +# $scope.item = value +# +# if not isEditable() +# $el.find('.view-subject .edit').remove() +# +# $scope.$on "$destroy", -> +# $el.off() +# +# +# return { +# link: link +# restrict: "EA" +# require: "ngModel" +# template: template +# } +# +# module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", +# "$tgTemplate", EditableSubjectDirective]) ############################################################################# diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index fd487f3c..e59c4fb0 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -13,6 +13,7 @@ border-radius: 50%; display: inline-block; height: .7rem; + margin: 0 .1rem; position: relative; width: .7rem; } diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee new file mode 100644 index 00000000..7fb78177 --- /dev/null +++ b/app/modules/stories/header/story-header.controller.coffee @@ -0,0 +1,67 @@ +### +# Copyright (C) 2014-2015 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.dashboard.controller.coffee +### + +module = angular.module("taigaUserStories") + +class StoryHeaderController + @.$inject = [ + "$rootScope", + "$tgConfirm", + "$tgQueueModelTransformation" + ] + + constructor: (@rootScope, @confirm, @modelTransform) -> + @.editMode = false + @.loadingSubject = false + @.originalSubject = @.item.subject + + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, @.requiredPerm) + } + + editSubject: (value) -> + if value + @.editMode = true + if !value + @.editMode = false + + onCancelEdition: (event) -> + if event.which == 27 + @.item.subject = @.originalSubject + @.editSubject(false) + + saveSubject: () -> + onEditSubjectSuccess = () => + @.loadingSubject = false + @rootScope.$broadcast("object:updated") + @confirm.notify('success') + + onEditSubjectError = () => + @.loadingSubject = false + @confirm.notify('error') + + @.editMode = false + @.loadingSubject = true + item = @.item + transform = @modelTransform.save (item) -> + return item + return transform.then(onEditSubjectSuccess, onEditSubjectError) + +module.controller("StoryHeaderCtrl", StoryHeaderController) diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/stories/header/story-header.directive.coffee new file mode 100644 index 00000000..58d9088a --- /dev/null +++ b/app/modules/stories/header/story-header.directive.coffee @@ -0,0 +1,42 @@ +### +# 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: story-header.directive.coffee +### + +module = angular.module('taigaUserStories') + +DetailHeaderDirective = () -> + @.$inject = [] + + link = (scope, el, attrs, ctrl) -> + ctrl._checkPermissions() + + return { + link: link, + controller: "StoryHeaderCtrl", + bindToController: true, + scope: { + item: "=", + project: "=", + requiredPerm: "@" + }, + controllerAs: "vm", + templateUrl:"stories/header/story-header.html" + } + + +module.directive("tgDetailHeader", DetailHeaderDirective) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade new file mode 100644 index 00000000..8c42b97f --- /dev/null +++ b/app/modules/stories/header/story-header.jade @@ -0,0 +1,76 @@ +.detail-title-wrapper + h2.detail-title-text.ng-animate-disabled( + ng-show="!vm.editMode" + ng-hide="vm.editMode" + ) + span.detail-number {{'#' + vm.item.ref}} + span.detail-subject( + ng-click="vm.editSubject(true)" + ng-if="vm.permissions.canEdit" + ) {{vm.item.subject}} + span.detail-subject( + ng-if="!vm.permissions.canEdit" + ) {{vm.item.subject}} + tg-svg.detail-edit( + ng-if="vm.permissions.canEdit" + svg-icon="icon-edit" + ng-click="vm.editSubject(true)" + ) + + .edit-title-wrapper(ng-if="vm.editMode") + input.edit-title-input( + type="text" + ng-model="vm.item.subject" + maxlength="500" + autofocus + required + ng-keydown="vm.onCancelEdition($event)" + ) + button.edit-title-button( + ng-click="vm.saveSubject()" + tg-loading="vm.loadingSubject" + ) + tg-svg( + svg-icon="icon-save" + ) +.belong-to-epics-wrapper(ng-if="vm.item.epics") + span This User Story belongs to + tg-belong-to-epics( + ng-if="::vm.item.epics" + epics="::vm.item.epics" + format="text" + project="project" + ) + +.item-origin-issue( + ng-if="vm.item.origin_issue" +) + span(translate="US.PROMOTED") + a( + href="" + tg-check-permission="view_us" + tg-nav="project-issues-detail:project=vm.project.slug,ref=vm.item.origin_issue.ref" + title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + ) + span {{'#' + vm.item.origin_issue.ref}} + span {{vm.item.origin_issue.subject}} + +.block-desc-container(ng-show="vm.item.is_blocked") + span.block-description-title(translate="COMMON.BLOCKED") + span.block-description( + ng-if="vm.item.blocked_note" + ) {{vm.item.blocked_note}} + +.issue-nav + a( + ng-show="previousUrl" + tg-bo-href="previousUrl" + title="{{'US.PREVIOUS' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-left") + a( + ng-show="nextUrl" + tg-bo-href="nextUrl" + title="{{'US.NEXT' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss new file mode 100644 index 00000000..a8d0693e --- /dev/null +++ b/app/modules/stories/header/story-header.scss @@ -0,0 +1,251 @@ +.detail-header-container { + background: $mass-white; + flex: 1; + padding: 1rem; + &:hover { + .detail-edit { + opacity: 1; + } + } + &.blocked { + background: $red; + color: $white; + transition: all .2s linear; + a, + .detail-number, + .detail-subject { + color: $white; + } + svg { + fill: $white; + } + } + .item-origin-issue, + .belong-to-epics-wrapper, + .block-desc-container { + @include font-size(small); + margin-top: .5rem; + } + .item-origin-issue { + a { + padding: 0 .2rem; + } + } +} + +.detail-title-wrapper { + @include font-size(larger); + @include font-type(text); + align-content: center; + display: flex; + position: relative; + transition: all .2s linear; + &.blocked { + background: $red; + transition: all .2s linear; + } + .detail-title-text { + margin: 0; + } + .detail-number { + color: $gray-light; + flex-shrink: 0; + margin-right: .5rem; + } + .detail-subject { + color: $gray; + flex-grow: 1; + } + + .detail-edit { + cursor: pointer; + margin-left: .75rem; + opacity: 0; + transition: opacity .2s; + svg { + @include svg-size(1.25rem); + } + } +} + +.edit-title-wrapper { + @include font-size(larger); + @include font-type(text); + display: flex; + flex: 1; + .edit-title-input { + background: $white; + flex: 1; + } + .edit-title-button { + background: none; + display: inline; + margin-left: 1rem; + transition: fill .2s; + &:hover { + fill: $primary; + } + } +} + +.block-desc-container { + .block-description-title { + @include font-type(bold); + margin-right: .5rem; + + } +} + +.issue-nav { + position: absolute; + right: 1rem; + top: 1rem; + a { + display: inline-block; + } + svg { + @include svg-size(1.2rem); + fill: currentColor; + } +} + +// +// .us-title { +// +// &.blocked { +// background: $red; +// transition: all .2s linear; +// vertical-align: middle; +// .us-title-text, +// input { +// margin-bottom: .5rem; +// } +// .us-number, +// .us-name, +// .us-related-task { +// color: $white; +// } +// a { +// color: $white; +// transition: color .3s linear; +// } +// a:hover { +// color: $red-light; +// } +// .unblock { +// @include font-type(bold); +// color: $white; +// float: right; +// } +// .unblock:hover { +// color: $red-light; +// transition: color .3s linear; +// } +// } +// p { +// margin-bottom: 0; +// } +// .us-edit-name-inner { +// display: flex; +// } +// .edit-subject { +// align-content: center; +// align-items: center; +// display: flex; +// width: 100%; +// } +// input { +// background: $white; +// flex-grow: 1; +// } +// .save-container { +// flex-grow: 1; +// .save { +// display: block; +// } +// } +// .us-title-text { +// @include font-size(larger); +// @include font-type(text); +// align-content: center; +// align-items: center; +// display: flex; +// flex: 1; +// margin-bottom: 0; +// max-width: 92%; +// width: 100%; +// } +// .us-title-text:hover { +// .edit { +// opacity: 1; +// transition: opacity .3s linear; +// } +// } +// .us-number { +// @include font-type(text); +// color: $gray-light; +// flex-shrink: 0; +// line-height: 2.2rem; +// margin-right: .5rem; +// } +// .us-name { +// color: $gray; +// display: inline-block; +// flex-grow: 1; +// line-height: 2.2rem; +// padding-right: 1rem; +// width: 100%; +// } +// .save, +// .edit { +// cursor: pointer; +// margin-left: .5rem; +// svg { +// fill: $gray-light; +// } +// } +// .edit { +// opacity: 0; +// } +// .us-related-task { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a { +// border-left: 1px solid $gray-light; +// padding: 0 .2rem; +// } +// a:hover { +// color: $primary; +// } +// a:first-child { +// border: 0; +// } +// } +// .block-desc-container { +// @include font-size(small); +// } +// .block-description-title { +// @include font-type(bold); +// color: $white; +// margin-right: .5rem; +// } +// .block-description { +// color: $white; +// display: inline-block; +// margin-right: 5rem; +// } +// } +// .belong-to-epics-wrapper { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a:hover { +// color: $primary; +// } +// } +// .loading-spinner { +// @include loading-spinner; +// max-height: 1.5rem; +// max-width: 1.5rem; +// } diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index eb3a0b5f..fbf4fd23 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -26,59 +26,53 @@ div.wrapper( on-upvote="ctrl.onUpvote" on-downvote="ctrl.onDownvote" ) - div.us-title(ng-class="{blocked: us.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="us.ref") - span.us-name( - tg-editable-subject - ng-model="us" - required-perm="modify_us" - ) - - p.belong-to-epics-wrapper(ng-if="us.epics") - span This User Story belongs to - tg-belong-to-epics( - ng-if="us.epics" - epics="us.epics" - format="text" - project="project" - ) + tg-detail-header.detail-header-container( + item="us" + project="project" + required-perm="modify_us" + ng-class="{blocked: us.is_blocked}" + ng-if="project && us" + ) - p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} - a( - href="" - tg-check-permission="view_us" - tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" - tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" - title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" - ) - span(tg-bo-ref="us.origin_issue.ref") - - p.external-reference(ng-if="us.external_reference") - | {{ 'US.EXTERNAL_REFERENCE'|translate }} - a( - tg-bo-href="us.external_reference[1]", - title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" - target="_blank" - ) - span {{ us.external_reference[1] }} - - p.block-desc-container(ng-show="us.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") - div.issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'US.PREVIOUS' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-left") - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'US.NEXT' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-right") + //- div.us-title(ng-class="{blocked: us.is_blocked}") + //- h2.us-title-text + //- + //- + //- p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" + //- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" + //- title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + //- ) + //- span(tg-bo-ref="us.origin_issue.ref") + //- + //- p.external-reference(ng-if="us.external_reference") + //- | {{ 'US.EXTERNAL_REFERENCE'|translate }} + //- a( + //- tg-bo-href="us.external_reference[1]", + //- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" + //- target="_blank" + //- ) + //- span {{ us.external_reference[1] }} + //- + //- p.block-desc-container(ng-show="us.is_blocked") + //- span.block-description-title(translate="COMMON.BLOCKED") + //- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") + //- div.issue-nav + //- a( + //- ng-show="previousUrl" + //- tg-bo-href="previousUrl" + //- title="{{'US.PREVIOUS' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-left") + //- a( + //- ng-show="nextUrl" + //- tg-bo-href="nextUrl" + //- title="{{'US.NEXT' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="us && project" diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 5daa3f6a..30af3d0a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -7,186 +7,6 @@ justify-content: center; margin-bottom: .5rem; } - .us-title { - @include font-size(large); - @include font-type(text); - align-items: flex-start; - background: $mass-white; - display: flex; - flex: 1; - flex-direction: column; - padding: .5rem; - position: relative; - transition: all .2s linear; - &.blocked { - background: $red; - transition: all .2s linear; - vertical-align: middle; - .us-title-text, - input { - margin-bottom: .5rem; - } - .us-number, - .us-name, - .us-related-task { - color: $white; - } - a { - color: $white; - transition: color .3s linear; - } - a:hover { - color: $red-light; - } - .unblock { - @include font-type(bold); - color: $white; - float: right; - } - .unblock:hover { - color: $red-light; - transition: color .3s linear; - } - } - p { - margin-bottom: 0; - } - .us-edit-name-inner { - display: flex; - } - .edit-subject { - align-content: center; - align-items: center; - display: flex; - width: 100%; - } - input { - background: $white; - flex-grow: 1; - } - .save-container { - flex-grow: 1; - .save { - display: block; - } - } - .us-title-text { - @include font-size(larger); - @include font-type(text); - align-content: center; - align-items: center; - display: flex; - flex: 1; - margin-bottom: 0; - max-width: 92%; - width: 100%; - } - .us-title-text:hover { - .edit { - opacity: 1; - transition: opacity .3s linear; - } - } - .us-number { - @include font-type(text); - color: $gray-light; - flex-shrink: 0; - line-height: 2.2rem; - margin-right: .5rem; - } - .us-name { - color: $gray; - display: inline-block; - flex-grow: 1; - line-height: 2.2rem; - padding-right: 1rem; - width: 100%; - } - .save, - .edit { - cursor: pointer; - margin-left: .5rem; - svg { - fill: $gray-light; - } - } - .edit { - opacity: 0; - } - .us-related-task { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a { - border-left: 1px solid $gray-light; - padding: 0 .2rem; - } - a:hover { - color: $primary; - } - a:first-child { - border: 0; - } - } - .block-desc-container { - @include font-size(small); - } - .block-description-title { - @include font-type(bold); - color: $white; - margin-right: .5rem; - } - .block-description { - color: $white; - display: inline-block; - margin-right: 5rem; - } - } - .belong-to-epics-wrapper { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a:hover { - color: $primary; - } - } - .loading-spinner { - @include loading-spinner; - max-height: 1.5rem; - max-width: 1.5rem; - } -} - -.blocked-warning { - margin-bottom: 1rem; - .blocked { - @include font-type(text); - @include font-size(xlarge); - color: $red; - line-height: 2.5rem; - margin-bottom: .5rem; - } - .icon { - @include font-size(xlarge); - vertical-align: middle; - } - .block-description { - color: $grayer; - margin: 0; - } -} - -.issue-nav { - position: absolute; - right: 1rem; - top: 1rem; - a { - display: inline-block; - } - svg { - @include svg-size(1.2rem); - fill: currentColor; - } } .subheader { From fccd26c7499fd9f263f9b80eb1362133a089040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 12 Aug 2016 14:08:34 +0200 Subject: [PATCH 037/137] US navigation --- app/coffee/modules/common/components.coffee | 87 ------------------- app/coffee/modules/userstories/detail.coffee | 14 --- .../header/story-header.controller.coffee | 27 +++++- app/modules/stories/header/story-header.jade | 10 +-- app/modules/stories/header/story-header.scss | 2 + 5 files changed, 31 insertions(+), 109 deletions(-) diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index d1c0cea5..5da45d4a 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -492,93 +492,6 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) -> module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective]) - -# ############################################################################# -# ## Editable subject directive -# ############################################################################# -# -# EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> -# template = $template.get("common/components/editable-subject.html") -# -# link = ($scope, $el, $attrs, $model) -> -# -# $scope.$on "object:updated", () -> -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# isEditable = -> -# return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 -# -# save = (subject) -> -# currentLoading = $loading() -# .target($el.find('.save-container')) -# .start() -# -# transform = $modelTransform.save (item) -> -# item.subject = subject -# -# return item -# -# transform.then => -# $confirm.notify("success") -# $rootscope.$broadcast("object:updated") -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# transform.then null, -> -# $confirm.notify("error") -# -# transform.finally -> -# currentLoading.finish() -# -# return transform -# -# $el.click -> -# return if not isEditable() -# $el.find('.edit-subject').show() -# $el.find('.view-subject').hide() -# $el.find('input').focus() -# -# $el.on "click", ".save", (e) -> -# e.preventDefault() -# -# subject = $scope.item.subject -# save(subject) -# -# $el.on "keyup", "input", (event) -> -# if event.keyCode == 13 -# subject = $scope.item.subject -# save(subject) -# else if event.keyCode == 27 -# $scope.$apply () => $model.$modelValue.revert() -# -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# $el.find('.edit-subject').hide() -# -# $scope.$watch $attrs.ngModel, (value) -> -# return if not value -# $scope.item = value -# -# if not isEditable() -# $el.find('.view-subject .edit').remove() -# -# $scope.$on "$destroy", -> -# $el.off() -# -# -# return { -# link: link -# restrict: "EA" -# require: "ngModel" -# template: template -# } -# -# module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", -# "$tgTemplate", EditableSubjectDirective]) - - ############################################################################# ## Editable description directive ############################################################################# diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index c19d4fdb..950185df 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -166,20 +166,6 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @modelTransform.setObject(@scope, 'us') - if @scope.us.neighbors.previous?.ref? - ctx = { - project: @scope.project.slug - ref: @scope.us.neighbors.previous.ref - } - @scope.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) - - if @scope.us.neighbors.next?.ref? - ctx = { - project: @scope.project.slug - ref: @scope.us.neighbors.next.ref - } - @scope.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) - return us loadSprint: -> diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 7fb78177..f0720cb9 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -23,14 +23,31 @@ class StoryHeaderController @.$inject = [ "$rootScope", "$tgConfirm", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "$tgNavUrls", ] - constructor: (@rootScope, @confirm, @modelTransform) -> + constructor: (@rootScope, @confirm, @modelTransform, @navUrls) -> @.editMode = false @.loadingSubject = false @.originalSubject = @.item.subject + console.log @.item + + if @.item.neighbors.previous?.ref? + ctx = { + project: @.project.slug + ref: @.item.neighbors.previous.ref + } + @.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) + + if @.item.neighbors.next?.ref? + ctx = { + project: @.project.slug + ref: @.item.neighbors.next.ref + } + @.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) + _checkPermissions: () -> @.permissions = { canEdit: _.includes(@.project.my_permissions, @.requiredPerm) @@ -42,7 +59,10 @@ class StoryHeaderController if !value @.editMode = false - onCancelEdition: (event) -> + onKeyDown: (event) -> + if event.which == 13 + @.saveSubject() + if event.which == 27 @.item.subject = @.originalSubject @.editSubject(false) @@ -52,6 +72,7 @@ class StoryHeaderController @.loadingSubject = false @rootScope.$broadcast("object:updated") @confirm.notify('success') + @.originalSubject = @.item.subject onEditSubjectError = () => @.loadingSubject = false diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 8c42b97f..31bd92ea 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -24,7 +24,7 @@ maxlength="500" autofocus required - ng-keydown="vm.onCancelEdition($event)" + ng-keydown="vm.onKeyDown($event)" ) button.edit-title-button( ng-click="vm.saveSubject()" @@ -63,14 +63,14 @@ .issue-nav a( - ng-show="previousUrl" - tg-bo-href="previousUrl" + ng-if="vm.previousUrl" + ng-href="{{vm.previousUrl}}" title="{{'US.PREVIOUS' | translate}}" ) tg-svg(svg-icon="icon-arrow-left") a( - ng-show="nextUrl" - tg-bo-href="nextUrl" + ng-if="vm.nextUrl" + ng-href="{{vm.nextUrl}}" title="{{'US.NEXT' | translate}}" ) tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index a8d0693e..330db122 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -2,6 +2,7 @@ background: $mass-white; flex: 1; padding: 1rem; + position: relative; &:hover { .detail-edit { opacity: 1; @@ -45,6 +46,7 @@ transition: all .2s linear; } .detail-title-text { + line-height: normal; margin: 0; } .detail-number { From 1dcb363389b55eb823bf208db258793914be5c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 09:07:09 +0200 Subject: [PATCH 038/137] Avoid click on select range --- .../stories/header/story-header.controller.coffee | 15 +++++++++------ app/modules/stories/header/story-header.scss | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index f0720cb9..46304d9a 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: epics.dashboard.controller.coffee +# File: story-header.controller.coffee ### module = angular.module("taigaUserStories") @@ -25,9 +25,10 @@ class StoryHeaderController "$tgConfirm", "$tgQueueModelTransformation", "$tgNavUrls", + "$window" ] - constructor: (@rootScope, @confirm, @modelTransform, @navUrls) -> + constructor: (@rootScope, @confirm, @modelTransform, @navUrls, @window) -> @.editMode = false @.loadingSubject = false @.originalSubject = @.item.subject @@ -54,10 +55,12 @@ class StoryHeaderController } editSubject: (value) -> - if value - @.editMode = true - if !value - @.editMode = false + selection = @window.getSelection() + if selection.type != "Range" + if value + @.editMode = true + if !value + @.editMode = false onKeyDown: (event) -> if event.which == 13 diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index 330db122..f98ec85c 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -39,6 +39,7 @@ @include font-type(text); align-content: center; display: flex; + max-width: 95%; position: relative; transition: all .2s linear; &.blocked { From 0905c2b56f97509285fc64bb649bc5ab23004621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 09:58:06 +0200 Subject: [PATCH 039/137] Apply to task --- .../header/story-header.controller.coffee | 2 + .../header/story-header.directive.coffee | 1 + app/modules/stories/header/story-header.jade | 11 ++ app/modules/stories/header/story-header.scss | 148 +----------------- app/partials/task/task-detail.jade | 113 +++++++------ app/partials/us/us-detail.jade | 40 ----- 6 files changed, 85 insertions(+), 230 deletions(-) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 46304d9a..77c1c5bc 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -35,6 +35,8 @@ class StoryHeaderController console.log @.item + _checkNav: () -> + if @.item.neighbors.previous?.ref? ctx = { project: @.project.slug diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/stories/header/story-header.directive.coffee index 58d9088a..81f0f9a4 100644 --- a/app/modules/stories/header/story-header.directive.coffee +++ b/app/modules/stories/header/story-header.directive.coffee @@ -24,6 +24,7 @@ DetailHeaderDirective = () -> link = (scope, el, attrs, ctrl) -> ctrl._checkPermissions() + ctrl._checkNav() return { link: link, diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 31bd92ea..9c2a6f8a 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -42,6 +42,17 @@ project="project" ) +.task-belongs-to(ng-if="vm.item.user_story") + span(translate="TASK.OWNER_US") + a( + href="" + tg-check-permission="view_us" + tg-nav="project-userstories-detail:project=project.slug,ref=vm.item.user_story.ref" + title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + ) + span.item-ref {{'#' + vm.item.ref}} + span {{::vm.item.subject}} + .item-origin-issue( ng-if="vm.item.origin_issue" ) diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index f98ec85c..251a316a 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -22,13 +22,19 @@ } } .item-origin-issue, + .task-belongs-to, .belong-to-epics-wrapper, .block-desc-container { @include font-size(small); margin-top: .5rem; } + .task-belongs-to, .item-origin-issue { a { + cursor: pointer; + padding: 0 .2rem; + } + .item-ref { padding: 0 .2rem; } } @@ -59,7 +65,6 @@ color: $gray; flex-grow: 1; } - .detail-edit { cursor: pointer; margin-left: .75rem; @@ -111,144 +116,3 @@ fill: currentColor; } } - -// -// .us-title { -// -// &.blocked { -// background: $red; -// transition: all .2s linear; -// vertical-align: middle; -// .us-title-text, -// input { -// margin-bottom: .5rem; -// } -// .us-number, -// .us-name, -// .us-related-task { -// color: $white; -// } -// a { -// color: $white; -// transition: color .3s linear; -// } -// a:hover { -// color: $red-light; -// } -// .unblock { -// @include font-type(bold); -// color: $white; -// float: right; -// } -// .unblock:hover { -// color: $red-light; -// transition: color .3s linear; -// } -// } -// p { -// margin-bottom: 0; -// } -// .us-edit-name-inner { -// display: flex; -// } -// .edit-subject { -// align-content: center; -// align-items: center; -// display: flex; -// width: 100%; -// } -// input { -// background: $white; -// flex-grow: 1; -// } -// .save-container { -// flex-grow: 1; -// .save { -// display: block; -// } -// } -// .us-title-text { -// @include font-size(larger); -// @include font-type(text); -// align-content: center; -// align-items: center; -// display: flex; -// flex: 1; -// margin-bottom: 0; -// max-width: 92%; -// width: 100%; -// } -// .us-title-text:hover { -// .edit { -// opacity: 1; -// transition: opacity .3s linear; -// } -// } -// .us-number { -// @include font-type(text); -// color: $gray-light; -// flex-shrink: 0; -// line-height: 2.2rem; -// margin-right: .5rem; -// } -// .us-name { -// color: $gray; -// display: inline-block; -// flex-grow: 1; -// line-height: 2.2rem; -// padding-right: 1rem; -// width: 100%; -// } -// .save, -// .edit { -// cursor: pointer; -// margin-left: .5rem; -// svg { -// fill: $gray-light; -// } -// } -// .edit { -// opacity: 0; -// } -// .us-related-task { -// @include font-size(small); -// color: $gray-light; -// margin-top: .5rem; -// a { -// border-left: 1px solid $gray-light; -// padding: 0 .2rem; -// } -// a:hover { -// color: $primary; -// } -// a:first-child { -// border: 0; -// } -// } -// .block-desc-container { -// @include font-size(small); -// } -// .block-description-title { -// @include font-type(bold); -// color: $white; -// margin-right: .5rem; -// } -// .block-description { -// color: $white; -// display: inline-block; -// margin-right: 5rem; -// } -// } -// .belong-to-epics-wrapper { -// @include font-size(small); -// color: $gray-light; -// margin-top: .5rem; -// a:hover { -// color: $primary; -// } -// } -// .loading-spinner { -// @include loading-spinner; -// max-height: 1.5rem; -// max-width: 1.5rem; -// } diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 6ec12a6b..59b6e4be 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -26,54 +26,71 @@ div.wrapper( on-upvote="ctrl.onUpvote", on-downvote="ctrl.onDownvote" ) - div.us-title(ng-class="{blocked: task.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="task.ref") - span.us-name( - tg-editable-subject - ng-model="task" - required-perm="modify_task" - ) - - h3.us-related-task(ng-if="us") - | {{ 'TASK.OWNER_US'|translate }} - a( - href="" - tg-check-permission="view_us" - tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - ) - span(tg-bo-ref="us.ref") - span(tg-bo-bind="us.subject") - - p.external-reference(ng-if="task.external_reference") - a( - tg-bo-href="task.external_reference[1]", - target="_blank" - title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" - ) - | {{ "TASK.ORIGIN_US"| translate }} - span {{ task.external_reference[1] }} - - p.block-desc-container(ng-show="task.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description( - ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" - ) - - div.issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'TASK.PREVIOUS' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-left") - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'TASK.NEXT' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-right") + tg-detail-header.detail-header-container( + item="task" + project="project" + required-perm="modify_task" + ng-class="{blocked: task.is_blocked}" + ng-if="project && task" + ) + //- h3.us-related-task(ng-if="us") + //- | {{ 'TASK.OWNER_US'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" + //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + //- ) + //- span(tg-bo-ref="us.ref") + //- span(tg-bo-bind="us.subject") + //- div.us-title(ng-class="{blocked: task.is_blocked}") + //- h2.us-title-text + //- span.us-number(tg-bo-ref="task.ref") + //- span.us-name( + //- tg-editable-subject + //- ng-model="task" + //- required-perm="modify_task" + //- ) + //- + //- h3.us-related-task(ng-if="us") + //- | {{ 'TASK.OWNER_US'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" + //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + //- ) + //- span(tg-bo-ref="us.ref") + //- span(tg-bo-bind="us.subject") + //- + //- p.external-reference(ng-if="task.external_reference") + //- a( + //- tg-bo-href="task.external_reference[1]", + //- target="_blank" + //- title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" + //- ) + //- | {{ "TASK.ORIGIN_US"| translate }} + //- span {{ task.external_reference[1] }} + //- + //- p.block-desc-container(ng-show="task.is_blocked") + //- span.block-description-title(translate="COMMON.BLOCKED") + //- span.block-description( + //- ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" + //- ) + //- + //- div.issue-nav + //- a( + //- ng-show="previousUrl" + //- tg-bo-href="previousUrl" + //- title="{{'TASK.PREVIOUS' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-left") + //- a( + //- ng-show="nextUrl" + //- tg-bo-href="nextUrl" + //- title="{{'TASK.NEXT' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="task && project" diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index fbf4fd23..b35b512d 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -33,46 +33,6 @@ div.wrapper( ng-class="{blocked: us.is_blocked}" ng-if="project && us" ) - - //- div.us-title(ng-class="{blocked: us.is_blocked}") - //- h2.us-title-text - //- - //- - //- p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" - //- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" - //- title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" - //- ) - //- span(tg-bo-ref="us.origin_issue.ref") - //- - //- p.external-reference(ng-if="us.external_reference") - //- | {{ 'US.EXTERNAL_REFERENCE'|translate }} - //- a( - //- tg-bo-href="us.external_reference[1]", - //- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" - //- target="_blank" - //- ) - //- span {{ us.external_reference[1] }} - //- - //- p.block-desc-container(ng-show="us.is_blocked") - //- span.block-description-title(translate="COMMON.BLOCKED") - //- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") - //- div.issue-nav - //- a( - //- ng-show="previousUrl" - //- tg-bo-href="previousUrl" - //- title="{{'US.PREVIOUS' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-left") - //- a( - //- ng-show="nextUrl" - //- tg-bo-href="nextUrl" - //- title="{{'US.NEXT' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="us && project" From 140bc1d3f8b018504e83632f49a3758f913b0b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 17:54:58 +0200 Subject: [PATCH 040/137] Header Detail --- app/locales/taiga/locale-en.json | 8 +-- .../header/story-header.controller.coffee | 5 +- app/modules/stories/header/story-header.jade | 53 +++++++++++++------ app/modules/stories/header/story-header.scss | 2 + app/partials/issue/issues-detail.jade | 53 +++---------------- 5 files changed, 51 insertions(+), 70 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 2c16e946..c79fd001 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "One item per line...", "NEW_BULK": "New bulk insert", "RELATED_TASKS": "Related tasks", + "PREVIOUS": "Previous", + "NEXT": "Next", "LOGOUT": "Logout", "EXTERNAL_USER": "an external user", "GENERIC_ERROR": "One of our Oompa Loompas says {{error}}.", @@ -1060,8 +1062,6 @@ "EXTERNAL_REFERENCE": "This US has been created from", "GO_TO_EXTERNAL_REFERENCE": "Go to origin", "BLOCKED": "This user story is blocked", - "PREVIOUS": "previous user story", - "NEXT": "next user story", "TITLE_DELETE_ACTION": "Delete User Story", "LIGHTBOX_TITLE_BLOKING_US": "Blocking us", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tasks completed", @@ -1295,8 +1295,6 @@ "ORIGIN_US": "This task has been created from", "TITLE_LINK_GO_ORIGIN": "Go to user story", "BLOCKED": "This task is blocked", - "PREVIOUS": "previous task", - "NEXT": "next task", "TITLE_DELETE_ACTION": "Delete Task", "LIGHTBOX_TITLE_BLOKING_TASK": "Blocking task", "FIELDS": { @@ -1341,8 +1339,6 @@ "EXTERNAL_REFERENCE": "This issue has been created from", "GO_TO_EXTERNAL_REFERENCE": "Go to origin", "BLOCKED": "This issue is blocked", - "TITLE_PREVIOUS_ISSUE": "previous issue", - "TITLE_NEXT_ISSUE": "next issue", "ACTION_DELETE": "Delete issue", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blocking issue", "FIELDS": { diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 77c1c5bc..e784a57c 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -36,20 +36,19 @@ class StoryHeaderController console.log @.item _checkNav: () -> - if @.item.neighbors.previous?.ref? ctx = { project: @.project.slug ref: @.item.neighbors.previous.ref } - @.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) + @.previousUrl = @navUrls.resolve("project-" + @.item._name + "-detail", ctx) if @.item.neighbors.next?.ref? ctx = { project: @.project.slug ref: @.item.neighbors.next.ref } - @.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) + @.nextUrl = @navUrls.resolve("project-" + @.item._name + "-detail", ctx) _checkPermissions: () -> @.permissions = { diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 9c2a6f8a..d56ae7e4 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -33,6 +33,8 @@ tg-svg( svg-icon="icon-save" ) + +//- User Story belongs to epic .belong-to-epics-wrapper(ng-if="vm.item.epics") span This User Story belongs to tg-belong-to-epics( @@ -42,20 +44,41 @@ project="project" ) -.task-belongs-to(ng-if="vm.item.user_story") +//- Task belongs to US +.task-belongs-to( + ng-if="vm.item.user_story_extra_info" + tg-check-permission="view_us" +) span(translate="TASK.OWNER_US") a( - href="" - tg-check-permission="view_us" - tg-nav="project-userstories-detail:project=project.slug,ref=vm.item.user_story.ref" + tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.item.user_story_extra_info.ref" title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" ) - span.item-ref {{'#' + vm.item.ref}} - span {{::vm.item.subject}} + span.item-ref {{'#' + vm.item.user_story_extra_info.ref}} + span {{::vm.item.user_story_extra_info.subject}} -.item-origin-issue( - ng-if="vm.item.origin_issue" -) + +//- User Stories generated from issue +.item-generated-us(ng-if="vm.item.generated_user_stories.length") + span(translate="ISSUES.PROMOTED") + a( + ng-repeat="userstory in vm.item.generated_user_stories track by userstory.id" + tg-check-permission="view_us" + tg-nav="project-userstories-detail:project=vm.project.slug,ref=userstory.ref" + ) {{'#' + userstory.ref}} {{userstory.subject}} + +//- Issue origin from github +.issue-external-reference(ng-if="vm.item.external_reference") + span(translate="ISSUES.EXTERNAL_REFERENCE") + a( + target="_blank" + ng-href="::vm.item.external_reference[1]" + ng-title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}" + ) + span {{ ::vm.item.external_reference[1] }} + +//- User Story promoted from issue +.item-origin-issue(ng-if="vm.item.origin_issue") span(translate="US.PROMOTED") a( href="" @@ -63,25 +86,25 @@ tg-nav="project-issues-detail:project=vm.project.slug,ref=vm.item.origin_issue.ref" title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" ) - span {{'#' + vm.item.origin_issue.ref}} + span.item-ref {{'#' + vm.item.origin_issue.ref}} span {{vm.item.origin_issue.subject}} +//- Blocked description .block-desc-container(ng-show="vm.item.is_blocked") span.block-description-title(translate="COMMON.BLOCKED") - span.block-description( - ng-if="vm.item.blocked_note" - ) {{vm.item.blocked_note}} + span.block-description(ng-if="vm.item.blocked_note") {{vm.item.blocked_note}} +//- Navigation .issue-nav a( ng-if="vm.previousUrl" ng-href="{{vm.previousUrl}}" - title="{{'US.PREVIOUS' | translate}}" + title="{{'COMMON.PREVIOUS' | translate}}" ) tg-svg(svg-icon="icon-arrow-left") a( ng-if="vm.nextUrl" ng-href="{{vm.nextUrl}}" - title="{{'US.NEXT' | translate}}" + title="{{'COMMON.NEXT' | translate}}" ) tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index 251a316a..122e1cd3 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -21,6 +21,7 @@ fill: $white; } } + .item-generated-us, .item-origin-issue, .task-belongs-to, .belong-to-epics-wrapper, @@ -28,6 +29,7 @@ @include font-size(small); margin-top: .5rem; } + .item-generated-us, .task-belongs-to, .item-origin-issue { a { diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 1b4185f0..771cf4fd 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -17,52 +17,13 @@ div.wrapper( on-upvote="ctrl.onUpvote" on-downvote="ctrl.onDownvote" ) - .us-title(ng-class="{blocked: issue.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="issue.ref") - span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue") - - p.us-related-task(ng-if="issue.generated_user_stories.length") {{ 'ISSUES.PROMOTED'|translate }} - a( - href="" - ng-repeat="us in issue.generated_user_stories" - tg-check-permission="view_us" - tg-bo-title="'#' + us.ref + ' ' + us.subject" - tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - ) - span(tg-bo-ref="us.ref") - - p.external-reference(ng-if="issue.external_reference") - | {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }} - a( - target="_blank" - tg-bo-href="issue.external_reference[1]" - title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}" - ) - span {{ issue.external_reference[1] }} - - p.block-desc-container(ng-show="issue.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)") - - .issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}" - ) - tg-svg( - svg-icon="icon-arrow-left" - ) - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}" - - ) - tg-svg( - svg-icon="icon-arrow-right" - ) + tg-detail-header.detail-header-container( + item="issue" + project="project" + required-perm="modify_issue" + ng-class="{blocked: issue.is_blocked}" + ng-if="project && issue" + ) .subheader tg-tag-line.tags-block( ng-if="issue && project" From 3b5fe82b5495e5502d5e96a5fd5a2381dca0abd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 17 Aug 2016 16:21:20 +0200 Subject: [PATCH 041/137] Tests --- .../header/story-header.controller.coffee | 2 - .../story-header.controller.spec.coffee | 144 ++++++++++++++++++ app/modules/stories/header/story-header.jade | 7 +- 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 app/modules/stories/header/story-header.controller.spec.coffee diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index e784a57c..2d2b5437 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -33,8 +33,6 @@ class StoryHeaderController @.loadingSubject = false @.originalSubject = @.item.subject - console.log @.item - _checkNav: () -> if @.item.neighbors.previous?.ref? ctx = { diff --git a/app/modules/stories/header/story-header.controller.spec.coffee b/app/modules/stories/header/story-header.controller.spec.coffee new file mode 100644 index 00000000..31ee9c96 --- /dev/null +++ b/app/modules/stories/header/story-header.controller.spec.coffee @@ -0,0 +1,144 @@ +### +# Copyright (C) 2014-2015 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: wiki-history.controller.spec.coffee +### + +describe "StoryHeaderComponent", -> + headerDetailCtrl = null + provide = null + controller = null + rootScope = null + mocks = {} + + _mockRootScope = () -> + mocks.rootScope = { + $broadcast: sinon.stub() + } + + provide.value "$rootScope", mocks.rootScope + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgQueueModelTransformation = () -> + mocks.modelTransform = { + save: sinon.stub() + } + + provide.value "$tgQueueModelTransformation", mocks.tgQueueModelTransformation + + _mockTgNav = () -> + mocks.navUrls = { + resolve: sinon.stub().returns('project-issues-detail') + } + + provide.value "$tgNavUrls", mocks.navUrls + + _mockWindow = () -> + mocks.window = { + getSelection: sinon.stub() + } + + provide.value "$window", mocks.window + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockRootScope() + _mockTgConfirm() + _mockTgQueueModelTransformation() + _mockTgNav() + _mockWindow() + + return null + + beforeEach -> + module "taigaUserStories" + + _mocks() + + inject ($controller) -> + controller = $controller + + headerDetailCtrl = controller "StoryHeaderCtrl", {}, { + item: { + subject: 'Example subject' + } + } + + headerDetailCtrl.originalSubject = headerDetailCtrl.item.subject + + it "previous item neighbor", () -> + headerDetailCtrl.project = { + slug: 'example_subject' + } + headerDetailCtrl.item.neighbors = { + previous: { + ref: 42 + } + } + headerDetailCtrl._checkNav() + headerDetailCtrl.previousUrl = mocks.navUrls.resolve("project-issues-detail") + expect(headerDetailCtrl.previousUrl).to.be.equal("project-issues-detail") + + it "check permissions", () -> + headerDetailCtrl.project = { + my_permissions: ['view_us'] + } + headerDetailCtrl.requiredPerm = 'view_us' + headerDetailCtrl._checkPermissions() + expect(headerDetailCtrl.permissions).to.be.eql({canEdit: true}) + + it "edit subject without selection", () -> + mocks.window.getSelection.returns({ + type: 'Range' + }) + headerDetailCtrl.editSubject(true) + expect(headerDetailCtrl.editMode).to.be.false + + it "edit subject on click", () -> + mocks.window.getSelection.returns({ + type: 'potato' + }) + headerDetailCtrl.editSubject(true) + expect(headerDetailCtrl.editMode).to.be.true + + it "do not edit subject", () -> + mocks.window.getSelection.returns({ + type: 'Range' + }) + headerDetailCtrl.editSubject(false) + expect(headerDetailCtrl.editMode).to.be.false + + it "save on keydown Enter", () -> + event = {} + event.which = 13 + headerDetailCtrl.saveSubject = sinon.stub() + headerDetailCtrl.onKeyDown(event) + expect(headerDetailCtrl.saveSubject).have.been.called + + it "don't save on keydown ESC", () -> + event = {} + event.which = 27 + headerDetailCtrl.editSubject = sinon.stub() + headerDetailCtrl.onKeyDown(event) + expect(headerDetailCtrl.item.subject).to.be.equal(headerDetailCtrl.originalSubject) + expect(headerDetailCtrl.editSubject).have.been.calledWith(false) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index d56ae7e4..81e957df 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -56,7 +56,12 @@ ) span.item-ref {{'#' + vm.item.user_story_extra_info.ref}} span {{::vm.item.user_story_extra_info.subject}} - + tg-belong-to-epics( + ng-if="::vm.item.user_story_extra_info.epics" + epics="::vm.item.user_story_extra_info.epics" + format="pill" + project="vm.project" + ) //- User Stories generated from issue .item-generated-us(ng-if="vm.item.generated_user_stories.length") From 1ad2dd70b358b45f0dc4fe6d3d0a6de5db05cfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 18 Aug 2016 09:00:22 +0200 Subject: [PATCH 042/137] Fix header tests --- app/modules/stories/header/story-header.jade | 12 ++++++------ bower.json | 2 +- e2e/helpers/detail-helper.js | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 81e957df..82615b20 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -1,24 +1,24 @@ -.detail-title-wrapper +.detail-title-wrapper.e2e-story-header h2.detail-title-text.ng-animate-disabled( ng-show="!vm.editMode" ng-hide="vm.editMode" ) span.detail-number {{'#' + vm.item.ref}} - span.detail-subject( + span.detail-subject.e2e-title-subject( ng-click="vm.editSubject(true)" ng-if="vm.permissions.canEdit" ) {{vm.item.subject}} - span.detail-subject( + span.detail-subject.e2e-title-subject( ng-if="!vm.permissions.canEdit" ) {{vm.item.subject}} - tg-svg.detail-edit( + tg-svg.detail-edit.e2e-detail-edit( ng-if="vm.permissions.canEdit" svg-icon="icon-edit" ng-click="vm.editSubject(true)" ) .edit-title-wrapper(ng-if="vm.editMode") - input.edit-title-input( + input.edit-title-input.e2e-title-input( type="text" ng-model="vm.item.subject" maxlength="500" @@ -26,7 +26,7 @@ required ng-keydown="vm.onKeyDown($event)" ) - button.edit-title-button( + button.edit-title-button.e2e-title-button( ng-click="vm.saveSubject()" tg-loading="vm.loadingSubject" ) diff --git a/bower.json b/bower.json index 7cd4a904..d8c928f0 100644 --- a/bower.json +++ b/bower.json @@ -69,7 +69,7 @@ "angular-translate-loader-partial": "~2.10.0", "angular-translate-loader-static-files": "~2.10.0", "angular-translate-interpolation-messageformat": "~2.10.0", - "ngInfiniteScroll": "1.3.0", + "ngInfiniteScroll": "^1.3.0", "immutable": "~3.8.1", "bluebird": "~3.3.5", "intro.js": "~2.1.0", diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 1148d21c..78daae62 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -2,22 +2,22 @@ var utils = require('../utils'); var helper = module.exports; helper.title = function() { - let el = $('span[tg-editable-subject]'); + let el = $('.e2e-story-header'); let obj = { el: el, getTitle: function() { - return el.$('.view-subject').getText(); + return el.$('.e2e-title-subject').getText(); }, setTitle: function(title) { - el.$('.view-subject').click(); - el.$('.edit-subject input').clear().sendKeys(title); + el.$('.e2e-detail-edit').click(); + el.$('.e2e-title-input').clear().sendKeys(title); }, save: async function() { - el.$('.save').click(); + el.$('.e2e-title-button').click(); await browser.waitForAngular(); } }; From e291f98a461de50fdf250d6800a724af60bed230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 19 Aug 2016 10:29:27 +0200 Subject: [PATCH 043/137] Add e2e tests for epics --- .../assigned-to-selector.jade | 4 +- .../components/assigned-to/assigned-to.jade | 12 +- .../epics/dashboard/epic-row/epic-row.jade | 10 +- .../dashboard/epics-table/epics-table.jade | 10 +- conf.e2e.js | 1 + e2e/helpers/epics-helper.js | 176 ++++++++++++++++++ e2e/suites/epics/epic-dashboard.e2e.js | 67 +++++++ e2e/utils/nav.js | 9 + 8 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 e2e/helpers/epics-helper.js create mode 100644 e2e/suites/epics/epic-dashboard.e2e.js diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index e74b5bb3..435a7979 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -15,12 +15,12 @@ tg-lightbox-close ng-if="vm.assigned" ) tg-assigned-item(member="member") - tg-svg.unassign-epic( + tg-svg.unassign-epic.e2e-unassign( svg-icon="icon-close" svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED" ng-click="vm.onRemoveAssigned()" ) - li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") + li.e2e-assigned-to-selector(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" ng-click="vm.onAssignTo({'member': member})" diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade index 3eb7fe76..a03af5a3 100644 --- a/app/modules/components/assigned-to/assigned-to.jade +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -1,24 +1,24 @@ -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="vm.assignedTo && vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="vm.assignedTo && !vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="!vm.assignedTo && vm.has_permissions" src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + alt="{{'EPICS.DASHBOARD.UNASSIGNED' | translate}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="!vm.assignedTo && !vm.has_permissions" src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + alt="{{'EPICS.DASHBOARD.UNASSIGNED' | translate}}" ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1644ab12..452d928a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,4 +1,4 @@ -.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-click="vm.requestUserStories(vm.epic)" ) @@ -31,7 +31,7 @@ .sprint( ng-if="vm.column.sprint" ) - .assigned + .assigned.e2e-assigned-to tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" @@ -51,13 +51,13 @@ ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" tg-loading="vm.loadingStatus" ) - span {{vm.epic.getIn(['status_extra_info', 'name'])}} + span.e2e-epic-status {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) ul.epic-statuses(ng-if="vm.displayStatusList") - li( + li.e2e-edit-epic-status( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" ng-click="vm.updateEpicStatus(status.id)" ) {{status.name}} @@ -70,7 +70,7 @@ .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( + tg-story-row.e2e-story( epic="vm.epic" story="story" project="vm.project" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index e82bdab0..1056460c 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -11,8 +11,8 @@ mixin epicSwitch(name, model) span.check-text.check-yes(translate="COMMON.YES") span.check-text.check-no(translate="COMMON.NO") -.epics-table - .epics-table-header +.epics-table.e2e-epic-table + .epics-table-header.e2e-epics-table-header .vote( translate="EPICS.TABLE.VOTES" ng-if="vm.column.votes" @@ -42,10 +42,10 @@ mixin epicSwitch(name, model) ng-if="vm.column.progress" ) .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") - button.epics-table-option-button(ng-click="vm.displayOptions = true") + button.epics-table-option-button.e2e-epics-column-button(ng-click="vm.displayOptions = true") span(translate="EPICS.TABLE.VIEW_OPTIONS") tg-svg(svg-icon="icon-arrow-down") - form.epics-table-dropdown(ng-show="vm.displayOptions") + form.epics-table-dropdown.e2e-epics-column-dropdown(ng-show="vm.displayOptions") .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.VOTES" @@ -90,7 +90,7 @@ mixin epicSwitch(name, model) +epicSwitch('switch-progress', 'vm.column.progress') .epics-table-body(tg-epic-sortable) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") - tg-epic-row( + tg-epic-row.e2e-epic( epic="epic" project="vm.project" column="vm.column" diff --git a/conf.e2e.js b/conf.e2e.js index a88f4a79..d7ca0a01 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -38,6 +38,7 @@ var config = { issues: "e2e/suites/issues/*.e2e.js", tasks: "e2e/suites/tasks/*.e2e.js", userProfile: "e2e/suites/user-profile/*.e2e.js", + epics: "e2e/suites/epics/*.e2e.js", userStories: "e2e/suites/user-stories/*.e2e.js", backlog: "e2e/suites/backlog.e2e.js", home: "e2e/suites/home.e2e.js", diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-helper.js new file mode 100644 index 00000000..4985b757 --- /dev/null +++ b/e2e/helpers/epics-helper.js @@ -0,0 +1,176 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.epic = function() { + let el = $$('.e2e-epic'); + + let obj = { + el: el, + displayUserStoriesinEpic: async function() { + await utils.common.takeScreenshot("epics", "epics-child-closed"); + let storiesCount = await el.count(); + let epicChildren; + for (var i = 0; i < storiesCount; i++) { + let story = await el.get(i); + story.click(); + epicChildren = await story.$$('.e2e-story').count(); + if (epicChildren > 0) { + await utils.common.takeScreenshot("epics", "epics-child-open"); + break; + } + } + return epicChildren; + }, + getAssignedTo: async function() { + return await el.get(0).$('.e2e-assigned-to-image').getAttribute("title"); + }, + resetAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-assigned-to-selector').get(0).click(); + }, + editAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-assigned-to-selector').last().click(); + }, + removeAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-unassign').click(); + return el.get(0).$('.e2e-assigned-to-image').getAttribute("alt"); + }, + resetStatus: function() { + el.get(0).$('.e2e-epic-status').click(); + el.get(0).$$('.e2e-edit-epic-status').get(0).click(); + }, + getStatus: function() { + return el.get(0).$('.e2e-epic-status').getText(); + }, + editStatus: function() { + el.get(0).$('.e2e-epic-status').click(); + el.get(0).$$('.e2e-edit-epic-status').last().click(); + }, + getColumns: function() { + return $$('.e2e-epics-table-header > div').count(); + }, + removeColumns: function() { + $('.e2e-epics-column-button').click(); + $$('.e2e-epics-column-dropdown .check').first().click(); + } + } + + return obj; +} + +// helper.title = function() { +// let el = $('.e2e-story-header'); +// +// let obj = { +// el: el, +// +// getTitle: function() { +// return el.$('.e2e-title-subject').getText(); +// }, +// +// setTitle: function(title) { +// el.$('.e2e-detail-edit').click(); +// el.$('.e2e-title-input').clear().sendKeys(title); +// }, +// +// save: async function() { +// el.$('.e2e-title-button').click(); +// await browser.waitForAngular(); +// } +// }; +// +// return obj; +// }; + +// +// helper.getCreateIssueLightbox = function() { +// let el = $('div[tg-lb-create-issue]'); +// +// let obj = { +// el: el, +// waitOpen: function() { +// return utils.lightbox.open(el); +// }, +// waitClose: function() { +// return utils.lightbox.close(el); +// }, +// subject: function() { +// return el.$$('input').first(); +// }, +// tags: function() { +// return el.$('.tag-input'); +// }, +// submit: function() { +// el.$('button[type="submit"]').click(); +// } +// }; +// +// return obj; +// }; +// +// helper.getBulkCreateLightbox = function() { +// let el = $('div[tg-lb-create-bulk-issues]'); +// +// let obj = { +// el: el, +// waitOpen: function() { +// return utils.lightbox.open(el); +// }, +// textarea: function() { +// return el.$('textarea'); +// }, +// submit: function() { +// el.$('button[type="submit"]').click(); +// }, +// waitClose: function() { +// return utils.lightbox.close(el); +// } +// }; +// +// return obj; +// }; +// +// helper.openNewIssueLb = function() { +// $('.new-issue .button-green').click(); +// }; +// +// helper.openBulk = function() { +// $('.new-issue .button-bulk').click(); +// }; +// +// helper.clickColumn = function(index) { +// $$('.row.title > div').get(index).click(); +// }; +// +// helper.getTable = function() { +// return $('.basic-table'); +// }; +// +// helper.openAssignTo = function(index) { +// $$('.issue-assignedto').get(index).click(); +// }; +// +// helper.getAssignTo = function(index) { +// return $$('.assigned-field figcaption').get(index).getText(); +// }; +// +// helper.clickPagination = function(index) { +// $$('.paginator li').get(index).click(); +// }; +// +// helper.getIssues = function() { +// return $$('.row.table-main'); +// }; +// +// helper.parseIssue = async function(elm) { +// let obj = {}; +// +// obj.ref = await elm.$$('.subject span').get(0).getText(); +// obj.ref = obj.ref.replace('#', ''); +// obj.subject = await elm.$$('.subject span').get(1).getText(); +// +// return obj; +// }; diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js new file mode 100644 index 00000000..369a906a --- /dev/null +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -0,0 +1,67 @@ +var utils = require('../../utils'); +var epicsHelper = require('../../helpers/epics-helper'); + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe('Epics Dashboard', function(){ + let usUrl = ''; + + before(async function(){ + await utils.nav + .init() + .project('Project Example 0') + .epics() + .go(); + + usUrl = await browser.getCurrentUrl(); + }); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "dashboard"); + }); + + it('display child stories', async function() { + let epic = epicsHelper.epic(); + let childStoriesNum = await epic.displayUserStoriesinEpic(); + expect(childStoriesNum).to.be.above(0); + }); + + it('change epic assigned from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetAssignedTo(); + let currentAssigned = await epic.getAssignedTo(); + await epic.editAssignedTo(); + let newAssigned = await epic.getAssignedTo(); + expect(currentAssigned).to.be.not.equal(newAssigned); + }); + + it('remove assigned from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetAssignedTo(); + let unAssigned = await epic.removeAssignedTo(); + console.log(unAssigned); + expect(unAssigned).to.be.equal('Unassigned'); + }); + + it('change status from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetStatus(); + let currentStatus = await epic.getStatus(); + await epic.editStatus(); + let newStatus = await epic.getStatus(); + expect(currentStatus).to.be.not.equal(newStatus); + }); + + it('remove columns from dashboard', async function() { + let epic = epicsHelper.epic(); + let currentColumns = await epic.getColumns(); + await epic.removeColumns(); + let newColumns = await epic.getColumns(); + expect(currentColumns).to.be.above(newColumns); + }); + +}) diff --git a/e2e/utils/nav.js b/e2e/utils/nav.js index 3712f716..17710dbe 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -46,6 +46,11 @@ var actions = { return common.waitLoader(); }, + epics: async function() { + await common.link($('#nav-epics a')); + + return common.waitLoader(); + }, backlog: async function() { await common.link($$('#nav-backlog a').first()); @@ -101,6 +106,10 @@ var nav = { this.actions.push(actions.issue.bind(null, index)); return this; }, + epics: function(index) { + this.actions.push(actions.epics.bind(null, index)); + return this; + }, backlog: function(index) { this.actions.push(actions.backlog.bind(null, index)); return this; From 5dc99133e5a5446404594a7b3058a0b355959a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 08:30:24 +0200 Subject: [PATCH 044/137] Moved to common --- .../detail/header/detail-header.controller.coffee} | 0 .../detail/header/detail-header.controller.spec.coffee} | 0 .../detail/header/detail-header.directive.coffee} | 0 .../detail/header/detail-header.jade} | 0 .../detail/header/detail-header.scss} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename app/modules/{stories/header/story-header.controller.coffee => components/detail/header/detail-header.controller.coffee} (100%) rename app/modules/{stories/header/story-header.controller.spec.coffee => components/detail/header/detail-header.controller.spec.coffee} (100%) rename app/modules/{stories/header/story-header.directive.coffee => components/detail/header/detail-header.directive.coffee} (100%) rename app/modules/{stories/header/story-header.jade => components/detail/header/detail-header.jade} (100%) rename app/modules/{stories/header/story-header.scss => components/detail/header/detail-header.scss} (100%) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/components/detail/header/detail-header.controller.coffee similarity index 100% rename from app/modules/stories/header/story-header.controller.coffee rename to app/modules/components/detail/header/detail-header.controller.coffee diff --git a/app/modules/stories/header/story-header.controller.spec.coffee b/app/modules/components/detail/header/detail-header.controller.spec.coffee similarity index 100% rename from app/modules/stories/header/story-header.controller.spec.coffee rename to app/modules/components/detail/header/detail-header.controller.spec.coffee diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/components/detail/header/detail-header.directive.coffee similarity index 100% rename from app/modules/stories/header/story-header.directive.coffee rename to app/modules/components/detail/header/detail-header.directive.coffee diff --git a/app/modules/stories/header/story-header.jade b/app/modules/components/detail/header/detail-header.jade similarity index 100% rename from app/modules/stories/header/story-header.jade rename to app/modules/components/detail/header/detail-header.jade diff --git a/app/modules/stories/header/story-header.scss b/app/modules/components/detail/header/detail-header.scss similarity index 100% rename from app/modules/stories/header/story-header.scss rename to app/modules/components/detail/header/detail-header.scss From d430587d31c9928647ac2a6aa2961a19cee0be9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 12:53:18 +0200 Subject: [PATCH 045/137] Epic styles fixes for blocked --- app/modules/epics/create-epic/create-epic.jade | 3 +-- app/modules/epics/dashboard/epic-row/epic-row.scss | 1 + app/styles/dependencies/mixins/epics-dashboard.scss | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index c7ccf9c2..8fdd54a7 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -7,7 +7,7 @@ tg-lightbox-close ) fieldset // TODO ADD COLOR SELECTOR - //- tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") + tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") input( type="text" name="subject" @@ -89,4 +89,3 @@ tg-lightbox-close type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) - diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 3cf55907..91dbda04 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -75,6 +75,7 @@ .progress-bar { background: $mass-white; max-width: 40vw; + padding-right: 1rem; width: 100%; } .progress-status { diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index c7e83fa4..75781d58 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -43,6 +43,8 @@ } .progress { flex-shrink: 3; + margin-right: 1rem; + position: relative; } .sprint { overflow: hidden; @@ -50,7 +52,4 @@ white-space: nowrap; width: 90%; } - .progress { - position: relative; - } } From 794ca91a7b71702e54118d3b1496e45e53b282bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 12:54:06 +0200 Subject: [PATCH 046/137] Create epics unit test --- .../create-epic.controller.spec.coffee | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/modules/epics/create-epic/create-epic.controller.spec.coffee diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee new file mode 100644 index 00000000..3453206d --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -0,0 +1,63 @@ +### +# Copyright (C) 2014-2015 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: epic-row.controller.spec.coffee +### + +describe "EpicRow", -> + createEpicCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + post: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "create Epic", (done) -> + createEpicCtrl = controller "CreateEpicCtrl" + createEpicCtrl.project = { + id: 7 + } + createEpicCtrl.newEpic = { + project: createEpicCtrl.project.id + } + createEpicCtrl.onReloadEpics = sinon.stub() + promise = mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve() + + createEpicCtrl.createEpic().then () -> + expect(createEpicCtrl.onReloadEpics).have.been.called + done() From 3b1f7108953546fc778a2c96caf5181b1a0d6faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 15:51:08 +0200 Subject: [PATCH 047/137] E2E tests for create epic --- .../epics/create-epic/create-epic.jade | 16 +++++++------- .../epics/dashboard/epics-dashboard.jade | 2 +- e2e/helpers/epics-helper.js | 21 ++++++++++++++++++- e2e/suites/epics/epic-dashboard.e2e.js | 11 ++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 8fdd54a7..7d7120ce 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -8,7 +8,7 @@ tg-lightbox-close fieldset // TODO ADD COLOR SELECTOR tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") - input( + input.e2e-create-epic-subject( type="text" name="subject" maxlength="140" @@ -18,7 +18,7 @@ tg-lightbox-close required ) fieldset - select( + select.e2e-create-epic-status( id="epic-status" name="status" ng-model="vm.newEpic.status" @@ -33,7 +33,7 @@ tg-lightbox-close ng-model="vm.newEpic.tags" ) fieldset - textarea( + textarea.e2e-create-epic-description( ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" ng-model="vm.newEpic.description" ) @@ -49,7 +49,7 @@ tg-lightbox-close ng-model="vm.newEpic.team_requirement" id="team-requirement" ) - label.requirement.trans-button( + label.requirement.trans-button.e2e-create-epic-team-requirement( for="team-requirement" translate="EPICS.CREATE.TEAM_REQUIREMENT" ) @@ -60,7 +60,7 @@ tg-lightbox-close ng-model="vm.newEpic.client_requirement" id="client-requirement" ) - label.requirement.trans-button( + label.requirement.trans-button.e2e-create-epic-client-requirement( for="client-requirement" translate="EPICS.CREATE.CLIENT_REQUIREMENT" ) @@ -72,12 +72,12 @@ tg-lightbox-close id="blocked" ng-click="displayBlockedReason = !displayBlockedReason" ) - label.requirement.trans-button.blocked( + label.requirement.trans-button.blocked.e2e-create-epic-blocked( for="blocked" translate="EPICS.CREATE.BLOCKED" ) fieldset(ng-if="displayBlockedReason") - input( + input.e2e-create-epic-blocked-note( type="text" name="blocked_note" maxlength="140" @@ -85,7 +85,7 @@ tg-lightbox-close placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" ) fieldset - input.button-green.create-epic-button( + input.button-green.create-epic-button.e2e-create-epic-button( type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index dd9f88ee..69ff744d 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -8,7 +8,7 @@ i18n-section-name="{{ vm.sectionName }}" ) .action-buttons(ng-if="vm.epics.size") - button.button-green( + button.button-green.e2e-create-epic( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", ng-click="vm.onCreateEpic()" diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-helper.js index 4985b757..305f7a27 100644 --- a/e2e/helpers/epics-helper.js +++ b/e2e/helpers/epics-helper.js @@ -7,8 +7,24 @@ helper.epic = function() { let obj = { el: el, + getEpics: async function() { + return el.count(); + }, + createEpic: async function(date, description) { + $('.e2e-create-epic').click(); + utils.common.takeScreenshot("epics", "epics-create-epic"); + $('.e2e-create-epic-subject').clear().sendKeys(date + description); + $('.e2e-create-epic-status').click(); + $$('.e2e-create-epic-status > option').get(0).click(); + $('.e2e-create-epic-description').clear().sendKeys(date + description); + $('.e2e-create-epic-client-requirement').click(); + $('.e2e-create-epic-team-requirement').click(); + $('.e2e-create-epic-blocked').click(); + $('.e2e-create-epic-blocked-note').clear().sendKeys(date + description); + $('.e2e-create-epic-button').click(); + }, displayUserStoriesinEpic: async function() { - await utils.common.takeScreenshot("epics", "epics-child-closed"); + utils.common.takeScreenshot("epics", "epics-child-closed"); let storiesCount = await el.count(); let epicChildren; for (var i = 0; i < storiesCount; i++) { @@ -31,6 +47,7 @@ helper.epic = function() { }, editAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); + utils.common.takeScreenshot("epics", "epics-edit-assigned"); $$('.e2e-assigned-to-selector').last().click(); }, removeAssignedTo: async function() { @@ -47,6 +64,7 @@ helper.epic = function() { }, editStatus: function() { el.get(0).$('.e2e-epic-status').click(); + utils.common.takeScreenshot("epics", "epics-edit-status"); el.get(0).$$('.e2e-edit-epic-status').last().click(); }, getColumns: function() { @@ -54,6 +72,7 @@ helper.epic = function() { }, removeColumns: function() { $('.e2e-epics-column-button').click(); + utils.common.takeScreenshot("epics", "epics-edit-columns"); $$('.e2e-epics-column-dropdown .check').first().click(); } } diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js index 369a906a..370cc6af 100644 --- a/e2e/suites/epics/epic-dashboard.e2e.js +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -64,4 +64,15 @@ describe('Epics Dashboard', function(){ expect(currentColumns).to.be.above(newColumns); }); + it.only('create Epic', async function() { + let date = Date.now(); + let description = Math.random().toString(36).substring(7); + let epic = epicsHelper.epic(); + let currentEpicsNum = await epic.getEpics(); + await epic.createEpic(date, description); + let newEpicsNum = await epic.getEpics(); + console.log(currentEpicsNum, newEpicsNum); + expect(newEpicsNum).to.be.above(currentEpicsNum); + }); + }) From f84087a0efb711820eb43d52e2f6ac373864fbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 23 Aug 2016 14:57:35 +0200 Subject: [PATCH 048/137] Fix new header directive URL --- .../belong-to-epics.directive.coffee | 4 +- .../header/detail-header.directive.coffee | 2 +- app/partials/issue/issues-detail.jade | 1 + app/partials/task/task-detail.jade | 59 +------------------ app/partials/us/us-detail.jade | 1 + 5 files changed, 7 insertions(+), 60 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 455adc1d..1e7d8fa7 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -29,7 +29,9 @@ BelongToEpicsDirective = () -> scope.project = Immutable.fromJS(scope.project) scope.getTemplateUrl = () -> - return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + if attrs.format + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return "components/belong-to-epics/belong-to-epics-pill.html" return { link: link, diff --git a/app/modules/components/detail/header/detail-header.directive.coffee b/app/modules/components/detail/header/detail-header.directive.coffee index 81f0f9a4..10267d0f 100644 --- a/app/modules/components/detail/header/detail-header.directive.coffee +++ b/app/modules/components/detail/header/detail-header.directive.coffee @@ -36,7 +36,7 @@ DetailHeaderDirective = () -> requiredPerm: "@" }, controllerAs: "vm", - templateUrl:"stories/header/story-header.html" + templateUrl:"components/detail/header/detail-header.html" } diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 771cf4fd..e85c9a3d 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -23,6 +23,7 @@ div.wrapper( required-perm="modify_issue" ng-class="{blocked: issue.is_blocked}" ng-if="project && issue" + format="text" ) .subheader tg-tag-line.tags-block( diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 59b6e4be..2a331a5b 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -32,65 +32,8 @@ div.wrapper( required-perm="modify_task" ng-class="{blocked: task.is_blocked}" ng-if="project && task" + type="text" ) - //- h3.us-related-task(ng-if="us") - //- | {{ 'TASK.OWNER_US'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - //- ) - //- span(tg-bo-ref="us.ref") - //- span(tg-bo-bind="us.subject") - //- div.us-title(ng-class="{blocked: task.is_blocked}") - //- h2.us-title-text - //- span.us-number(tg-bo-ref="task.ref") - //- span.us-name( - //- tg-editable-subject - //- ng-model="task" - //- required-perm="modify_task" - //- ) - //- - //- h3.us-related-task(ng-if="us") - //- | {{ 'TASK.OWNER_US'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - //- ) - //- span(tg-bo-ref="us.ref") - //- span(tg-bo-bind="us.subject") - //- - //- p.external-reference(ng-if="task.external_reference") - //- a( - //- tg-bo-href="task.external_reference[1]", - //- target="_blank" - //- title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" - //- ) - //- | {{ "TASK.ORIGIN_US"| translate }} - //- span {{ task.external_reference[1] }} - //- - //- p.block-desc-container(ng-show="task.is_blocked") - //- span.block-description-title(translate="COMMON.BLOCKED") - //- span.block-description( - //- ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" - //- ) - //- - //- div.issue-nav - //- a( - //- ng-show="previousUrl" - //- tg-bo-href="previousUrl" - //- title="{{'TASK.PREVIOUS' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-left") - //- a( - //- ng-show="nextUrl" - //- tg-bo-href="nextUrl" - //- title="{{'TASK.NEXT' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="task && project" diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index b35b512d..6c04fb9f 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -32,6 +32,7 @@ div.wrapper( required-perm="modify_us" ng-class="{blocked: us.is_blocked}" ng-if="project && us" + type="text" ) .subheader tg-tag-line.tags-block( From ad01386a797ae8158030d9c3fe238613d29bd058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 14:36:16 +0200 Subject: [PATCH 049/137] Epic in Admin > Project > Default Values --- .../modules/admin/project-profile.coffee | 8 ++-- app/locales/taiga/locale-en.json | 9 +++-- .../modules/admin/default-values.jade | 40 ++++++++++++------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 06fba778..4b8c247d 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -89,16 +89,16 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.projectId = project.id @scope.project = project - @scope.pointsList = _.sortBy(project.points, "order") + @scope.epicStatusList = _.sortBy(project.epic_statuses, "order") @scope.usStatusList = _.sortBy(project.us_statuses, "order") + @scope.pointsList = _.sortBy(project.points, "order") @scope.taskStatusList = _.sortBy(project.task_statuses, "order") - @scope.prioritiesList = _.sortBy(project.priorities, "order") - @scope.severitiesList = _.sortBy(project.severities, "order") @scope.issueTypesList = _.sortBy(project.issue_types, "order") @scope.issueStatusList = _.sortBy(project.issue_statuses, "order") + @scope.prioritiesList = _.sortBy(project.priorities, "order") + @scope.severitiesList = _.sortBy(project.severities, "order") @scope.$emit('project:loaded', project) - @scope.projectTags = _.map @scope.project.tags, (it) => return [it, @scope.project.tags_colors[it]] diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index c79fd001..2feafc66 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -720,13 +720,14 @@ "DEFAULT_DELETE_MESSAGE": "the invitation to {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Default value for points selector", - "LABEL_US": "Default value for US status selector", "LABEL_TASK_STATUS": "Default value for task status selector", - "LABEL_PRIORITY": "Default value for priority selector", - "LABEL_SEVERITY": "Default value for severity selector", "LABEL_ISSUE_TYPE": "Default value for issue type selector", - "LABEL_ISSUE_STATUS": "Default value for issue status selector" + "LABEL_ISSUE_STATUS": "Default value for issue status selector", + "LABEL_PRIORITY": "Default value for priority selector", + "LABEL_SEVERITY": "Default value for severity selector" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Write a name for the new status" diff --git a/app/partials/includes/modules/admin/default-values.jade b/app/partials/includes/modules/admin/default-values.jade index a8ab9e6e..cd3b3745 100644 --- a/app/partials/includes/modules/admin/default-values.jade +++ b/app/partials/includes/modules/admin/default-values.jade @@ -1,20 +1,40 @@ section.default-values form + + //- Epics + fieldset + label(for="default-value-epic", translate="ADMIN.DEFAULT_VALUES.LABEL_EPIC_STATUS") + select(id="default-value-epic", ng-model="project.default_epic_status", + ng-options="s.id as s.name for s in epicStatusList") + + //- User stories + fieldset + label(for="default-value-us", translate="ADMIN.DEFAULT_VALUES.LABEL_US_STATUS") + select(id="default-value-us", ng-model="project.default_us_status", + ng-options="s.id as s.name for s in usStatusList") + fieldset label(for="default-points", translate="ADMIN.DEFAULT_VALUES.LABEL_POINTS") select(id="default-points", ng-model="project.default_points", ng-options="s.id as s.name for s in pointsList") - fieldset - label(for="default-value-us", translate="ADMIN.DEFAULT_VALUES.LABEL_US") - select(id="default-value-us", ng-model="project.default_us_status", - ng-options="s.id as s.name for s in usStatusList") - + //- Tasks fieldset label(for="default-value-task", translate="ADMIN.DEFAULT_VALUES.LABEL_TASK_STATUS") select(id="default-value-task", ng-model="project.default_task_status", ng-options="s.id as s.name for s in taskStatusList") + //- Issues + fieldset + label(for="default-value-issue-type", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_TYPE") + select(id="default-value-issue-type", ng-model="project.default_issue_type", + ng-options="s.id as s.name for s in issueTypesList") + + fieldset + label(for="default-value-issue-status", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_STATUS") + select(id="default-value-issue-status", ng-model="project.default_issue_status", + ng-options="s.id as s.name for s in issueStatusList") + fieldset label(for="default-value-priority", translate="ADMIN.DEFAULT_VALUES.LABEL_PRIORITY") select(id="default-value-priority", ng-model="project.default_priority", @@ -25,16 +45,6 @@ section.default-values select(id="default-value-severity", ng-model="project.default_severity", ng-options="s.id as s.name for s in severitiesList") - fieldset - label(for="default-value-issue-type", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_TYPE") - select(id="default-value-issue-type", ng-model="project.default_issue_type", - ng-options="s.id as s.name for s in issueTypesList") - - fieldset - label(for="default-value-issue-status", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_STATUS") - select(id="default-value-issue-status", ng-model="project.default_issue_status", - ng-options="s.id as s.name for s in issueStatusList") - fieldset button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}") span(translate="COMMON.SAVE") From a9fca5e43ba90c1ad815cebcbe661377f96938f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 15:21:57 +0200 Subject: [PATCH 050/137] Epic in Admin > Project > Reports --- .../modules/admin/project-profile.coffee | 20 +++++++++++++++++++ app/coffee/modules/resources.coffee | 1 + app/coffee/modules/resources/projects.coffee | 12 +++++++---- app/locales/taiga/locale-en.json | 1 + app/partials/admin/admin-project-reports.jade | 1 + 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 4b8c247d..65eb759c 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -425,6 +425,10 @@ class CsvExporterController extends taiga.Controller @._generateUuid() +class CsvExporterEpicsController extends CsvExporterController + type: "epics" + + class CsvExporterUserstoriesController extends CsvExporterController type: "userstories" @@ -437,6 +441,7 @@ class CsvExporterIssuesController extends CsvExporterController type: "issues" +module.controller("CsvExporterEpicsController", CsvExporterEpicsController) module.controller("CsvExporterUserstoriesController", CsvExporterUserstoriesController) module.controller("CsvExporterTasksController", CsvExporterTasksController) module.controller("CsvExporterIssuesController", CsvExporterIssuesController) @@ -446,6 +451,21 @@ module.controller("CsvExporterIssuesController", CsvExporterIssuesController) ## CSV Directive ############################################################################# +CsvEpicDirective = ($translate) -> + link = ($scope) -> + $scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_EPIC" + + return { + controller: "CsvExporterEpicsController", + controllerAs: "ctrl", + templateUrl: "admin/project-csv.html", + link: link, + scope: true + } + +module.directive("tgCsvEpic", ["$translate", CsvEpicDirective]) + + CsvUsDirective = ($translate) -> link = ($scope) -> $scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_US" diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index df127c5e..afb7043d 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -161,6 +161,7 @@ urls = { "webhooklogs-resend": "/webhooklogs/%s/resend" # Reports - CSV + "epics-csv": "/epics/csv?uuid=%s" "userstories-csv": "/userstories/csv?uuid=%s" "tasks-csv": "/tasks/csv?uuid=%s" "issues-csv": "/issues/csv?uuid=%s" diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index 108012cd..dcb48a72 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -61,18 +61,22 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> url = $urls.resolve("bulk-update-projects-order") return $http.post(url, bulkData) + service.regenerate_epics_csv_uuid = (projectId) -> + url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_epics_csv_uuid" + return $http.post(url) + service.regenerate_userstories_csv_uuid = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_userstories_csv_uuid" return $http.post(url) - service.regenerate_issues_csv_uuid = (projectId) -> - url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_issues_csv_uuid" - return $http.post(url) - service.regenerate_tasks_csv_uuid = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_tasks_csv_uuid" return $http.post(url) + service.regenerate_issues_csv_uuid = (projectId) -> + url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_issues_csv_uuid" + return $http.post(url) + service.leave = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/leave" return $http.post(url) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 2feafc66..5e8d7ed9 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -559,6 +559,7 @@ "REGENERATE_SUBTITLE": "You going to change the CSV data access url. The previous url will be disabled. Are you sure?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "user stories reports", "SECTION_TITLE_TASK": "tasks reports", "SECTION_TITLE_ISSUE": "issues reports", diff --git a/app/partials/admin/admin-project-reports.jade b/app/partials/admin/admin-project-reports.jade index 1ad4c439..a3e669b9 100644 --- a/app/partials/admin/admin-project-reports.jade +++ b/app/partials/admin/admin-project-reports.jade @@ -18,6 +18,7 @@ div.wrapper( p(translate="ADMIN.REPORTS.DESCRIPTION") + div.admin-attributes-section(tg-csv-epic) div.admin-attributes-section(tg-csv-us) div.admin-attributes-section(tg-csv-task) div.admin-attributes-section(tg-csv-issue) From 605ff1480e9e92085c39b3fde5a3cccc5f3672d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 16:52:46 +0200 Subject: [PATCH 051/137] Epic in Admin > Attributes > Status --- app/coffee/modules/resources.coffee | 2 + app/coffee/modules/resources/epics.coffee | 55 +++++++++++++++++++ app/locales/taiga/locale-en.json | 3 +- .../admin/admin-project-values-status.jade | 6 ++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 app/coffee/modules/resources/epics.coffee diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index afb7043d..ae917665 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -81,6 +81,7 @@ urls = { "project-transfer-start": "/projects/%s/transfer_start" # Project Values - Choises + "epic-statuses": "/epic-statuses" "userstory-statuses": "/userstory-statuses" "points": "/points" "task-statuses": "/task-statuses" @@ -223,6 +224,7 @@ module.run([ "$tgRolesResourcesProvider", "$tgUserSettingsResourcesProvider", "$tgSprintsResourcesProvider", + "$tgEpicsResourcesProvider", "$tgUserstoriesResourcesProvider", "$tgTasksResourcesProvider", "$tgIssuesResourcesProvider", diff --git a/app/coffee/modules/resources/epics.coffee b/app/coffee/modules/resources/epics.coffee new file mode 100644 index 00000000..9793f485 --- /dev/null +++ b/app/coffee/modules/resources/epics.coffee @@ -0,0 +1,55 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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: modules/resources/epics.coffee +### + + +taiga = @.taiga + +generateHash = taiga.generateHash + + +resourceProvider = ($repo, $storage) -> + service = {} + hashSuffix = "epics-queryparams" + + service.listValues = (projectId, type) -> + params = {"project": projectId} + service.storeQueryParams(projectId, params) + return $repo.queryMany(type, params) + + service.storeQueryParams = (projectId, params) -> + ns = "#{projectId}:#{hashSuffix}" + hash = generateHash([projectId, ns]) + $storage.set(hash, params) + + service.getQueryParams = (projectId) -> + ns = "#{projectId}:#{hashSuffix}" + hash = generateHash([projectId, ns]) + return $storage.get(hash) or {} + + return (instance) -> + instance.epics = service + + +module = angular.module("taigaResources") +module.factory("$tgEpicsResourcesProvider", ["$tgRepo", "$tgStorage", resourceProvider]) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 5e8d7ed9..7fa886c2 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -609,7 +609,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specify the statuses your user stories, tasks and issues will go through", - "US_TITLE": "US Statuses", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Task Statuses", "ISSUE_TITLE": "Issue Statuses" }, diff --git a/app/partials/admin/admin-project-values-status.jade b/app/partials/admin/admin-project-values-status.jade index 8eab5f9b..64540f3a 100644 --- a/app/partials/admin/admin-project-values-status.jade +++ b/app/partials/admin/admin-project-values-status.jade @@ -15,6 +15,12 @@ div.wrapper(ng-controller="ProjectValuesSectionController", include ../includes/components/mainTitle p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_STATUS.SUBTITLE") + div.admin-attributes-section(tg-project-values, type="epic-statuses", + ng-controller="ProjectValuesController as ctrl", + ng-init="section='admin'; resource='epics'; type='epic-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.EPIC_TITLE'" + objName="status") + include ../includes/modules/admin/project-status + div.admin-attributes-section(tg-project-values, type="userstory-statuses", ng-controller="ProjectValuesController as ctrl", ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.US_TITLE'", From fbfde871e2992cc64c371a5490e9d597dccede4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 17:04:43 +0200 Subject: [PATCH 052/137] Epic in Admin > Attributes > Custom Fields --- app/coffee/modules/resources.coffee | 6 ++++-- .../modules/resources/custom-attributes-values.coffee | 3 +++ app/coffee/modules/resources/custom-attributes.coffee | 3 +++ app/locales/taiga/locale-en.json | 2 ++ app/partials/admin/admin-project-values-custom-fields.jade | 7 +++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index ae917665..9085f7da 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -146,14 +146,16 @@ urls = { "attachments/wiki_page": "/wiki/attachments" # Custom Attributess + "custom-attributes/epic": "/epic-custom-attributes" "custom-attributes/userstory": "/userstory-custom-attributes" - "custom-attributes/issue": "/issue-custom-attributes" "custom-attributes/task": "/task-custom-attributes" + "custom-attributes/issue": "/issue-custom-attributes" # Custom Attributess - Values + "custom-attributes-values/epic": "/epics/custom-attributes-values" "custom-attributes-values/userstory": "/userstories/custom-attributes-values" - "custom-attributes-values/issue": "/issues/custom-attributes-values" "custom-attributes-values/task": "/tasks/custom-attributes-values" + "custom-attributes-values/issue": "/issues/custom-attributes-values" # Webhooks "webhooks": "/webhooks" diff --git a/app/coffee/modules/resources/custom-attributes-values.coffee b/app/coffee/modules/resources/custom-attributes-values.coffee index f5a38b2c..904d506e 100644 --- a/app/coffee/modules/resources/custom-attributes-values.coffee +++ b/app/coffee/modules/resources/custom-attributes-values.coffee @@ -29,6 +29,9 @@ resourceProvider = ($repo) -> return $repo.queryOne(resource, objectId) service = { + epic: { + get: (objectId) -> _get(objectId, "custom-attributes-values/epic") + } userstory: { get: (objectId) -> _get(objectId, "custom-attributes-values/userstory") } diff --git a/app/coffee/modules/resources/custom-attributes.coffee b/app/coffee/modules/resources/custom-attributes.coffee index 520ec2d2..88ae4872 100644 --- a/app/coffee/modules/resources/custom-attributes.coffee +++ b/app/coffee/modules/resources/custom-attributes.coffee @@ -32,6 +32,9 @@ resourceProvider = ($repo) -> return $repo.queryMany(resource, {project: projectId}) service = { + epic:{ + list: (projectId) -> _list(projectId, "custom-attributes/epic") + } userstory:{ list: (projectId) -> _list(projectId, "custom-attributes/userstory") } diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 7fa886c2..298d93a3 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -572,6 +572,8 @@ "CUSTOM_FIELDS": { "TITLE": "Custom Fields", "SUBTITLE": "Specify the custom fields for your user stories, tasks and issues", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "User stories custom fields", "US_ADD": "Add a custom field in user stories", "TASK_DESCRIPTION": "Tasks custom fields", diff --git a/app/partials/admin/admin-project-values-custom-fields.jade b/app/partials/admin/admin-project-values-custom-fields.jade index 691079b5..19cb292b 100644 --- a/app/partials/admin/admin-project-values-custom-fields.jade +++ b/app/partials/admin/admin-project-values-custom-fields.jade @@ -17,6 +17,13 @@ div.wrapper( include ../includes/components/mainTitle p.admin-subtitle(translate="ADMIN.CUSTOM_FIELDS.SUBTITLE") + div.admin-attributes-section( + tg-project-custom-attributes, + ng-controller="ProjectCustomAttributesController as ctrl", + ng-init="type='epic'; customFieldSectionTitle='ADMIN.CUSTOM_FIELDS.EPIC_DESCRIPTION'; customFieldButtonTitle='ADMIN.CUSTOM_FIELDS.EPIC_ADD'" + ) + include ../includes/modules/admin/admin-custom-attributes + div.admin-attributes-section( tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", From dcd26a954ec45d2908d15193aeca5bb6540f8c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 19:14:33 +0200 Subject: [PATCH 053/137] Epic in Admin > Permissions --- app/coffee/modules/admin/roles.coffee | 12 ++++++++++++ app/locales/taiga/locale-en.json | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index f1815b99..c282f7c6 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -353,6 +353,18 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> categories = [] + epicPermissions = [ + { key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"} + { key: "add_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.ADD_EPICS"} + { key: "modify_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.MODIFY_EPICS"} + { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_EPICS"} + { key: "delete_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.DELETE_EPICS"} + ] + categories.push({ + name: "COMMON.PERMISIONS_CATEGORIES.EPICS.NAME" , + permissions: setActivePermissions(epicPermissions) + }) + milestonePermissions = [ { key: "view_milestones", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.VIEW_SPRINTS"} { key: "add_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.ADD_SPRINTS"} diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 298d93a3..3403ff03 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -256,6 +256,14 @@ "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "View sprints", From f8dd7408d2eee562c50964329f96869adf9268f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 25 Aug 2016 15:24:57 +0200 Subject: [PATCH 054/137] Use new tag-line-common component in Add-Epic form --- .../tags/color-selector/color-selector.jade | 2 +- .../tag-line-common.directive.coffee | 2 +- .../create-epic/create-epic.controller.coffee | 23 ++++++++++++++++++- .../epics/create-epic/create-epic.jade | 22 +++++++++++------- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/tags/color-selector/color-selector.jade index c66e83aa..54402386 100644 --- a/app/modules/components/tags/color-selector/color-selector.jade +++ b/app/modules/components/tags/color-selector/color-selector.jade @@ -4,7 +4,7 @@ ng-class="{'empty-color': !vm.color}" ng-style="{'background': vm.color}" ) - .color-selector-dropdown(ng-show="vm.displaycolorList") + .color-selector-dropdown(ng-if="vm.displaycolorList") ul.color-selector-dropdown-list.e2e-color-dropdown li.color-selector-option( ng-repeat="color in vm.colorList" diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee index 536c229a..1fd9e8f6 100644 --- a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee +++ b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee @@ -33,7 +33,7 @@ TagLineCommonDirective = () -> ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors) el.on "keydown", ".tag-input", (event) -> - if event.keyCode == 27 && ctrl.newTag.name.length + if event.keyCode == 27 ctrl.addTag = false ctrl.newTag.name = "" diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index c80ef89c..638a2480 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -17,6 +17,9 @@ # File: create-epic.controller.coffee ### +taiga = @.taiga +trim = taiga.trim + module = angular.module("taigaEpics") class CreateEpicController @@ -25,11 +28,29 @@ class CreateEpicController ] constructor: (@rs) -> + @.newEpic = { + color: null + projecti: @.project.id + status: @.project.default_epic_status + tags: [] + } @.attachments = Immutable.List() createEpic: () -> - @.newEpic.project = @.project.id return @rs.epics.post(@.newEpic).then () => @.onReloadEpics() + selectColor: (color) -> + @.newEpic.color = color + + addTag: (name, color) -> + name = trim(name.toLowerCase()) + + if not _.find(@.newEpic.tags, (it) -> it[0] == name) + @.newEpic.tags.push([name, color]) + + deleteTag: (tag) -> + _.remove @.newEpic.tags, (it) -> it[0] == tag[0] + + module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 7d7120ce..7d1e7e73 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -6,8 +6,10 @@ tg-lightbox-close ng-submit="vm.createEpic()" ) fieldset - // TODO ADD COLOR SELECTOR - tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") + tg-color-selector( + color="vm.newEpic.color", + on-select-color="vm.selectColor(color)" + ) input.e2e-create-epic-subject( type="text" name="subject" @@ -25,13 +27,17 @@ tg-lightbox-close ) option( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-value="status.id" + ng-value="::status.id" ng-selected="vm.project.default_epic_status" - ) {{status.name}} - fieldset.tags-block( - tg-lb-tag-line - ng-model="vm.newEpic.tags" - ) + ) {{::status.name}} + 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)" + ) fieldset textarea.e2e-create-epic-description( ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" From 3d858cf82aedc6006a987b4cd0b28dd39fd39598 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 26 Aug 2016 12:11:12 +0200 Subject: [PATCH 055/137] Epic detail --- app/coffee/app.coffee | 14 +- app/coffee/modules/common/filters.coffee | 26 ++ app/coffee/modules/epics.coffee | 2 +- app/coffee/modules/epics/detail.coffee | 337 ++++++++++++++++++ app/coffee/modules/resources.coffee | 8 + app/coffee/modules/resources/epics.coffee | 26 +- app/locales/taiga/locale-en.json | 21 ++ .../belong-to-epics/belong-to-epics-text.jade | 2 +- .../belong-to-epics.directive.coffee | 3 - .../detail/header/detail-header.jade | 2 - .../watch-button.controller.coffee | 3 +- .../epics/dashboard/epic-row/epic-row.jade | 2 +- .../related-userstories-controller.coffee | 33 ++ ...lated-userstories-create.controller.coffee | 92 +++++ ...-userstories-create.controller.spec.coffee | 185 ++++++++++ ...elated-userstories-create.directive.coffee | 79 ++++ .../related-userstories-create.jade | 153 ++++++++ .../related-userstories-create.scss | 78 ++++ ...related-userstories.controller.spec.coffee | 66 ++++ .../related-userstories.directive.coffee | 37 ++ .../related-userstories.jade | 23 ++ .../related-userstories.scss | 147 ++++++++ .../related-userstory-row.controller.coffee | 63 ++++ ...lated-userstory-row.controller.spec.coffee | 169 +++++++++ .../related-userstory-row.directive.coffee | 42 +++ .../related-userstory-row.jade | 44 +++ .../resources/epics-resource.service.coffee | 25 ++ .../userstories-resource.service.coffee | 16 + app/partials/epic/epic-detail.jade | 127 +++++++ app/styles/modules/common/wizard.scss | 1 - conf.e2e.js | 82 ++--- e2e/helpers/detail-helper.js | 42 ++- e2e/helpers/epic-detail-helper.js | 76 ++++ ...cs-helper.js => epics-dashboard-helper.js} | 13 +- e2e/helpers/index.js | 2 + e2e/helpers/us-detail-helper.js | 39 -- e2e/shared/detail.js | 38 +- .../admin/attributes/custom-fields.e2e.js | 63 +++- e2e/suites/admin/members.e2e.js | 2 +- e2e/suites/epics/epic-dashboard.e2e.js | 38 +- e2e/suites/epics/epic-detail.e2e.js | 100 ++++++ .../user-stories/user-story-detail.e2e.js | 30 +- e2e/utils/nav.js | 14 + gulpfile.js | 1 + run-e2e.js | 1 + 45 files changed, 2214 insertions(+), 153 deletions(-) create mode 100644 app/coffee/modules/epics/detail.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss create mode 100644 app/modules/epics/related-userstories/related-userstories.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories.jade create mode 100644 app/modules/epics/related-userstories/related-userstories.scss create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade create mode 100644 app/partials/epic/epic-detail.jade create mode 100644 e2e/helpers/epic-detail-helper.js rename e2e/helpers/{epics-helper.js => epics-dashboard-helper.js} (94%) create mode 100644 e2e/suites/epics/epic-detail.e2e.js diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 97c3efbd..fb81877e 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -46,7 +46,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $animateProvider.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/) - # wait until the trasnlation is ready to resolve the page + # wait until the translation is ready to resolve the page originalWhen = $routeProvider.when $routeProvider.when = (path, route) -> @@ -162,6 +162,15 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + # Epics + $routeProvider.when("/project/:pslug/epic/:epicref", + { + templateUrl: "epic/epic-detail.html", + loader: true, + section: "epics" + } + ) + $routeProvider.when("/project/:pslug/backlog", { templateUrl: "backlog/backlog.html", @@ -793,6 +802,7 @@ modules = [ "taigaPlugins", "taigaIntegrations", "taigaComponents", + # new modules "taigaProfile", "taigaHome", @@ -801,7 +811,7 @@ modules = [ "taigaDiscover", "taigaHistory", "taigaWikiHistory", - 'taigaEpics', + "taigaEpics", # template cache "templates", diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index 7590b45c..17696c55 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -74,3 +74,29 @@ sizeFormat = => return @.taiga.sizeFormat module.filter("sizeFormat", sizeFormat) + + +toMutableFilter = -> + toMutable = (js) -> + return js.toJS() + + memoizedMutable = _.memoize(toMutable) + + return (input) -> + if input instanceof Immutable.List + return memoizedMutable(input) + + return input + +module.filter("toMutable", toMutableFilter) + + +byRefFilter = ($filterFilter)-> + return (userstories, filter) -> + if filter?.startsWith("#") + cleanRef= filter.substr(1) + return _.filter(userstories, (us) => String(us.ref).startsWith(cleanRef)) + + return $filterFilter(userstories, filter) + +module.filter("byRef", ["filterFilter", byRefFilter]) diff --git a/app/coffee/modules/epics.coffee b/app/coffee/modules/epics.coffee index 15941253..743e70d4 100644 --- a/app/coffee/modules/epics.coffee +++ b/app/coffee/modules/epics.coffee @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: modules/projects.coffee +# File: modules/epics.coffee ### module = angular.module("taigaEpics", []) diff --git a/app/coffee/modules/epics/detail.coffee b/app/coffee/modules/epics/detail.coffee new file mode 100644 index 00000000..f5a35849 --- /dev/null +++ b/app/coffee/modules/epics/detail.coffee @@ -0,0 +1,337 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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: modules/epics/detail.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf +toString = @.taiga.toString +joinStr = @.taiga.joinStr +groupBy = @.taiga.groupBy +bindOnce = @.taiga.bindOnce +bindMethods = @.taiga.bindMethods + +module = angular.module("taigaEpics") + +############################################################################# +## Epic Detail Controller +############################################################################# + +class EpicDetailController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "$tgConfirm", + "$tgResources", + "tgResources" + "$routeParams", + "$q", + "$tgLocation", + "$log", + "tgAppMetaService", + "$tgAnalytics", + "$tgNavUrls", + "$translate", + "$tgQueueModelTransformation", + "tgErrorHandlingService" + ] + + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @rs2, @params, @q, @location, + @log, @appMetaService, @analytics, @navUrls, @translate, @modelTransform, @errorHandlingService) -> + bindMethods(@) + + @scope.epicRef = @params.epicref + @scope.sectionName = @translate.instant("EPIC.SECTION_NAME") + @.initializeEventHandlers() + + promise = @.loadInitialData() + + # On Success + promise.then => + @._setMeta() + @.initializeOnDeleteGoToUrl() + + # On Error + promise.then null, @.onInitialDataError.bind(@) + + _setMeta: -> + title = @translate.instant("EPIC.PAGE_TITLE", { + epicRef: "##{@scope.epic.ref}" + epicSubject: @scope.epic.subject + projectName: @scope.project.name + }) + description = @translate.instant("EPIC.PAGE_DESCRIPTION", { + epicStatus: @scope.statusById[@scope.epic.status]?.name or "--" + epicDescription: angular.element(@scope.epic.description_html or "").text() + }) + @appMetaService.setAll(title, description) + + initializeEventHandlers: -> + @scope.$on "attachment:create", => + @analytics.trackEvent("attachment", "create", "create attachment on epic", 1) + + @scope.$on "comment:new", => + @.loadEpic() + + @scope.$on "custom-attributes-values:edit", => + @rootscope.$broadcast("object:updated") + + initializeOnDeleteGoToUrl: -> + ctx = {project: @scope.project.slug} + @scope.onDeleteGoToUrl = @navUrls.resolve("project-epics", ctx) + + loadProject: -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id + @scope.project = project + @scope.immutableProject = Immutable.fromJS(project._attrs) + @scope.$emit('project:loaded', project) + @scope.statusList = project.epic_statuses + @scope.statusById = groupBy(project.epic_statuses, (x) -> x.id) + return project + + loadEpic: -> + return @rs.epics.getByRef(@scope.projectId, @params.epicref).then (epic) => + @scope.epic = epic + @scope.immutableEpic = Immutable.fromJS(epic._attrs) + @scope.epicId = epic.id + @scope.commentModel = epic + + @modelTransform.setObject(@scope, 'epic') + + if @scope.epic.neighbors.previous?.ref? + ctx = { + project: @scope.project.slug + ref: @scope.epic.neighbors.previous.ref + } + @scope.previousUrl = @navUrls.resolve("project-epics-detail", ctx) + + if @scope.epic.neighbors.next?.ref? + ctx = { + project: @scope.project.slug + ref: @scope.epic.neighbors.next.ref + } + @scope.nextUrl = @navUrls.resolve("project-epics-detail", ctx) + + loadUserstories: -> + return @rs2.userstories.listInEpic(@scope.epicId).then (data) => + @scope.userstories = data + + loadInitialData: -> + promise = @.loadProject() + return promise.then (project) => + @.fillUsersAndRoles(project.members, project.roles) + @.loadEpic().then(=> @.loadUserstories()) + + ### + # Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button. + # See app/modules/components/vote-button for more info + ### + onUpvote: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.upvote(@scope.epicId).then(onSuccess, onError) + + onDownvote: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.downvote(@scope.epicId).then(onSuccess, onError) + + ### + # Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button. + # See app/modules/components/watch-button for more info + ### + onWatch: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.watch(@scope.epicId).then(onSuccess, onError) + + onUnwatch: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.unwatch(@scope.epicId).then(onSuccess, onError) + + onSelectColor: (color) -> + onSelectColorSuccess = () => + @rootscope.$broadcast("object:updated") + @confirm.notify('success') + + onSelectColorError = () => + @confirm.notify('error') + + transform = @modelTransform.save (epic) -> + epic.color = color + return epic + + return transform.then(onSelectColorSuccess, onSelectColorError) + +module.controller("EpicDetailController", EpicDetailController) + + +############################################################################# +## Epic status display directive +############################################################################# + +EpicStatusDisplayDirective = ($template, $compile) -> + # Display if an epic is open or closed and its status. + # + # Example: + # tg-epic-status-display(ng-model="epic") + # + # Requirements: + # - Epic object (ng-model) + # - scope.statusById object + + template = $template.get("common/components/status-display.html", true) + + link = ($scope, $el, $attrs) -> + render = (epic) -> + status = $scope.statusById[epic.status] + + html = template({ + is_closed: status.is_closed + status: status + }) + + html = $compile(html)($scope) + $el.html(html) + + $scope.$watch $attrs.ngModel, (epic) -> + render(epic) if epic? + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + require: "ngModel" + } + +module.directive("tgEpicStatusDisplay", ["$tgTemplate", "$compile", EpicStatusDisplayDirective]) + + +############################################################################# +## Epic status button directive +############################################################################# + +EpicStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $compile, $translate, $template) -> + # Display the status of epic and you can edit it. + # + # Example: + # tg-epic-status-button(ng-model="epic") + # + # Requirements: + # - Epic object (ng-model) + # - scope.statusById object + # - $scope.project.my_permissions + + template = $template.get("common/components/status-button.html", true) + + link = ($scope, $el, $attrs, $model) -> + isEditable = -> + return $scope.project.my_permissions.indexOf("modify_epic") != -1 + + render = (epic) => + status = $scope.statusById[epic.status] + + html = $compile(template({ + status: status + statuses: $scope.statusList + editable: isEditable() + }))($scope) + + $el.html(html) + + save = (status) -> + currentLoading = $loading() + .target($el) + .start() + + transform = $modelTransform.save (epic) -> + epic.status = status + + return epic + + onSuccess = -> + $rootScope.$broadcast("object:updated") + currentLoading.finish() + + onError = -> + $confirm.notify("error") + currentLoading.finish() + + transform.then(onSuccess, onError) + + $el.on "click", ".js-edit-status", (event) -> + event.preventDefault() + event.stopPropagation() + return if not isEditable() + + $el.find(".pop-status").popover().open() + + $el.on "click", ".status", (event) -> + event.preventDefault() + event.stopPropagation() + return if not isEditable() + + target = angular.element(event.currentTarget) + + $.fn.popover().closeAll() + + save(target.data("status-id")) + + $scope.$watch () -> + return $model.$modelValue?.status + , () -> + epic = $model.$modelValue + render(epic) if epic + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + require: "ngModel" + } + +module.directive("tgEpicStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", + "$compile", "$translate", "$tgTemplate", EpicStatusButtonDirective]) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 9085f7da..20b9fa9a 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -95,6 +95,12 @@ urls = { # Epics "epics": "/epics" + "epic-upvote": "/epics/%s/upvote" + "epic-downvote": "/epics/%s/downvote" + "epic-watch": "/epics/%s/watch" + "epic-unwatch": "/epics/%s/unwatch" + "epic-related-userstories": "/epics/%s/related_userstories" + "epic-related-userstories-bulk-create": "/epics/%s/related_userstories/bulk_create" # User stories "userstories": "/userstories" @@ -134,12 +140,14 @@ urls = { "wiki-links": "/wiki-links" # History + "history/epic": "/history/epic" "history/us": "/history/userstory" "history/issue": "/history/issue" "history/task": "/history/task" "history/wiki": "/history/wiki/%s" # Attachments + "attachments/epic": "/epics/attachments" "attachments/us": "/userstories/attachments" "attachments/issue": "/issues/attachments" "attachments/task": "/tasks/attachments" diff --git a/app/coffee/modules/resources/epics.coffee b/app/coffee/modules/resources/epics.coffee index 9793f485..480395ce 100644 --- a/app/coffee/modules/resources/epics.coffee +++ b/app/coffee/modules/resources/epics.coffee @@ -28,10 +28,16 @@ taiga = @.taiga generateHash = taiga.generateHash -resourceProvider = ($repo, $storage) -> +resourceProvider = ($repo, $http, $urls, $storage) -> service = {} hashSuffix = "epics-queryparams" + service.getByRef = (projectId, ref) -> + params = service.getQueryParams(projectId) + params.project = projectId + params.ref = ref + return $repo.queryOne("epics", "by_ref", params) + service.listValues = (projectId, type) -> params = {"project": projectId} service.storeQueryParams(projectId, params) @@ -47,9 +53,25 @@ resourceProvider = ($repo, $storage) -> hash = generateHash([projectId, ns]) return $storage.get(hash) or {} + service.upvote = (epicId) -> + url = $urls.resolve("epic-upvote", epicId) + return $http.post(url) + + service.downvote = (epicId) -> + url = $urls.resolve("epic-downvote", epicId) + return $http.post(url) + + service.watch = (epicId) -> + url = $urls.resolve("epic-watch", epicId) + return $http.post(url) + + service.unwatch = (epicId) -> + url = $urls.resolve("epic-unwatch", epicId) + return $http.post(url) + return (instance) -> instance.epics = service module = angular.module("taigaResources") -module.factory("$tgEpicsResourcesProvider", ["$tgRepo", "$tgStorage", resourceProvider]) +module.factory("$tgEpicsResourcesProvider", ["$tgRepo","$tgHttp", "$tgUrls", "$tgStorage", resourceProvider]) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 3403ff03..cd73c545 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -47,6 +47,7 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", "CARD": { "ASSIGN_TO": "Assign To", "EDIT": "Edit card" @@ -1061,6 +1062,26 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with a user story", + "RELATED_WITH": "Related with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "Whats' the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade index f8b935d0..db9614b9 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-text.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -6,5 +6,5 @@ span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id' ) a.belong-to-epic-text( href="" - tg-nav="project-epics-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=epic.getIn(['project', 'slug']),ref=epic.get('ref')" ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 1e7d8fa7..08d4ac43 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -25,9 +25,6 @@ BelongToEpicsDirective = () -> if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) - if scope.project && !scope.project.isIterable - scope.project = Immutable.fromJS(scope.project) - scope.getTemplateUrl = () -> if attrs.format return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" diff --git a/app/modules/components/detail/header/detail-header.jade b/app/modules/components/detail/header/detail-header.jade index 82615b20..317065c0 100644 --- a/app/modules/components/detail/header/detail-header.jade +++ b/app/modules/components/detail/header/detail-header.jade @@ -41,7 +41,6 @@ ng-if="::vm.item.epics" epics="::vm.item.epics" format="text" - project="project" ) //- Task belongs to US @@ -60,7 +59,6 @@ ng-if="::vm.item.user_story_extra_info.epics" epics="::vm.item.user_story_extra_info.epics" format="pill" - project="vm.project" ) //- User Stories generated from issue diff --git a/app/modules/components/watch-button/watch-button.controller.coffee b/app/modules/components/watch-button/watch-button.controller.coffee index 99514424..e7cbae9c 100644 --- a/app/modules/components/watch-button/watch-button.controller.coffee +++ b/app/modules/components/watch-button/watch-button.controller.coffee @@ -45,7 +45,8 @@ class WatchButtonController perms = { userstories: 'modify_us', issues: 'modify_issue', - tasks: 'modify_task' + tasks: 'modify_task', + epics: 'modify_epic' } return perms[name] diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 452d928a..2dc410b5 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -15,7 +15,7 @@ .name(ng-if="vm.column.name") - var hash = "#"; a( - tg-nav="project-epics-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=vm.project.slug,ref=vm.epic.get('ref')" ng-attr-title="{{::vm.epic.get('subject')}}" ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} span.epic-pill( diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee new file mode 100644 index 00000000..8042fa8f --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2015 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: related-userstories.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserStoriesController + @.$inject = ["tgResources"] + + constructor: (@rs) -> + @.sectionName = "Epics" + @.showCreateRelatedUserstoriesLightbox = false + + loadRelatedUserstories: () -> + @rs.userstories.listInEpic(@.epic.get('id')).then (data) => + @.userstories = data + +module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee new file mode 100644 index 00000000..8b51a2c2 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee @@ -0,0 +1,92 @@ +### +# Copyright (C) 2014-2015 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: related-userstory-create.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserstoriesCreateController + @.$inject = [ + "tgCurrentUserService", + "tgResources", + "$tgConfirm", + "$tgAnalytics" + ] + + constructor: (@currentUserService, @rs, @confirm, @analytics) -> + @.projects = @currentUserService.projects.get("all") + @.projectUserstories = Immutable.List() + @.loading = false + + selectProject: (selectedProjectId, onSelectedProject) -> + @rs.userstories.listAllInProject(selectedProjectId).then (data) => + excludeIds = @.epicUserstories.map((us) -> us.get('id')) + filteredData = data.filter((us) -> excludeIds.indexOf(us.get('id')) == -1) + @.projectUserstories = filteredData + if onSelectedProject + onSelectedProject() + + saveRelatedUserStory: (selectedUserstoryId, onSavedRelatedUserstory) -> + # This method assumes the following methods are binded to the controller: + # - validateExistingUserstoryForm + # - setExistingUserstoryFormErrors + # - loadRelatedUserstories + return if not @.validateExistingUserstoryForm() + + @.loading = true + + onError = (data) => + @.loading = false + @confirm.notify("error") + @.setExistingUserstoryFormErrors(data) + + onSuccess = () => + @analytics.trackEvent("epic related user story", "create", "create related user story on epic", 1) + @.loading = false + if onSavedRelatedUserstory + onSavedRelatedUserstory() + @.loadRelatedUserstories() + + epicId = @.epic.get('id') + @rs.epics.addRelatedUserstory(epicId, selectedUserstoryId).then(onSuccess, onError) + + bulkCreateRelatedUserStories: (selectedProjectId, userstoriesText, onCreatedRelatedUserstory) -> + # This method assumes the following methods are binded to the controller: + # - validateNewUserstoryForm + # - setNewUserstoryFormErrors + # - loadRelatedUserstories + return if not @.validateNewUserstoryForm() + + @.loading = true + + onError = (data) => + @.loading = false + @confirm.notify("error") + @.setNewUserstoryFormErrors(data) + + onSuccess = () => + @analytics.trackEvent("epic related user story", "create", "create related user story on epic", 1) + @.loading = false + if onCreatedRelatedUserstory + onCreatedRelatedUserstory() + @.loadRelatedUserstories() + + epicId = @.epic.get('id') + @rs.epics.bulkCreateRelatedUserStories(epicId, selectedProjectId, userstoriesText).then(onSuccess, onError) + + +module.controller("RelatedUserstoriesCreateCtrl", RelatedUserstoriesCreateController) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee new file mode 100644 index 00000000..f3bc84b1 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee @@ -0,0 +1,185 @@ +### +# Copyright (C) 2014-2015 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: related-userstories-create.controller.spec.coffee +### + +describe "RelatedUserstoriesCreate", -> + RelatedUserstoriesCreateCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgCurrentUserService = () -> + mocks.tgCurrentUserService = { + projects: { + get: sinon.stub() + } + } + + provide.value "tgCurrentUserService", mocks.tgCurrentUserService + + _mockTgConfirm = () -> + mocks.tgConfirm = { + askOnDelete: sinon.stub() + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + + _mockTgResources = () -> + mocks.tgResources = { + userstories: { + listAllInProject: sinon.stub() + } + epics: { + deleteRelatedUserstory: sinon.stub() + addRelatedUserstory: sinon.stub() + bulkCreateRelatedUserStories: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgAnalytics = () -> + mocks.tgAnalytics = { + trackEvent: sinon.stub() + } + + provide.value "$tgAnalytics", mocks.tgAnalytics + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgCurrentUserService() + _mockTgConfirm() + _mockTgResources() + _mockTgAnalytics() + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserstoriesCreateCtrl = controller "RelatedUserstoriesCreateCtrl" + + it "select project", (done) -> + # This test tries to reproduce a project containing userstories 11 and 12 where 11 + # is yet related to the epic + RelatedUserstoriesCreateCtrl.epicUserstories = Immutable.fromJS([ + { + id: 11 + } + ]) + + onSelectedProjectCallback = sinon.stub() + userstories = Immutable.fromJS([ + { + id: 11 + }, + { + + id: 12 + } + ]) + filteredUserstories = Immutable.fromJS([ + { + + id: 12 + } + ]) + + promise = mocks.tgResources.userstories.listAllInProject.withArgs(1).promise().resolve(userstories) + RelatedUserstoriesCreateCtrl.selectProject(1, onSelectedProjectCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.projectUserstories.toJS()).to.eql(filteredUserstories.toJS()) + done() + + it "save related user story success", (done) -> + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm.returns(true) + onSavedRelatedUserstoryCallback = sinon.stub() + onSavedRelatedUserstoryCallback.returns(true) + RelatedUserstoriesCreateCtrl.loadRelatedUserstories = sinon.stub() + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.addRelatedUserstory.withArgs(1, 11).promise().resolve(true) + RelatedUserstoriesCreateCtrl.saveRelatedUserStory(11, onSavedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm).have.been.calledOnce + expect(onSavedRelatedUserstoryCallback).have.been.calledOnce + expect(mocks.tgResources.epics.addRelatedUserstory).have.been.calledWith(1, 11) + expect(mocks.tgAnalytics.trackEvent).have.been.calledWith("epic related user story", "create", "create related user story on epic", 1) + expect(RelatedUserstoriesCreateCtrl.loadRelatedUserstories).have.been.calledOnce + done() + + it "save related user story error", (done) -> + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm.returns(true) + onSavedRelatedUserstoryCallback = sinon.stub() + RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors = sinon.stub() + RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors.returns({}) + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.addRelatedUserstory.withArgs(1, 11).promise().reject(new Error("error")) + RelatedUserstoriesCreateCtrl.saveRelatedUserStory(11, onSavedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm).have.been.calledOnce + expect(onSavedRelatedUserstoryCallback).to.not.have.been.called + expect(mocks.tgResources.epics.addRelatedUserstory).have.been.calledWith(1, 11) + expect(mocks.tgConfirm.notify).have.been.calledWith("error") + expect(RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors).have.been.calledOnce + done() + + it "bulk create related user stories success", (done) -> + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm.returns(true) + onCreatedRelatedUserstoryCallback = sinon.stub() + onCreatedRelatedUserstoryCallback.returns(true) + RelatedUserstoriesCreateCtrl.loadRelatedUserstories = sinon.stub() + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.bulkCreateRelatedUserStories.withArgs(1, 22, 'a\nb').promise().resolve(true) + RelatedUserstoriesCreateCtrl.bulkCreateRelatedUserStories(22, 'a\nb', onCreatedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateNewUserstoryForm).have.been.calledOnce + expect(onCreatedRelatedUserstoryCallback).have.been.calledOnce + expect(mocks.tgResources.epics.bulkCreateRelatedUserStories).have.been.calledWith(1, 22, 'a\nb') + expect(mocks.tgAnalytics.trackEvent).have.been.calledWith("epic related user story", "create", "create related user story on epic", 1) + expect(RelatedUserstoriesCreateCtrl.loadRelatedUserstories).have.been.calledOnce + done() + + it "bulk create related user stories error", (done) -> + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm.returns(true) + onCreatedRelatedUserstoryCallback = sinon.stub() + RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors = sinon.stub() + RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors.returns({}) + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.bulkCreateRelatedUserStories.withArgs(1, 22, 'a\nb').promise().reject(new Error("error")) + RelatedUserstoriesCreateCtrl.bulkCreateRelatedUserStories(22, 'a\nb', onCreatedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateNewUserstoryForm).have.been.calledOnce + expect(onCreatedRelatedUserstoryCallback).to.not.have.been.called + expect(mocks.tgResources.epics.bulkCreateRelatedUserStories).have.been.calledWith(1, 22, 'a\nb') + expect(mocks.tgConfirm.notify).have.been.calledWith("error") + expect(RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors).have.been.calledOnce + done() diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee new file mode 100644 index 00000000..9ecd4a03 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee @@ -0,0 +1,79 @@ +### +# 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: related-userstory-create.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoriesCreateDirective = (@lightboxService) -> + link = (scope, el, attrs, ctrl) -> + newUserstoryForm = el.find(".new-user-story-form").checksley() + existingUserstoryForm = el.find(".existing-user-story-form").checksley() + + ctrl.validateNewUserstoryForm = => + return newUserstoryForm.validate() + + ctrl.setNewUserstoryFormErrors = (errors) => + newUserstoryForm.setErrors(errors) + + ctrl.validateExistingUserstoryForm = => + return existingUserstoryForm.validate() + + ctrl.setExistingUserstoryFormErrors = (errors) => + existingUserstoryForm.setErrors(errors) + + scope.showLightbox = (selectedProjectId) -> + scope.selectProject(selectedProjectId).then () => + lightboxService.open(el.find(".lightbox-create-related-user-stories")) + + scope.closeLightbox = () -> + scope.selectedUserstory = null + scope.searchUserstory = "" + scope.relatedUserstoriesText = "" + lightboxService.close(el.find(".lightbox-create-related-user-stories")) + + scope.$watch 'vm.project', (project) -> + if project? + scope.selectedProject = project.get('id') + + scope.selectProject = (selectedProjectId) -> + scope.selectedUserstory = null + scope.searchUserstory = "" + ctrl.selectProject(selectedProjectId) + + scope.onUpdateSearchUserstory = () -> + scope.selectedUserstory = null + + return { + link: link, + templateUrl:"epics/related-userstories/related-userstories-create/related-userstories-create.html", + controller: "RelatedUserstoriesCreateCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + showCreateRelatedUserstoriesLightbox: "=" + project: "=" + epic: "=" + epicUserstories: "=" + loadRelatedUserstories:"&" + } + + } + +RelatedUserstoriesCreateDirective.$inject = ["lightboxService",] + +module.directive("tgRelatedUserstoriesCreate", RelatedUserstoriesCreateDirective) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade new file mode 100644 index 00000000..468acbbb --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -0,0 +1,153 @@ +a.add-button.e2e-add-userstory-button( + href="" + ng-click="showLightbox(vm.project.get('id'))" +) + tg-svg(svg-icon="icon-add") + +div.lightbox.lightbox-create-related-user-stories + tg-lightbox-close + + div.form + h2.title(translate="EPIC.CREATE_RELATED_USERSTORIES") + + .related-with-selector-title + legend(translate="EPIC.RELATED_WITH") + + .related-with-selector + fieldset + input( + type="radio" + name="related-with-selector" + id="new-user-story" + value="new-user-story" + ng-model="relatedWithSelector" + ng-init="relatedWithSelector='new-user-story'" + ) + label.e2e-new-userstory-label(for="new-user-story") + span.name {{ 'EPIC.NEW_USERSTORY' | translate}} + + fieldset + input( + type="radio" + name="related-with-selector" + id="existing-user-story" + value="existing-user-story" + ng-model="relatedWithSelector" + ) + label.e2e-existing-user-story-label(for="existing-user-story") + span.name {{ 'EPIC.EXISTING_USERSTORY' | translate}} + + .project-selector-title + legend( + ng-if="relatedWithSelector=='new-user-story'" + translate="EPIC.CHOOSE_PROJECT_FOR_CREATION" + ) + + legend( + ng-if="relatedWithSelector=='existing-user-story'" + translate="EPIC.CHOOSE_PROJECT_FROM" + ) + + .project-selector() + select( + ng-model="selectedProject" + ng-change="selectProject(selectedProject)" + data-required="true" + required + ng-options="p.id as p.name for p in vm.projects | toMutable" + ) + + div(ng-show="relatedWithSelector=='new-user-story'") + .new-user-story-selector + .new-user-story-title + legend( + ng-show="creationMode=='single-new-user-story'" + translate="EPIC.SUBJECT" + ) + + legend( + ng-show="creationMode=='bulk-new-user-stories'" + translate="EPIC.SUBJECT_BULK_MODE" + ) + .new-user-story-options + fieldset + input( + type="radio" + name="new-user-story-selector" + id="single-new-user-story" + value="single-new-user-story" + ng-model="creationMode" + ng-init="creationMode='single-new-user-story'" + ) + label.e2e-single-creation-label(for="single-new-user-story") + tg-svg(svg-icon="icon-add") + + fieldset + input( + type="radio" + name="new-user-story-selector" + id="bulk-new-user-stories" + value="bulk-new-user-stories" + ng-model="creationMode" + ) + label.e2e-bulk-creation-label(for="bulk-new-user-stories") + tg-svg(svg-icon="icon-bulk") + + form.new-user-story-form + .single-creation(ng-show="creationMode=='single-new-user-story'") + input.e2e-new-userstory-input-text( + type="text" + ng-model="relatedUserstoriesText" + data-required="true" + ) + + .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") + textarea.e2e-new-userstories-input-textarea( + ng-model="relatedUserstoriesText" + data-required="true" + ) + + a.button-green.e2e-create-userstory-button( + href="" + ng-click="vm.bulkCreateRelatedUserStories(selectedProject, relatedUserstoriesText, closeLightbox)" + tg-loading="vm.loading" + ) + span( + translate="COMMON.SAVE" + ) + + .existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + .existing-user-story-title + legend(translate="EPIC.CHOOSE_USERSTORY") + + input.userstory.e2e-filter-userstories-input( + type="text" + placeholder="{{'EPIC.FILTER_USERSTORIES' | translate}}" + ng-model="searchUserstory" + ng-change="onUpdateSearchUserstory()" + ) + + form.existing-user-story-form + select.userstory.e2e-userstories-select( + size="5" + ng-model="selectedUserstory" + required + data-required="true" + ) + - var hash = "#"; + option.hidden( + value="" + ) + option( + ng-repeat="us in vm.projectUserstories | toMutable | byRef:searchUserstory track by us.id" + value="{{ ::us.id }}" + ) #{hash}{{::us.ref}} {{::us.subject}} + + a.button-green.e2e-select-related-userstory-button( + href="" + ng-click="vm.saveRelatedUserStory(selectedUserstory, closeLightbox)" + tg-loading="vm.loading" + ) + span( + translate="COMMON.SAVE" + ) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss new file mode 100644 index 00000000..82412585 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss @@ -0,0 +1,78 @@ +.lightbox-create-related-user-stories { + .related-with-selector-title, + .project-selector-title, + .new-user-story-title, + .existing-user-story-title { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + } + .related-with-selector, + .new-user-story-selector { + display: flex; + input { + display: none; + } + fieldset { + &:first-child { + margin-right: .5rem; + } + } + } + .project-selector, + .single-creation { + margin-bottom: 1rem; + } + input { + &:checked+label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + &:hover { + background: $primary-light; + } + } + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: 2rem 1rem; + text-align: center; + transition: background .2s ease-in; + &:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } + .icon { + fill: currentColor; + margin-top: .25rem; + vertical-align: text-top; + } + .name { + @include font-size(large); + text-transform: uppercase; + } + } + } + .new-user-story-selector { + display: flex; + justify-content: space-between; + .new-user-story-options { + display: flex; + } + fieldset { + width: auto; + } + label { + height: 1.5rem; + padding: 0; + width: 1.5rem; + } + } + + .existing-user-story { + .button-green { + margin-top: 1rem; + } + } +} diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee new file mode 100644 index 00000000..9162c935 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -0,0 +1,66 @@ +### +# Copyright (C) 2014-2015 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: related-userstories.controller.spec.coffee +### + +describe "RelatedUserStories", -> + RelatedUserStoriesCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserStoriesCtrl = controller "RelatedUserStoriesCtrl" + + it "load related userstories", (done) -> + userstories = Immutable.fromJS([ + { + id: 1 + } + ]) + + RelatedUserStoriesCtrl.epic = Immutable.fromJS({ + id: 66 + }) + + promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) + RelatedUserStoriesCtrl.loadRelatedUserstories().then () -> + expect(RelatedUserStoriesCtrl.userstories).is.equal(userstories) + done() diff --git a/app/modules/epics/related-userstories/related-userstories.directive.coffee b/app/modules/epics/related-userstories/related-userstories.directive.coffee new file mode 100644 index 00000000..e3db9be8 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.directive.coffee @@ -0,0 +1,37 @@ +### +# 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: related-userstories.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserStoriesDirective = () -> + return { + templateUrl:"epics/related-userstories/related-userstories.html", + controller: "RelatedUserStoriesCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + userstories: '=', + project: '=' + epic: '=' + } + } + +RelatedUserStoriesDirective.$inject = [] + +module.directive("tgRelatedUserstories", RelatedUserStoriesDirective) diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade new file mode 100644 index 00000000..ecf642de --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -0,0 +1,23 @@ +section.related-userstories + .related-userstories-header + span.related-userstories-title(translate="COMMON.RELATED_USERSTORIES") + tg-related-userstories-create( + tg-check-permission="modify_epic" + show-create-related-userstories-lightbox="vm.showCreateRelatedUserstoriesLightbox" + project="vm.project" + epic="vm.epic" + epic-userstories="vm.userstories" + load-related-userstories="vm.loadRelatedUserstories()" + ) + + .related-userstories-body + div(tg-repeat="us in vm.userstories track by us.get('id')") + tg-related-userstory-row.row( + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + userstory="us" + epic="vm.epic" + project="vm.project" + load-related-userstories="vm.loadRelatedUserstories()" + ) + + div(tg-related-userstories-create-form) diff --git a/app/modules/epics/related-userstories/related-userstories.scss b/app/modules/epics/related-userstories/related-userstories.scss new file mode 100644 index 00000000..62bc0b46 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.scss @@ -0,0 +1,147 @@ +.related-userstories { + margin-bottom: 2rem; + position: relative; +} + +.related-userstories-header { + align-content: center; + align-items: center; + background: $mass-white; + display: flex; + justify-content: space-between; + min-height: 36px; + .related-userstories-title { + @include font-size(medium); + @include font-type(bold); + margin-left: 1rem; + } + .add-button { + background: $grayer; + border: 0; + display: inline-block; + padding: .5rem; + transition: background .25s; + &:hover, + &.is-active { + background: $primary-light; + } + svg { + fill: $white; + height: 1.25rem; + margin-bottom: -.2rem; + width: 1.25rem; + } + } +} + +.related-userstories-body { + width: 100%; + .row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: .5rem 0 .5rem .5rem; + &:hover { + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + } + .userstory-name { + flex: 1; + } + .userstory-settings { + flex-shrink: 0; + width: 60px; + } + .status { + flex-shrink: 0; + width: 125px; + } + .assigned-to-column { + flex-shrink: 0; + width: 150px; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + } + .project { + flex-basis: 100px; + img { + width: 40px; + } + } + } + + .userstory-name { + display: flex; + margin-right: 1rem; + + span { + margin-right: .25rem; + } + } + .status { + position: relative; + } + .closed { + border-left: 10px solid $whitish; + color: $whitish; + a, + svg { + fill: $whitish; + } + .userstory-name a { + color: $whitish; + text-decoration: line-through; + + } + } + .blocked { + background: rgba($red-light, .2); + border-left: 10px solid $red-light; + } + .userstory-settings { + align-items: center; + display: flex; + opacity: 0; + svg { + @include svg-size(1.1rem); + fill: $gray-light; + margin-right: .5rem; + transition: fill .2s ease-in; + &:hover { + fill: $gray; + } + } + a { + &:hover { + cursor: pointer; + } + } + } + .delete-userstory { + &:hover { + .icon-trash { + fill: $red-light; + } + } + } + .avatar { + align-items: center; + display: flex; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + figcaption { + margin-left: .5rem; + } + } +} diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee new file mode 100644 index 00000000..ef58ab9b --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -0,0 +1,63 @@ +### +# Copyright (C) 2014-2015 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: reñated-userstory-row.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserstoryRowController + @.$inject = [ + "tgAvatarService", + "$translate", + "$tgConfirm", + "tgResources" + ] + + constructor: (@avatarService, @translate, @confirm, @rs) -> + + setAvatarData: () -> + member = @.userstory.get('assigned_to_extra_info') + @.avatar = @avatarService.getAvatar(member) + + getAssignedToFullNameDisplay: () -> + if @.userstory.get('assigned_to') + return @.userstory.getIn(['assigned_to_extra_info', 'full_name_display']) + + return @translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED") + + onDeleteRelatedUserstory: () -> + title = @translate.instant('EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY') + message = @translate.instant('EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY', { + subject: @.userstory.get('subject') + }) + + return @confirm.askOnDelete(title, message) + .then (askResponse) => + onError = () => + message = @translate.instant('EPIC.ERROR_DELETE_RELATED_USERSTORY', {errorMessage: message}) + @confirm.notify("error", null, message) + askResponse.finish(false) + + onSuccess = () => + @.loadRelatedUserstories() + askResponse.finish() + + epicId = @.epic.get('id') + userstoryId = @.userstory.get('id') + @rs.epics.deleteRelatedUserstory(epicId, userstoryId).then(onSuccess, onError) + +module.controller("RelatedUserstoryRowCtrl", RelatedUserstoryRowController) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee new file mode 100644 index 00000000..c300b372 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee @@ -0,0 +1,169 @@ +### +# Copyright (C) 2014-2015 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: related-userstory-row.controller.spec.coffee +### + +describe "RelatedUserstoryRow", -> + RelatedUserstoryRowCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgConfirm = () -> + mocks.tgConfirm = { + askOnDelete: sinon.stub() + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgAvatarService = () -> + mocks.tgAvatarService = { + getAvatar: sinon.stub() + } + + provide.value "tgAvatarService", mocks.tgAvatarService + + _mockTranslate = () -> + mocks.translate = { + instant: sinon.stub() + } + + provide.value "$translate", mocks.translate + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + deleteRelatedUserstory: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgConfirm() + _mockTgAvatarService() + _mockTranslate() + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserstoryRowCtrl = controller "RelatedUserstoryRowCtrl" + + it "set avatar data", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to_extra_info: { + id: 3 + } + }) + member = RelatedUserstoryRowCtrl.userstory.get("assigned_to_extra_info") + avatar = { + url: "http://taiga.io" + bg: "#AAAAAA" + } + mocks.tgAvatarService.getAvatar.withArgs(member).returns(avatar) + RelatedUserstoryRowCtrl.setAvatarData() + expect(mocks.tgAvatarService.getAvatar).have.been.calledWith(member) + expect(RelatedUserstoryRowCtrl.avatar).is.equal(avatar) + done() + + it "get assigned to full name display for existing user", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to: 1 + assigned_to_extra_info: { + full_name_display: "Beta tester" + } + }) + + expect(RelatedUserstoryRowCtrl.getAssignedToFullNameDisplay()).is.equal("Beta tester") + done() + + it "get assigned to full name display for unassigned user story", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to: null + }) + mocks.translate.instant.withArgs("COMMON.ASSIGNED_TO.NOT_ASSIGNED").returns("Unassigned") + expect(RelatedUserstoryRowCtrl.getAssignedToFullNameDisplay()).is.equal("Unassigned") + done() + + it "delete related userstory success", (done) -> + RelatedUserstoryRowCtrl.epic = Immutable.fromJS({ + id: 123 + }) + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + subject: "Deleting" + id: 124 + }) + + RelatedUserstoryRowCtrl.loadRelatedUserstories = sinon.stub() + + askResponse = { + finish: sinon.spy() + } + + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + + mocks.tgConfirm.askOnDelete = sinon.stub() + mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) + + promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().resolve(true) + RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> + expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) + expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).have.been.calledOnce + expect(askResponse.finish).have.been.calledOnce + done() + + it "delete related userstory error", (done) -> + RelatedUserstoryRowCtrl.epic = Immutable.fromJS({ + id: 123 + }) + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + subject: "Deleting" + id: 124 + }) + + RelatedUserstoryRowCtrl.loadRelatedUserstories = sinon.stub() + + askResponse = { + finish: sinon.spy() + } + + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.ERROR_DELETE_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") + + mocks.tgConfirm.askOnDelete = sinon.stub() + mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) + + promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().reject(new Error("error")) + RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> + expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) + expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).to.not.have.been.called + expect(askResponse.finish).have.been.calledWith(false) + expect(mocks.tgConfirm.notify).have.been.calledWith("error", null, "error message") + done() diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee new file mode 100644 index 00000000..02ea4ebd --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee @@ -0,0 +1,42 @@ +### +# 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: related-userstory-row.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoryRowDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.setAvatarData() + + return { + link: link, + templateUrl:"epics/related-userstories/related-userstory-row/related-userstory-row.html", + controller: "RelatedUserstoryRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + userstory: '=' + epic: '=' + project: '=' + loadRelatedUserstories:"&" + } + } + +RelatedUserstoryRowDirective.$inject = [] + +module.directive("tgRelatedUserstoryRow", RelatedUserstoryRowDirective) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade new file mode 100644 index 00000000..7c7b8a41 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -0,0 +1,44 @@ +.userstory-name + - var hash = "#"; + a( + tg-nav="project-userstories-detail:project=vm.userstory.getIn(['project_extra_info', 'slug']),ref=vm.userstory.get('ref')" + ng-attr-title="{{vm.userstory.get('subject')}}" + ) #{hash}{{vm.userstory.get('ref')}} {{vm.userstory.get('subject')}} + + tg-belong-to-epics( + format="pill" + ng-if="vm.userstory.get('epics')" + epics="vm.userstory.get('epics')" + ) + +.userstory-settings + a.delete-userstory.e2e-delete-userstory( + tg-check-permission="modify_epic" + title="{{'COMMON.DELETE' | translate}}" + href="" + ng-click="vm.onDeleteRelatedUserstory()" + ) + tg-svg(svg-icon="icon-trash") + +.project( + tg-nav="project:project=vm.userstory.getIn(['project_extra_info', 'slug'])" +) + img( + tg-project-logo-small-src="::vm.userstory.get('project_extra_info')" + alt="{{::vm.userstory.getIn(['project_extra_info', 'name'])}}" + ) + +.status + span.userstory-status(ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}") {{vm.userstory.getIn(['status_extra_info', 'name'])}} + +.assigned-to-column + figure.avatar + img( + style="background-color: {{ vm.avatar.bg }}" + src="{{ vm.avatar.url }}" + alt="{{ vm.avatar.full_name_display }}" + ) + + figcaption {{ vm.getAssignedToFullNameDisplay() }} + +div(tg-related-userstories-create-form) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 21129745..82d48c11 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -51,6 +51,31 @@ Resource = (urlsService, http) -> return http.post(url, params) + service.addRelatedUserstory = (epicId, userstoryId) -> + url = urlsService.resolve("epic-related-userstories", epicId) + + params = { + user_story: userstoryId + epic: epicId + } + + return http.post(url, params) + + service.bulkCreateRelatedUserStories = (epicId, projectId, bulk_userstories) -> + url = urlsService.resolve("epic-related-userstories-bulk-create", epicId) + + params = { + bulk_userstories: bulk_userstories, + project_id: projectId + } + + return http.post(url, params) + + service.deleteRelatedUserstory = (epicId, userstoryId) -> + url = urlsService.resolve("epic-related-userstories", epicId) + "/#{userstoryId}" + + return http.delete(url) + return () -> return {"epics": service} diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index 7b7f9b90..d410036e 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,6 +33,22 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.listAllInProject = (projectId) -> + url = urlsService.resolve("userstories") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + params = { + project: projectId + } + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + service.listInEpic = (epicIid) -> url = urlsService.resolve("userstories") diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade new file mode 100644 index 00000000..b03ec020 --- /dev/null +++ b/app/partials/epic/epic-detail.jade @@ -0,0 +1,127 @@ +doctype html + +div.wrapper( + ng-controller="EpicDetailController as ctrl", + ng-init="section='epics'" +) + tg-project-menu + + div.main.us-detail + div.us-detail-header.header-with-actions + include ../includes/components/mainTitle + + section.us-story-main-data + header + tg-vote-button.upvote-btn( + item="epic" + on-upvote="ctrl.onUpvote" + on-downvote="ctrl.onDownvote" + ) + + .detail-header-container + tg-color-selector( + color="epic.color", + on-select-color="ctrl.onSelectColor(color)" + ) + tg-detail-header( + item="epic" + project="project" + required-perm="modify_epic" + ng-class="{blocked: epic.is_blocked}" + ng-if="project && epic" + format="text" + ) + .subheader + tg-tag-line.tags-block( + ng-if="epic && project" + project="project" + item="epic" + permissions="modify_epic" + ) + tg-created-by-display.ticket-created-by(ng-model="epic") + + section.duty-content( + tg-editable-description + tg-editable-wysiwyg + ng-model="epic" + required-perm="modify_epic" + ) + + // Custom Fields + tg-custom-attributes-values( + ng-model="epic" + type="epic" + project="project" + required-edition-perm="modify_epic" + ) + + tg-related-userstories( + project="immutableProject" + userstories="userstories" + epic="immutableEpic" + ) + + tg-attachments-full( + obj-id="epic.id" + type="epic", + project-id="projectId" + edit-permission = "modify_epic" + ) + + tg-history-section( + ng-if="epic" + type="epic" + name="epic" + id="epic.id" + project-id="projectId" + ) + + sidebar.menu-secondary.sidebar.ticket-data + + .ticket-header + span.ticket-title( + tg-epic-status-display + ng-model="epic" + ) + span.detail-status( + tg-epic-status-button + ng-model="epic" + ) + + section.ticket-assigned-to( + tg-assigned-to + ng-model="epic" + required-perm="modify_epic" + ) + + section.ticket-watch-buttons + div.ticket-watch( + tg-watch-button + item="epic" + data-environment="ticket" + on-watch="ctrl.onWatch" + on-unwatch="ctrl.onUnwatch" + ) + div.ticket-watchers( + tg-watchers + ng-model="epic" + required-perm="modify_epic" + ) + + section.ticket-detail-settings + tg-us-team-requirement-button(ng-model="epic") + tg-us-client-requirement-button(ng-model="epic") + tg-block-button( + tg-check-permission="modify_epic", + ng-model="epic" + ) + tg-delete-button( + tg-check-permission="delete_epic", + on-delete-title="{{'EPIC.ACTION_DELETE' | translate}}", + on-delete-go-to-url="onDeleteGoToUrl", + ng-model="epic" + ) + + div.lightbox.lightbox-block(tg-lb-block, ng-model="epic", title="EPIC.LIGHTBOX_TITLE_BLOKING_EPIC") + div.lightbox.lightbox-select-user(tg-lb-assignedto) + div.lightbox.lightbox-select-user(tg-lb-watchers) diff --git a/app/styles/modules/common/wizard.scss b/app/styles/modules/common/wizard.scss index f0482026..5bcae75f 100644 --- a/app/styles/modules/common/wizard.scss +++ b/app/styles/modules/common/wizard.scss @@ -57,7 +57,6 @@ .icon { @include svg-size(1.5rem); fill: currentColor; - margin-right: 1rem; vertical-align: text-top; } .template-name { diff --git a/conf.e2e.js b/conf.e2e.js index d7ca0a01..cd421c09 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -53,55 +53,55 @@ var config = { onPrepare: function() { // disable by default because performance problems on IE // track mouse movements - // var trackMouse = function() { - // angular.module('trackMouse', []).run(function($document) { + var trackMouse = function() { + angular.module('trackMouse', []).run(function($document) { - // function addDot(ev) { - // var color = 'black', - // size = 6; + function addDot(ev) { + var color = 'black', + size = 6; - // switch (ev.type) { - // case 'click': - // color = 'red'; - // break; - // case 'dblclick': - // color = 'blue'; - // break; - // case 'mousemove': - // color = 'green'; - // break; - // } + switch (ev.type) { + case 'click': + color = 'red'; + break; + case 'dblclick': + color = 'blue'; + break; + case 'mousemove': + color = 'green'; + break; + } - // var dotEl = $('
') - // .css({ - // position: 'fixed', - // height: size + 'px', - // width: size + 'px', - // 'background-color': color, - // top: ev.clientY, - // left: ev.clientX, + var dotEl = $('
') + .css({ + position: 'fixed', + height: size + 'px', + width: size + 'px', + 'background-color': color, + top: ev.clientY, + left: ev.clientX, - // 'z-index': 9999, + 'z-index': 9999, - // // make sure this dot won't interfere with the mouse events of other elements - // 'pointer-events': 'none' - // }) - // .appendTo('body'); + // make sure this dot won't interfere with the mouse events of other elements + 'pointer-events': 'none' + }) + .appendTo('body'); - // setTimeout(function() { - // dotEl.remove(); - // }, 1000); - // } + setTimeout(function() { + dotEl.remove(); + }, 1000); + } - // $document.on({ - // click: addDot, - // dblclick: addDot, - // mousemove: addDot - // }); + $document.on({ + click: addDot, + dblclick: addDot, + mousemove: addDot + }); - // }); - // }; - // browser.addMockModule('trackMouse', trackMouse); + }); + }; + browser.addMockModule('trackMouse', trackMouse); browser.params.glob.back = argv.back; diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 78daae62..a315edbb 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -86,7 +86,7 @@ helper.tags = function() { for (let tag of tags){ htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container")); el.$('.e2e-add-tag-input').sendKeys(tag); - await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + el.$('.save').click(); await htmlChanges(); } } @@ -542,3 +542,43 @@ helper.watchersLightbox = function() { return obj; }; + +helper.teamRequirement = function() { + let el = $('tg-us-team-requirement-button'); + + let obj = { + el: el, + + toggleStatus: async function(){ + await el.$("label").click(); + await browser.waitForAngular(); + }, + + isRequired: async function() { + let classes = await el.$("label").getAttribute('class'); + return classes.includes("active"); + } + }; + + return obj; +}; + +helper.clientRequirement = function() { + let el = $('tg-us-client-requirement-button'); + + let obj = { + el: el, + + toggleStatus: async function(){ + await el.$("label").click(); + await browser.waitForAngular(); + }, + + isRequired: async function() { + let classes = await el.$("label").getAttribute('class'); + return classes.includes("active"); + } + }; + + return obj; +}; diff --git a/e2e/helpers/epic-detail-helper.js b/e2e/helpers/epic-detail-helper.js new file mode 100644 index 00000000..84368661 --- /dev/null +++ b/e2e/helpers/epic-detail-helper.js @@ -0,0 +1,76 @@ +var utils = require('../utils'); +var commonHelper = require('./common-helper'); + +var helper = module.exports; + + +helper.colorEditor = function() { + let el = $('tg-color-selector'); + + let obj = { + el: el, + + open: async function(){ + await el.$(".e2e-open-color-selector").click(); + }, + + selectFirstColor: async function() { + let color = el.$$(".color-selector-option").first(); + color.click(); + await browser.waitForAngular(); + }, + + selectLastColor: async function() { + let color = el.$$(".color-selector-option").last(); + color.click(); + await browser.waitForAngular(); + } + }; + + return obj; +}; + +helper.relatedUserstories = function() { + let el = $('tg-related-userstories'); + + let obj = { + el: el, + + createNewUserStory: async function(subject) { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-new-userstory-label").click(); + el.$(".e2e-single-creation-label").click(); + el.$(".e2e-new-userstory-input-text").sendKeys(subject); + el.$(".e2e-create-userstory-button").click(); + await browser.waitForAngular(); + }, + + createNewUserStories: async function(subject) { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-new-userstory-label").click(); + el.$(".e2e-bulk-creation-label").click(); + el.$(".e2e-new-userstories-input-textarea").sendKeys(subject); + el.$(".e2e-create-userstory-button").click(); + await browser.waitForAngular(); + }, + + selectFirstRelatedUserstory: async function() { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-existing-user-story-label").click(); + el.$(".e2e-filter-userstories-input").click().sendKeys("#1"); + await browser.waitForAngular(); + el.$$(".e2e-userstories-select option").get(1).click() + el.$(".e2e-select-related-userstory-button").click(); + await browser.waitForAngular(); + }, + + deleteFirstRelatedUserstory: async function() { + let relatedUSRow = el.$$("tg-related-userstory-row").first(); + browser.actions().mouseMove(relatedUSRow).perform(); + relatedUSRow.$(".e2e-delete-userstory").click(); + await utils.lightbox.confirm.ok(); + } + }; + + return obj; +} diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-dashboard-helper.js similarity index 94% rename from e2e/helpers/epics-helper.js rename to e2e/helpers/epics-dashboard-helper.js index 305f7a27..787acd1c 100644 --- a/e2e/helpers/epics-helper.js +++ b/e2e/helpers/epics-dashboard-helper.js @@ -44,33 +44,38 @@ helper.epic = function() { resetAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); $$('.e2e-assigned-to-selector').get(0).click(); + await browser.waitForAngular(); }, editAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); utils.common.takeScreenshot("epics", "epics-edit-assigned"); $$('.e2e-assigned-to-selector').last().click(); + await browser.waitForAngular(); }, removeAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); - $$('.e2e-unassign').click(); + $('.e2e-unassign').click(); + await browser.waitForAngular(); return el.get(0).$('.e2e-assigned-to-image').getAttribute("alt"); }, - resetStatus: function() { + resetStatus: async function() { el.get(0).$('.e2e-epic-status').click(); el.get(0).$$('.e2e-edit-epic-status').get(0).click(); + await browser.waitForAngular(); }, getStatus: function() { return el.get(0).$('.e2e-epic-status').getText(); }, - editStatus: function() { + editStatus: async function() { el.get(0).$('.e2e-epic-status').click(); utils.common.takeScreenshot("epics", "epics-edit-status"); el.get(0).$$('.e2e-edit-epic-status').last().click(); + await browser.waitForAngular(); }, getColumns: function() { return $$('.e2e-epics-table-header > div').count(); }, - removeColumns: function() { + removeColumns: async function() { $('.e2e-epics-column-button').click(); utils.common.takeScreenshot("epics", "epics-edit-columns"); $$('.e2e-epics-column-dropdown .check').first().click(); diff --git a/e2e/helpers/index.js b/e2e/helpers/index.js index bc497ffc..307b9daf 100644 --- a/e2e/helpers/index.js +++ b/e2e/helpers/index.js @@ -13,3 +13,5 @@ module.exports.adminPermissions = require("./admin-permissions"); module.exports.adminIntegrations = require("./admin-integrations"); module.exports.issues = require("./issues-helper"); module.exports.createProject = require("./create-project-helper"); +module.exports.epicsDashboard = require("./epics-dashboard-helper"); +module.exports.epicDetail = require("./epic-detail-helper"); diff --git a/e2e/helpers/us-detail-helper.js b/e2e/helpers/us-detail-helper.js index c41f53af..b419d433 100644 --- a/e2e/helpers/us-detail-helper.js +++ b/e2e/helpers/us-detail-helper.js @@ -3,45 +3,6 @@ var commonHelper = require('./common-helper'); var helper = module.exports; -helper.teamRequirement = function() { - let el = $('tg-us-team-requirement-button'); - - let obj = { - el: el, - - toggleStatus: async function(){ - await el.$("label").click(); - await browser.waitForAngular(); - }, - - isRequired: async function() { - let classes = await el.$("label").getAttribute('class'); - return classes.includes("active"); - } - }; - - return obj; -}; - -helper.clientRequirement = function() { - let el = $('tg-us-client-requirement-button'); - - let obj = { - el: el, - - toggleStatus: async function(){ - await el.$("label").click(); - await browser.waitForAngular(); - }, - - isRequired: async function() { - let classes = await el.$("label").getAttribute('class'); - return classes.includes("active"); - } - }; - - return obj; -}; helper.relatedTaskForm = async function(form, name, status, assigned_to) { await form.$('input').sendKeys(name); diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index 3024a595..f8694cbd 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -274,12 +274,12 @@ shared.blockTesting = async function() { let descriptionText = await $('.block-description').getText(); expect(descriptionText).to.be.equal('This is a testing block reason'); - let isDisplayed = $('.block-description').isDisplayed(); + let isDisplayed = $('.block-desc-container').isDisplayed(); expect(isDisplayed).to.be.equal.true; blockHelper.unblock(); - isDisplayed = $('.block-description').isDisplayed(); + isDisplayed = $('.block-desc-container').isDisplayed(); expect(isDisplayed).to.be.equal.false; await notifications.success.close(); @@ -548,3 +548,37 @@ shared.customFields = function(typeIndex) { expect(fieldText).to.be.equal('test text2 edit'); }); }; + +shared.teamRequirementTesting = function() { + it('team requirement edition', async function() { + let requirementHelper = detailHelper.teamRequirement(); + let isRequired = await requirementHelper.isRequired(); + + // Toggle + requirementHelper.toggleStatus(); + let newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.not.equal(newIsRequired); + + // Toggle again + requirementHelper.toggleStatus(); + newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.equal(newIsRequired); + }); +} + +shared.clientRequirementTesting = function () { + it('client requirement edition', async function() { + let requirementHelper = detailHelper.clientRequirement(); + let isRequired = await requirementHelper.isRequired(); + + // Toggle + requirementHelper.toggleStatus(); + let newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.not.equal(newIsRequired); + + // Toggle again + requirementHelper.toggleStatus(); + newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.equal(newIsRequired); + }); +} diff --git a/e2e/suites/admin/attributes/custom-fields.e2e.js b/e2e/suites/admin/attributes/custom-fields.e2e.js index 0a61db8c..aef42ac7 100644 --- a/e2e/suites/admin/attributes/custom-fields.e2e.js +++ b/e2e/suites/admin/attributes/custom-fields.e2e.js @@ -16,8 +16,64 @@ describe('custom-fields', function() { }); describe('create custom fields', function() { + describe('epics', function() { + let typeIndex = 0; + + it('create', async function() { + let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + await customFieldsHelper.create(typeIndex, 'test1-text', 'desc1', 1); + + // debounce :( + await utils.notifications.success.open(); + await browser.sleep(2000); + + await customFieldsHelper.create(typeIndex, 'test1-multi', 'desc1', 3); + + // debounce :( + await utils.notifications.success.open(); + await browser.sleep(2000); + + let countCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + expect(countCustomFields).to.be.equal(oldCountCustomFields + 2); + }); + + it('edit', async function() { + customFieldsHelper.edit(typeIndex, 0, 'edit', 'desc2', 2); + + let open = await utils.notifications.success.open(); + + expect(open).to.be.true; + + await utils.notifications.success.close(); + }); + + it('drag', async function() { + let nameOld = await customFieldsHelper.getName(typeIndex, 0); + + await customFieldsHelper.drag(typeIndex, 0, 1); + + let nameNew = await customFieldsHelper.getName(typeIndex, 1); + + expect(nameNew).to.be.equal(nameOld); + }); + + it('delete', async function() { + let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + await customFieldsHelper.delete(typeIndex, 0); + + await browser.wait(async function() { + let countCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + return countCustomFields === oldCountCustomFields - 1; + }, 4000); + }); + }); + describe('userstories', function() { - let typeIndex = 0; + let typeIndex = 1; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -73,7 +129,7 @@ describe('custom-fields', function() { }); describe('tasks', function() { - let typeIndex = 1; + let typeIndex = 2; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -126,7 +182,7 @@ describe('custom-fields', function() { }); describe('issues', function() { - let typeIndex = 2; + let typeIndex = 3; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -180,5 +236,6 @@ describe('custom-fields', function() { }, 4000); }); }); + }); }); diff --git a/e2e/suites/admin/members.e2e.js b/e2e/suites/admin/members.e2e.js index 2cbf6904..5178b139 100644 --- a/e2e/suites/admin/members.e2e.js +++ b/e2e/suites/admin/members.e2e.js @@ -8,7 +8,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect; -describe.only('admin - members', function() { +describe('admin - members', function() { before(async function(){ browser.get(browser.params.glob.host + 'project/project-0/admin/memberships'); diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js index 370cc6af..9eb5d606 100644 --- a/e2e/suites/epics/epic-dashboard.e2e.js +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -1,5 +1,5 @@ var utils = require('../../utils'); -var epicsHelper = require('../../helpers/epics-helper'); +var epicsDashboardHelper = require('../../helpers').epicsDashboard; var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -8,7 +8,7 @@ chai.use(chaiAsPromised); var expect = chai.expect; describe('Epics Dashboard', function(){ - let usUrl = ''; + let epicsUrl = ''; before(async function(){ await utils.nav @@ -17,7 +17,7 @@ describe('Epics Dashboard', function(){ .epics() .go(); - usUrl = await browser.getCurrentUrl(); + epicsUrl = await browser.getCurrentUrl(); }); it('screenshot', async function() { @@ -25,13 +25,23 @@ describe('Epics Dashboard', function(){ }); it('display child stories', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); let childStoriesNum = await epic.displayUserStoriesinEpic(); expect(childStoriesNum).to.be.above(0); }); + it('create Epic', async function() { + let date = Date.now(); + let description = Math.random().toString(36).substring(7); + let epic = epicsDashboardHelper.epic(); + let currentEpicsNum = await epic.getEpics(); + await epic.createEpic(date, description); + let newEpicsNum = await epic.getEpics(); + expect(newEpicsNum).to.be.above(currentEpicsNum); + }); + it('change epic assigned from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetAssignedTo(); let currentAssigned = await epic.getAssignedTo(); await epic.editAssignedTo(); @@ -40,15 +50,14 @@ describe('Epics Dashboard', function(){ }); it('remove assigned from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetAssignedTo(); let unAssigned = await epic.removeAssignedTo(); - console.log(unAssigned); expect(unAssigned).to.be.equal('Unassigned'); }); it('change status from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetStatus(); let currentStatus = await epic.getStatus(); await epic.editStatus(); @@ -57,22 +66,11 @@ describe('Epics Dashboard', function(){ }); it('remove columns from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); let currentColumns = await epic.getColumns(); await epic.removeColumns(); let newColumns = await epic.getColumns(); expect(currentColumns).to.be.above(newColumns); }); - it.only('create Epic', async function() { - let date = Date.now(); - let description = Math.random().toString(36).substring(7); - let epic = epicsHelper.epic(); - let currentEpicsNum = await epic.getEpics(); - await epic.createEpic(date, description); - let newEpicsNum = await epic.getEpics(); - console.log(currentEpicsNum, newEpicsNum); - expect(newEpicsNum).to.be.above(currentEpicsNum); - }); - }) diff --git a/e2e/suites/epics/epic-detail.e2e.js b/e2e/suites/epics/epic-detail.e2e.js new file mode 100644 index 00000000..66c5e34a --- /dev/null +++ b/e2e/suites/epics/epic-detail.e2e.js @@ -0,0 +1,100 @@ +var utils = require('../../utils'); +var sharedDetail = require('../../shared/detail'); +var epicDetailHelper = require('../../helpers').epicDetail; + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe('Epic detail', async function(){ + let epicUrl = ''; + + before(async function(){ + await utils.nav + .init() + .project('Project Example 0') + .epics() + .epic(0) + .go(); + + epicUrl = await browser.getCurrentUrl(); + }); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "detail"); + }); + + it('color edition', async function() { + let colorEditor = epicDetailHelper.colorEditor(); + await colorEditor.open(); + await colorEditor.selectFirstColor(); + await colorEditor.open(); + await colorEditor.selectLastColor(); + await utils.common.takeScreenshot("epics", "detail color updated"); + }); + + it('title edition', sharedDetail.titleTesting); + + it('tags edition', sharedDetail.tagsTesting); + + describe('description', sharedDetail.descriptionTesting); + + describe('related userstories', function() { + let relatedUserstories = epicDetailHelper.relatedUserstories(); + it('create new user story', async function(){ + await relatedUserstories.createNewUserStory("Testing subject"); + }); + + it('create new user stories in bulk', async function(){ + await relatedUserstories.createNewUserStories("Testing subject1\nTesting subject 2"); + }); + + it('add related userstory', async function(){ + await relatedUserstories.selectFirstRelatedUserstory(); + }); + + it('delete related userstory', async function(){ + await relatedUserstories.deleteFirstRelatedUserstory(); + }) + }); + + it('status edition', sharedDetail.statusTesting.bind(this, 'Ready', 'In progress')); + + describe('assigned to edition', sharedDetail.assignedToTesting); + + describe('watchers edition', sharedDetail.watchersTesting); + + it('history', sharedDetail.historyTesting.bind(this, "epics")); + + it('block', sharedDetail.blockTesting); + + describe('team requirement edition', sharedDetail.teamRequirementTesting); + + describe('client requirement edition', sharedDetail.clientRequirementTesting); + + it('attachments', sharedDetail.attachmentTesting); + + describe('custom-fields', sharedDetail.customFields.bind(this, 0)); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "detail updated"); + }); + + describe('delete & redirect', function() { + it('delete', sharedDetail.deleteTesting); + + it('redirected', async function (){ + let url = await browser.getCurrentUrl(); + expect(url).not.to.be.equal(epicUrl); + }); + }); + +}); + + +/* +TODO: +# Related user stories +*/ diff --git a/e2e/suites/user-stories/user-story-detail.e2e.js b/e2e/suites/user-stories/user-story-detail.e2e.js index 9727efa6..bbc52509 100644 --- a/e2e/suites/user-stories/user-story-detail.e2e.js +++ b/e2e/suites/user-stories/user-story-detail.e2e.js @@ -36,35 +36,9 @@ describe('User story detail', function(){ describe('assigned to edition', sharedDetail.assignedToTesting); - it('team requirement edition', async function() { - let requirementHelper = usDetailHelper.teamRequirement(); - let isRequired = await requirementHelper.isRequired(); + describe('team requirement edition', sharedDetail.teamRequirementTesting); - // Toggle - requirementHelper.toggleStatus(); - let newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.not.equal(newIsRequired); - - // Toggle again - requirementHelper.toggleStatus(); - newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.equal(newIsRequired); - }); - - it('client requirement edition', async function() { - let requirementHelper = usDetailHelper.clientRequirement(); - let isRequired = await requirementHelper.isRequired(); - - // Toggle - requirementHelper.toggleStatus(); - let newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.not.equal(newIsRequired); - - // Toggle again - requirementHelper.toggleStatus(); - newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.equal(newIsRequired); - }); + describe('client requirement edition', sharedDetail.clientRequirementTesting); describe('watchers edition', sharedDetail.watchersTesting); diff --git a/e2e/utils/nav.js b/e2e/utils/nav.js index 17710dbe..b3c32daa 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -46,11 +46,21 @@ var actions = { return common.waitLoader(); }, + epics: async function() { await common.link($('#nav-epics a')); return common.waitLoader(); }, + + epic: async function(index) { + let epic = $$('.e2e-epic-row .name a').get(index); + + await common.link(epic); + + return common.waitLoader(); + }, + backlog: async function() { await common.link($$('#nav-backlog a').first()); @@ -110,6 +120,10 @@ var nav = { this.actions.push(actions.epics.bind(null, index)); return this; }, + epic: function(index) { + this.actions.push(actions.epic.bind(null, index)); + return this; + }, backlog: function(index) { this.actions.push(actions.backlog.bind(null, index)); return this; diff --git a/gulpfile.js b/gulpfile.js index b69e5c92..3fbc02a5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -130,6 +130,7 @@ paths.coffee_order = [ paths.app + "coffee/modules/backlog/*.coffee", paths.app + "coffee/modules/taskboard/*.coffee", paths.app + "coffee/modules/kanban/*.coffee", + paths.app + "coffee/modules/epics/*.coffee", paths.app + "coffee/modules/issues/*.coffee", paths.app + "coffee/modules/userstories/*.coffee", paths.app + "coffee/modules/tasks/*.coffee", diff --git a/run-e2e.js b/run-e2e.js index 77d8e48b..56d78341 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -12,6 +12,7 @@ var suites = [ 'wiki', 'admin', 'issues', + 'epics', 'tasks', 'userProfile', 'userStories', From c5f9cd54eb42f6738790947466a83fd46d08e114 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Aug 2016 12:34:40 +0200 Subject: [PATCH 056/137] Fixing status style on epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 91dbda04..c8d78a16 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -110,6 +110,7 @@ list-style-type: none; margin: 0; position: absolute; + text-align: left; top: 2.5rem; width: 200px; z-index: 99; From 7070f181ac7579ed7b6e4e0d0d12a21e1e075cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 29 Aug 2016 12:58:17 +0200 Subject: [PATCH 057/137] Create epics lightbox --- app/coffee/utils.coffee | 13 ++++++ .../color-selector.controller.coffee | 44 ++++++++----------- .../color-selector.controller.spec.coffee | 2 +- .../color-selector.directive.coffee | 25 ++++++----- .../color-selector/color-selector.jade | 18 +++++--- .../color-selector/color-selector.scss | 0 .../tags/components/add-tag-input.jade | 1 - .../create-epic/create-epic.controller.coffee | 28 +++++++++--- .../create-epic/create-epic.directive.coffee | 2 +- .../epics/create-epic/create-epic.jade | 4 +- .../epics-dashboard.controller.coffee | 4 +- app/partials/epic/epic-detail.jade | 3 +- 12 files changed, 88 insertions(+), 56 deletions(-) rename app/modules/components/{tags => }/color-selector/color-selector.controller.coffee (63%) rename app/modules/components/{tags => }/color-selector/color-selector.controller.spec.coffee (98%) rename app/modules/components/{tags => }/color-selector/color-selector.directive.coffee (73%) rename app/modules/components/{tags => }/color-selector/color-selector.jade (64%) rename app/modules/components/{tags => }/color-selector/color-selector.scss (100%) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index 1bffd7c7..b4580029 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -239,6 +239,17 @@ patch = (oldImmutable, newImmutable) -> return pathObj +DEFAULT_COLOR_LIST = [ + '#fce94f', '#edd400', '#c4a000', '#8ae234', '#73d216', '#4e9a06', '#d3d7cf', + '#fcaf3e', '#f57900', '#ce5c00', '#729fcf', '#3465a4', '#204a87', '#888a85', + '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000' +] + +getRandomDefaultColor = () -> + return _.sample(DEFAULT_COLOR_LIST) + +getDefaulColorList = () -> + return _.clone(DEFAULT_COLOR_LIST) taiga = @.taiga taiga.addClass = addClass @@ -267,3 +278,5 @@ taiga.defineImmutableProperty = defineImmutableProperty taiga.isImage = isImage taiga.isPdf = isPdf taiga.patch = patch +taiga.getRandomDefaultColor = getRandomDefaultColor +taiga.getDefaulColorList = getDefaulColorList diff --git a/app/modules/components/tags/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee similarity index 63% rename from app/modules/components/tags/color-selector/color-selector.controller.coffee rename to app/modules/components/color-selector/color-selector.controller.coffee index 2d817a15..d9779606 100644 --- a/app/modules/components/tags/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -17,41 +17,35 @@ # File: color-selector.controller.coffee ### -module = angular.module('taigaCommon') +taiga = @.taiga +getDefaulColorList = taiga.getDefaulColorList + class ColorSelectorController - constructor: () -> - @.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - '#8ae234', - '#73d216', - '#4e9a06', - '#d3d7cf', - '#fcaf3e', - '#f57900', - '#ce5c00', - '#729fcf', - '#3465a4', - '#204a87', - '#888a85', - '#ad7fa8', - '#75507b', - '#5c3566', - '#ef2929', - '#cc0000', - '#a40000' - ] + @.colorList = getDefaulColorList() + + if @.initColor + @.color = @.initColor + @.displaycolorList = false toggleColorList: () -> @.displaycolorList = !@.displaycolorList + if @.isRequired and not @.color + @.color = @.initColor + onSelectDropdownColor: (color) -> + @.color = color @.onSelectColor({color: color}) @.toggleColorList() + onKeyDown: (event) -> + if event.which == 13 # ENTER + event.stopPropagation() + if @.color or not @.isRequired + @.onSelectDropdownColor(@.color) -module.controller("ColorSelectorCtrl", ColorSelectorController) + +angular.module('taigaComponents').controller("ColorSelectorCtrl", ColorSelectorController) diff --git a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee similarity index 98% rename from app/modules/components/tags/color-selector/color-selector.controller.spec.coffee rename to app/modules/components/color-selector/color-selector.controller.spec.coffee index ec212331..7456195c 100644 --- a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -29,7 +29,7 @@ describe "ColorSelector", -> return null beforeEach -> - module "taigaCommon" + module "taigaComponents" _mocks() diff --git a/app/modules/components/tags/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee similarity index 73% rename from app/modules/components/tags/color-selector/color-selector.directive.coffee rename to app/modules/components/color-selector/color-selector.directive.coffee index 67e02f57..9652fb33 100644 --- a/app/modules/components/tags/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -17,21 +17,20 @@ # File: color-selector.directive.coffee ### -module = angular.module('taigaCommon') - ColorSelectorDirective = ($timeout) -> - link = (scope, el) -> - timeout = null + link = (scope, el, attrs, ctrl) -> + # Animation + _timeout = null cancel = () -> - $timeout.cancel(timeout) - timeout = null + $timeout.cancel(_timeout) + _timeout = null close = () -> - return if timeout + return if _timeout - timeout = $timeout (() -> - scope.vm.displaycolorList = false + _timeout = $timeout (() -> + scope.vm.toggleColorList() ), 400 el.find('.color-selector') @@ -45,17 +44,19 @@ ColorSelectorDirective = ($timeout) -> return { link: link, scope:{ + isRequired: "=" onSelectColor: "&", - color: "=" + initColor: "=" }, - templateUrl:"components/tags/color-selector/color-selector.html", + templateUrl:"components/color-selector/color-selector.html", controller: "ColorSelectorCtrl", controllerAs: "vm", bindToController: true } + ColorSelectorDirective.$inject = [ "$timeout" ] -module.directive("tgColorSelector", ColorSelectorDirective) +angular.module('taigaComponents').directive("tgColorSelector", ColorSelectorDirective) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade similarity index 64% rename from app/modules/components/tags/color-selector/color-selector.jade rename to app/modules/components/color-selector/color-selector.jade index 54402386..7ffccb49 100644 --- a/app/modules/components/tags/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -12,19 +12,23 @@ ng-title="color" ng-click="vm.onSelectDropdownColor(color)" ) - li.empty-color(ng-click="vm.onSelectDropdownColor(null)") + li.empty-color( + ng-if="!vm.isRequired" + ng-click="vm.onSelectDropdownColor(null)" + ) .custom-color-selector .display-custom-color.empty-color( - ng-if="!customColor.color || customColor.color.length < 7" + ng-if="!vm.color" ) .display-custom-color( - ng-if="customColor.color.length === 7" - ng-style="{'background': customColor.color}" - ng-click="vm.onSelectDropdownColor(customColor.color)" + ng-if="vm.color" + ng-style="{'background': vm.color}" + ng-click="vm.onSelectDropdownColor(vm.color)" ) input.custom-color-input( type="text" maxlength="7" - placeholder="#000000" - ng-model="customColor.color" + placeholder="Type hex code" + ng-model="vm.color" + ng-keydown="vm.onKeyDown($event)" ) diff --git a/app/modules/components/tags/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss similarity index 100% rename from app/modules/components/tags/color-selector/color-selector.scss rename to app/modules/components/color-selector/color-selector.scss diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade index 7e1b11ca..5e8de537 100644 --- a/app/modules/components/tags/components/add-tag-input.jade +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -21,7 +21,6 @@ tg-color-selector( ng-if="!vm.disableColorSelection" - color="vm.newTag.color", on-select-color="vm.selectColor(color)" ) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 638a2480..eb78e615 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -19,30 +19,40 @@ taiga = @.taiga trim = taiga.trim +getRandomDefaultColor = taiga.getRandomDefaultColor module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ "tgResources" + "tgAttachmentsService" + "$q" ] - constructor: (@rs) -> + constructor: (@rs, @attachmentsService, @q) -> @.newEpic = { - color: null - projecti: @.project.id + color: getRandomDefaultColor() + project: @.project.id status: @.project.default_epic_status tags: [] } @.attachments = Immutable.List() createEpic: () -> - return @rs.epics.post(@.newEpic).then () => - @.onReloadEpics() + promise = @rs.epics.post(@.newEpic) + promise.then (response) => + @._createAttachments(response.data) + + promise.then (data) => + @.onCreateEpic() + + # Color selector selectColor: (color) -> @.newEpic.color = color + # Tags addTag: (name, color) -> name = trim(name.toLowerCase()) @@ -52,5 +62,13 @@ class CreateEpicController deleteTag: (tag) -> _.remove @.newEpic.tags, (it) -> it[0] == tag[0] + # Attachments + 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) module.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 6fd7883a..3e9e8f36 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -28,7 +28,7 @@ CreateEpicDirective = () -> bindToController: true, scope: { project: '=', - onReloadEpics: '&' + onCreateEpic: '&' } } diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 7d1e7e73..99efc7f9 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -7,7 +7,8 @@ tg-lightbox-close ) fieldset tg-color-selector( - color="vm.newEpic.color", + is-required="true" + init-color="vm.newEpic.color" on-select-color="vm.selectColor(color)" ) input.e2e-create-epic-subject( @@ -46,6 +47,7 @@ tg-lightbox-close fieldset tg-attachments-simple( attachments="vm.attachments" + on-add="vm.addAttachment(attachment)" ) .settings fieldset.team-requirement diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 26c2edbf..d6f23146 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -55,10 +55,10 @@ class EpicsDashboardController @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" "project": "project" - "on-reload-epics": "reloadEpics()" + "on-create-epic": "onCreateEpic()" }, { "project": @.project - "reloadEpics": @._onCreateEpic.bind(this) + "onCreateEpic": @._onCreateEpic.bind(this) }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index b03ec020..edee4440 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -20,7 +20,8 @@ div.wrapper( .detail-header-container tg-color-selector( - color="epic.color", + is-required="true" + init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" ) tg-detail-header( From 7aa281c52089739b78065462c878c7223364bcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 30 Aug 2016 21:04:24 +0200 Subject: [PATCH 058/137] Improve add-epic form --- .../color-selector.controller.coffee | 10 +++++---- .../color-selector.directive.coffee | 15 ++++++------- .../color-selector/color-selector.jade | 2 +- .../create-epic/create-epic.controller.coffee | 21 +++++++++++++------ .../create-epic/create-epic.directive.coffee | 15 ++++++++++--- .../epics/create-epic/create-epic.jade | 14 ++++++------- 6 files changed, 48 insertions(+), 29 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index d9779606..fc1214d0 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -28,14 +28,16 @@ class ColorSelectorController if @.initColor @.color = @.initColor - @.displaycolorList = false - - toggleColorList: () -> - @.displaycolorList = !@.displaycolorList + @.displayColorList = false + resetColor: () -> if @.isRequired and not @.color @.color = @.initColor + toggleColorList: () -> + @.displayColorList = !@.displayColorList + @.resetColor() + onSelectDropdownColor: (color) -> @.color = color @.onSelectColor({color: color}) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index 9652fb33..817d2e9b 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -30,7 +30,8 @@ ColorSelectorDirective = ($timeout) -> return if _timeout _timeout = $timeout (() -> - scope.vm.toggleColorList() + ctrl.displayColorList = false + ctrl.resetColor() ), 400 el.find('.color-selector') @@ -43,15 +44,15 @@ ColorSelectorDirective = ($timeout) -> return { link: link, - scope:{ - isRequired: "=" - onSelectColor: "&", - initColor: "=" - }, templateUrl:"components/color-selector/color-selector.html", controller: "ColorSelectorCtrl", controllerAs: "vm", - bindToController: true + bindToController: { + isRequired: "=", + onSelectColor: "&", + initColor: "=" + }, + scope: {}, } diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 7ffccb49..80025485 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -4,7 +4,7 @@ ng-class="{'empty-color': !vm.color}" ng-style="{'background': vm.color}" ) - .color-selector-dropdown(ng-if="vm.displaycolorList") + .color-selector-dropdown(ng-if="vm.displayColorList") ul.color-selector-dropdown-list.e2e-color-dropdown li.color-selector-option( ng-repeat="color in vm.colorList" diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index eb78e615..1fcbb4d9 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -21,16 +21,16 @@ taiga = @.taiga trim = taiga.trim getRandomDefaultColor = taiga.getRandomDefaultColor -module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ "tgResources" + "$tgConfirm" "tgAttachmentsService" "$q" ] - constructor: (@rs, @attachmentsService, @q) -> + constructor: (@rs, @confirm, @attachmentsService, @q) -> @.newEpic = { color: getRandomDefaultColor() project: @.project.id @@ -40,13 +40,22 @@ class CreateEpicController @.attachments = Immutable.List() createEpic: () -> - promise = @rs.epics.post(@.newEpic) + return if not @.validateForm() + @.loading = true + + promise = @rs.epics.post(@.newEpic) promise.then (response) => @._createAttachments(response.data) - - promise.then (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 # Color selector selectColor: (color) -> @@ -71,4 +80,4 @@ class CreateEpicController return attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') return @q.all(promises) -module.controller("CreateEpicCtrl", CreateEpicController) +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 3e9e8f36..abb527a7 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -20,16 +20,25 @@ module = angular.module('taigaEpics') CreateEpicDirective = () -> + link = (scope, el, attrs, ctrl) -> + form = el.find("form").checksley() + + ctrl.validateForm = => + return form.validate() + + ctrl.setFormErrors = (errors) => + form.setErrors(errors) return { + link: link, templateUrl:"epics/create-epic/create-epic.html", controller: "CreateEpicCtrl", controllerAs: "vm", - bindToController: true, - scope: { + bindToController: { project: '=', onCreateEpic: '&' - } + }, + scope: {} } CreateEpicDirective.$inject = [] diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 99efc7f9..29d3ac17 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -14,23 +14,19 @@ tg-lightbox-close input.e2e-create-epic-subject( type="text" name="subject" - maxlength="140" ng-model="vm.newEpic.subject" tg-auto-select placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" - required + data-required="true" + data-maxlength="140" ) fieldset select.e2e-create-epic-status( id="epic-status" name="status" ng-model="vm.newEpic.status" + ng-options="s.id as s.name for s in vm.project.epic_statuses | orderBy:'order'" ) - option( - ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-value="::status.id" - ng-selected="vm.project.default_epic_status" - ) {{::status.name}} fieldset.tags-block tg-tag-line-common( project="vm.project" @@ -93,7 +89,9 @@ tg-lightbox-close placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" ) fieldset - input.button-green.create-epic-button.e2e-create-epic-button( + button.button-green.create-epic-button.e2e-create-epic-button( type="submit" + tg-loading="vm.loading" + title="{{ 'EPICS.CREATE.CREATE_EPIC' | translate }}" translate="EPICS.CREATE.CREATE_EPIC" ) From a5a87a48c752e2fab49678c5fed5f4e9dfe0dd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 12:32:58 +0200 Subject: [PATCH 059/137] Fix some style errors in the create-epic lightbox --- app/locales/taiga/locale-en.json | 1 + .../epics/create-epic/create-epic.jade | 36 ++++++++++--------- .../epics/create-epic/create-epic.scss | 8 +++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index cd73c545..38a18a64 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -427,6 +427,7 @@ }, "CREATE": { "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", "TEAM_REQUIREMENT": "Team requirement", "CLIENT_REQUIREMENT": "Client requirement", "BLOCKED": "Blocked", diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 29d3ac17..da51d342 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -5,21 +5,25 @@ tg-lightbox-close form( ng-submit="vm.createEpic()" ) - fieldset - tg-color-selector( - is-required="true" - init-color="vm.newEpic.color" - on-select-color="vm.selectColor(color)" - ) - input.e2e-create-epic-subject( - type="text" - name="subject" - ng-model="vm.newEpic.subject" - tg-auto-select - placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" - data-required="true" - data-maxlength="140" - ) + .subject-container + .color-selector + fieldset + tg-color-selector( + is-required="true" + init-color="vm.newEpic.color" + on-select-color="vm.selectColor(color)" + ) + .subject + fieldset + input.e2e-create-epic-subject( + type="text" + name="subject" + ng-model="vm.newEpic.subject" + tg-auto-select + placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" + data-required="true" + data-maxlength="140" + ) fieldset select.e2e-create-epic-status( id="epic-status" @@ -37,7 +41,7 @@ tg-lightbox-close ) fieldset textarea.e2e-create-epic-description( - ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" + ng-attr-placeholder="{{'EPICS.CREATE.PLACEHOLDER_DESCRIPTION' | translate}}" ng-model="vm.newEpic.description" ) fieldset diff --git a/app/modules/epics/create-epic/create-epic.scss b/app/modules/epics/create-epic/create-epic.scss index ad2090c4..e778e047 100644 --- a/app/modules/epics/create-epic/create-epic.scss +++ b/app/modules/epics/create-epic/create-epic.scss @@ -7,6 +7,14 @@ max-width: 700px; width: 90%; } + .subject-container { + align-items: center; + display: flex; + .subject { + padding-left: 1rem; + width: 100%; + } + } .attachments { margin-bottom: 0; } From 61d99fea14d239898a16045b95773c0c52239ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:08:46 +0200 Subject: [PATCH 060/137] Fix test about color selector --- .../color-selector.controller.spec.coffee | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index 7456195c..09597fee 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,19 +36,14 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller - colorSelectorCtrl = controller "ColorSelectorCtrl" - colorSelectorCtrl.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - ] - colorSelectorCtrl.displaycolorList = false it "display Color Selector", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.toggleColorList() - expect(colorSelectorCtrl.displaycolorList).to.be.true + expect(colorSelectorCtrl.displayColorList).to.be.true it "on select Color", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.toggleColorList = sinon.stub() color = '#FFFFFF' @@ -58,3 +53,17 @@ describe "ColorSelector", -> colorSelectorCtrl.onSelectDropdownColor(color) expect(colorSelectorCtrl.toggleColorList).have.been.called expect(colorSelectorCtrl.onSelectColor).to.have.been.calledWith({color: color}) + + it "save on keydown Enter", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" + colorSelectorCtrl.onSelectDropdownColor = sinon.stub() + + event = {which: 13, stopPropagation: sinon.stub()} + color = "#fabada" + + colorSelectorCtrl.color = color + + colorSelectorCtrl.onKeyDown(event) + expect(event.stopPropagation).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color) From f0a6ab4c0b79f83164b391b7f75d7aa6007cf1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:09:08 +0200 Subject: [PATCH 061/137] remove a log trace --- .../discover-home-order-by.controller.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee index a2799d09..6b00be7e 100644 --- a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee @@ -44,7 +44,6 @@ class DiscoverHomeOrderByController orderBy: (type) -> @.currentOrderBy = type @.is_open = false - console.log "Ijsdfkldsfklj" @.onChange({orderBy: @.currentOrderBy}) angular.module("taigaDiscover").controller("DiscoverHomeOrderBy", DiscoverHomeOrderByController) From 547b5c64586fb950bf1263da632bb1d6c189d87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:09:35 +0200 Subject: [PATCH 062/137] Fix create-epic lightbox tests --- .../create-epic/create-epic.controller.coffee | 6 +- .../create-epic.controller.spec.coffee | 90 ++++++++++++++++--- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 1fcbb4d9..570ab10c 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -57,6 +57,8 @@ class CreateEpicController promise.finally () => @.loading = false + return promise + # Color selector selectColor: (color) -> @.newEpic.color = color @@ -76,8 +78,8 @@ class CreateEpicController @.attachments.push(attachment) _createAttachments: (epic) -> - promises = _.map @.attachments.toJS(), (attachment) -> - return attachmentsService.upload(attachment.file, epic.id, epic.project, '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.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index 3453206d..fa713fd4 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -32,11 +32,33 @@ describe "EpicRow", -> provide.value "tgResources", mocks.tgResources + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgAttachmentsService = () -> + mocks.tgAttachmentsService = { + upload: sinon.stub() + } + provide.value "tgAttachmentsService", mocks.tgAttachmentsService + + _mockQ = () -> + mocks.q = { + all: sinon.spy() + } + + provide.value "$q", mocks.q + + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() - + _mockTgConfirm() + _mockTgAttachmentsService() + _mockQ() return null beforeEach -> @@ -47,17 +69,65 @@ describe "EpicRow", -> inject ($controller) -> controller = $controller - it "create Epic", (done) -> - createEpicCtrl = controller "CreateEpicCtrl" - createEpicCtrl.project = { - id: 7 + it "create Epic with invalid form", () -> + data = { + project: {id: 1, default_epic_status: 1} + validateForm: sinon.stub() + setFormErrors: sinon.stub() + onCreateEpic: sinon.stub() } - createEpicCtrl.newEpic = { - project: createEpicCtrl.project.id + createEpicCtrl = controller "CreateEpicCtrl", null, data + createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + data.validateForm.withArgs().returns(false) + + createEpicCtrl.createEpic() + + expect(data.validateForm).have.been.called + expect(mocks.tgResources.epics.post).not.have.been.called + + it "create Epic successfully", (done) -> + data = { + project: {id: 1, default_epic_status: 1} + validateForm: sinon.stub() + setFormErrors: sinon.stub() + onCreateEpic: sinon.stub() } - createEpicCtrl.onReloadEpics = sinon.stub() - promise = mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve() + createEpicCtrl = controller "CreateEpicCtrl", null, data + createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + data.validateForm.withArgs().returns(true) + mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve( + {data: {id: 1, project: 1}} + ) createEpicCtrl.createEpic().then () -> - expect(createEpicCtrl.onReloadEpics).have.been.called + expect(data.validateForm).have.been.called + expect(mocks.tgAttachmentsService.upload).have.been.calledTwice + expect(createEpicCtrl.onCreateEpic).have.been.called done() + + # TODO: Talk with JuanFran. How to return a response with an object when a promise is rejected? + # reject_response = {data: {_error_message: "error"}} + # + # + #it "create Epic with an API error", (done) -> + # data = { + # project: {id: 1, default_epic_status: 1} + # validateForm: sinon.stub() + # setFormErrors: sinon.stub() + # onCreateEpic: sinon.stub() + # } + # createEpicCtrl = controller "CreateEpicCtrl", null, data + # createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + # data.validateForm.withArgs().returns(true) + # mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().reject(new Error("error")) + + # createEpicCtrl.createEpic().then () -> + # expect(data.validateForm).have.been.called + # expect(mocks.tgAttachmentsService.upload).not.have.been.called + # expect(createEpicCtrl.onCreateEpic).not.have.been.called + # expect(data.setFormErrors).have.been.called + # expect(mocks.tgConfirm.notify).have.been.called + # done() From a12047bc1c1a5e372bf0ced3c3f7ec3f7c61f219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 10:55:36 +0200 Subject: [PATCH 063/137] Remove some commented code --- .../create-epic.controller.spec.coffee | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index fa713fd4..7dac8ef5 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -106,28 +106,3 @@ describe "EpicRow", -> expect(mocks.tgAttachmentsService.upload).have.been.calledTwice expect(createEpicCtrl.onCreateEpic).have.been.called done() - - # TODO: Talk with JuanFran. How to return a response with an object when a promise is rejected? - # reject_response = {data: {_error_message: "error"}} - # - # - #it "create Epic with an API error", (done) -> - # data = { - # project: {id: 1, default_epic_status: 1} - # validateForm: sinon.stub() - # setFormErrors: sinon.stub() - # onCreateEpic: sinon.stub() - # } - # createEpicCtrl = controller "CreateEpicCtrl", null, data - # createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) - - # data.validateForm.withArgs().returns(true) - # mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().reject(new Error("error")) - - # createEpicCtrl.createEpic().then () -> - # expect(data.validateForm).have.been.called - # expect(mocks.tgAttachmentsService.upload).not.have.been.called - # expect(createEpicCtrl.onCreateEpic).not.have.been.called - # expect(data.setFormErrors).have.been.called - # expect(mocks.tgConfirm.notify).have.been.called - # done() From 862efde74aea20bbfa6d6ce7c48c3d7b4ed17ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 12:00:15 +0200 Subject: [PATCH 064/137] Fix belong-to-epic to use labels instead of pill --- .../belong-to-epics/belong-to-epics-pill.jade | 2 +- .../belong-to-epics/belong-to-epics-text.jade | 8 ++++---- .../belong-to-epics/belong-to-epics.directive.coffee | 11 +++++------ .../components/belong-to-epics/belong-to-epics.scss | 9 +++++++++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 61670efa..2ff75ba3 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,5 +1,5 @@ - var hash = "#"; -.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") +span.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( ng-style="{'background': epic.get('color')}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade index db9614b9..7d23bdbe 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-text.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -1,10 +1,10 @@ - var hash = "#"; span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id')") - .belong-to-epic-pill( - ng-style="{'background': epic.get('color')}" - title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" - ) a.belong-to-epic-text( href="" tg-nav="project-epics-detail:project=epic.getIn(['project', 'slug']),ref=epic.get('ref')" ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} + span.belong-to-epic-label( + ng-style="::{'background-color': epic.get('color')}" + translate="EPICS.EPIC" + ) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 08d4ac43..39eae9fb 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -25,19 +25,18 @@ BelongToEpicsDirective = () -> if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) - scope.getTemplateUrl = () -> - if attrs.format - return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" - return "components/belong-to-epics/belong-to-epics-pill.html" + templateUrl = (el, attrs) -> + if attrs.format + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return "components/belong-to-epics/belong-to-epics-pill.html" return { link: link, scope: { epics: '=' }, - template : '' + templateUrl: templateUrl } -BelongToEpicsDirective.$inject = [] module.directive("tgBelongToEpics", BelongToEpicsDirective) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e59c4fb0..e068c2c6 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -25,3 +25,12 @@ .belong-to-epic-text { margin-left: .25rem; } +.belong-to-epic-label { + @include font-type(light); + @include font-size(xsmall); + background: $grayer; + border-radius: .25rem; + color: $white; + margin: 0 .5rem; + padding: .1rem .25rem; +} From 5e2278b99aec1d6b4327a24ebbc242f5d037a7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 13:08:08 +0200 Subject: [PATCH 065/137] Minor refactor --- .../epics-table/epics-table.controller.coffee | 7 +++-- .../epics-table.controller.spec.coffee | 26 ++++++++++--------- .../epics-table/epics-table.directive.coffee | 9 ++----- 3 files changed, 19 insertions(+), 23 deletions(-) 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 ecc9ae69..a2032a99 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -35,14 +35,13 @@ class EpicsTableController progress: true } - toggleEpicTableOptions: () -> - @.displayOptions = !@.displayOptions - - _checkPermissions: () -> @.permissions = { canEdit: _.includes(@.project.my_permissions, 'modify_epic') } + toggleEpicTableOptions: () -> + @.displayOptions = !@.displayOptions + reorderEpics: (epic, index) -> console.log epic, index diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 95f19644..30978472 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -44,21 +44,23 @@ describe "EpicTable", -> expect(epicTableCtrl.displayOptions).to.be.false it "can edit", () -> - epicTableCtrl = controller "EpicsTableCtrl" - epicTableCtrl.project = { - my_permissions: [ - 'modify_epic' - ] + data = { + project: { + my_permissions: [ + 'modify_epic' + ] + } } - epicTableCtrl._checkPermissions() + epicTableCtrl = controller "EpicsTableCtrl", null, data expect(epicTableCtrl.permissions.canEdit).to.be.true it "can NOT edit", () -> - epicTableCtrl = controller "EpicsTableCtrl" - epicTableCtrl.project = { - my_permissions: [ - 'modify_us' - ] + data = { + project: { + my_permissions: [ + 'modify_us' + ] + } } - epicTableCtrl._checkPermissions() + epicTableCtrl = controller "EpicsTableCtrl", null, data expect(epicTableCtrl.permissions.canEdit).to.be.false 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 39827f77..ceb094a6 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -20,21 +20,16 @@ module = angular.module('taigaEpics') EpicsTableDirective = () -> - - link = (scope, el, attrs, ctrl) -> - ctrl._checkPermissions() - return { - link: link, templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", - bindToController: true, - scope: { + bindToController: { epics: "=", project: "=", onUpdateEpic: "&" } + scope: {} } EpicsTableDirective.$inject = [] From 84332ff538e684c4e791f9bf9c90686de6419575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 17:04:37 +0200 Subject: [PATCH 066/137] Fix tests --- .../epics-table/epics-table.controller.spec.coffee | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 30978472..98c5f3a0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -38,7 +38,14 @@ describe "EpicTable", -> controller = $controller it "toggle table options", () -> - epicTableCtrl = controller "EpicsTableCtrl" + data = { + project: { + my_permissions: [ + 'modify_epic' + ] + } + } + epicTableCtrl = controller "EpicsTableCtrl", null, data epicTableCtrl.displayOptions = true epicTableCtrl.toggleEpicTableOptions() expect(epicTableCtrl.displayOptions).to.be.false From 037a3d462fe36981b27f2205be900bd9d750cc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 2 Sep 2016 13:55:21 +0200 Subject: [PATCH 067/137] Sort epics --- .../epic-sortable.directive.coffee | 35 ----------- .../epics-sortable.directive.coffee | 61 +++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 5 +- .../dashboard/epics-table/epics-table.jade | 7 ++- 4 files changed, 69 insertions(+), 39 deletions(-) delete mode 100644 app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee create mode 100644 app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee diff --git a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee deleted file mode 100644 index b0d70468..00000000 --- a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee +++ /dev/null @@ -1,35 +0,0 @@ -### -# 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: epic-sortable.directive.coffee -### - -EpicSortableDirective = ($parse) -> - link = (scope, el, attrs) -> - - drake = dragula([el[0]]) - - scope.$on "$destroy", -> - el.off() - drake.destroy() - - return { - link: link - } - -EpicSortableDirective.$inject = [] - -angular.module("taigaComponents").directive("tgEpicSortable", EpicSortableDirective) diff --git a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee new file mode 100644 index 00000000..d9063f7a --- /dev/null +++ b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee @@ -0,0 +1,61 @@ +### +# 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-sortable.directive.coffee +### + +EpicsSortableDirective = ($parse) -> + link = (scope, el, attrs) -> + callback = $parse(attrs.tgEpicsSortable) + + drake = dragula([el[0]], { + copySortSource: false + copy: false + mirrorContainer: el[0] + moves: (item) -> + return $(item).is('div.epics-table-body-row') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + + epic = itemEl.scope().epic + newIndex = itemEl.index() + + scope.$apply () -> + callback(scope, {epic: epic, newIndex: newIndex}) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging + }) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +EpicsSortableDirective.$inject = [ + "$parse" +] + +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 a2032a99..37eb275b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -42,7 +42,8 @@ class EpicsTableController toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - reorderEpics: (epic, index) -> - console.log epic, index + reorderEpic: (epic, newIndex) -> + console.log epic, newIndex + module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 1056460c..baaad4e6 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -88,8 +88,11 @@ mixin epicSwitch(name, model) for="switch-progress" ) +epicSwitch('switch-progress', 'vm.column.progress') - .epics-table-body(tg-epic-sortable) - .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") + .epics-table-body(tg-epics-sortable="vm.reorderEpic(epic, newIndex)") + .epics-table-body-row( + tg-repeat="epic in vm.epics track by epic.get('id')" + tg-bind-scope + ) tg-epic-row.e2e-epic( epic="epic" project="vm.project" From da6cc6789729479afecd162fa06623a32f8629e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 6 Sep 2016 20:51:04 +0200 Subject: [PATCH 068/137] Divide some long lines --- app/coffee/modules/kanban/main.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 7b875662..f50f9007 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -102,7 +102,8 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi filtersReloadContent: () -> @.loadUserstories().then () => - openArchived = _.difference(@kanbanUserstoriesService.archivedStatus, @kanbanUserstoriesService.statusHide) + openArchived = _.difference(@kanbanUserstoriesService.archivedStatus, + @kanbanUserstoriesService.statusHide) if openArchived.length for statusId in openArchived @.loadUserStoriesForStatus({}, statusId) @@ -131,8 +132,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi addNewUs: (type, statusId) -> switch type - when "standard" then @rootscope.$broadcast("usform:new", @scope.projectId, statusId, @scope.usStatusList) - when "bulk" then @rootscope.$broadcast("usform:bulk", @scope.projectId, statusId) + when "standard" then @rootscope.$broadcast("usform:new", + @scope.projectId, statusId, @scope.usStatusList) + when "bulk" then @rootscope.$broadcast("usform:bulk", + @scope.projectId, statusId) editUs: (id) -> us = @kanbanUserstoriesService.getUs(id) From 99e04c369fd5c47cb7d0c751cd7195b21a96938a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 7 Sep 2016 15:29:00 +0200 Subject: [PATCH 069/137] Refactor epics module (need tests) --- app/coffee/app.coffee | 1 + app/locales/taiga/locale-en.json | 1 + .../create-epic/create-epic.controller.coffee | 43 ++++---- .../create-epic/create-epic.directive.coffee | 7 +- .../epics/create-epic/create-epic.jade | 10 +- .../epic-row/epic-row.controller.coffee | 94 ++++++------------ .../epic-row/epic-row.directive.coffee | 14 +-- .../epics/dashboard/epic-row/epic-row.jade | 29 +++--- .../epics-dashboard.controller.coffee | 52 +++++----- .../epics/dashboard/epics-dashboard.jade | 18 ++-- .../epics-sortable.directive.coffee | 7 +- .../epics-table/epics-table.controller.coffee | 21 ++-- .../epics-table/epics-table.directive.coffee | 10 +- .../dashboard/epics-table/epics-table.jade | 3 - .../story-row/story-row.controller.coffee | 7 +- .../story-row/story-row.directive.coffee | 5 - .../epics/dashboard/story-row/story-row.jade | 6 +- app/modules/epics/epics.service.coffee | 99 +++++++++++++++++++ .../resources/epics-resource.service.coffee | 7 ++ .../utils/isolate-click.directive.coffee | 27 +++++ app/modules/utils/utils.module.coffee | 20 ++++ 21 files changed, 286 insertions(+), 195 deletions(-) create mode 100644 app/modules/epics/epics.service.coffee create mode 100644 app/modules/utils/isolate-click.directive.coffee create mode 100644 app/modules/utils/utils.module.coffee 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", []) From 14e53ec76b752e2095f3f9b2f2f70e2a6e62fec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:19:21 +0200 Subject: [PATCH 070/137] Fix create-epic lightbox --- .../create-epic/create-epic.controller.coffee | 7 ++-- .../epics-dashboard.controller.coffee | 9 ++-- app/modules/epics/epics.service.coffee | 42 +++++++++---------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 119ec352..ba4ee2f0 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -52,14 +52,15 @@ class CreateEpicController @.loading = true - @epicsService.createEpic(@.epic, @.attachments) + @epicsService.createEpic(@.newEpic, @.attachments) .then (response) => # On success @.onCreateEpic() - .then null, (response) => # On error + @.loading = false + .catch (response) => # On error + @.loading = false @.setFormErrors(response.data) if response.data._error_message @confirm.notify("error", response.data._error_message) - @.loading = false # Color selector selectColor: (color) -> diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 199927a6..b7cc1b7b 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -54,13 +54,16 @@ class EpicsDashboardController return @projectService.hasPermission("add_epic") onCreateEpic: () -> + onCreateEpic = () => + @lightboxService.closeAll() + @confirm.notify("success") + return # To prevent error https://docs.angularjs.org/error/$parse/isecdom?p0=onCreateEpic() + @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" "on-create-epic": "onCreateEpic()" }, { - "onCreateEpic": () => - @lightboxService.closeAll() - @confirm.notify("success") + "onCreateEpic": onCreateEpic.bind(this) }) angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 0f6da542..01b3de08 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -21,22 +21,22 @@ taiga = @.taiga class EpicsService @.$inject = [ - "tgProjectService", - "tgAttachmentsService" - "tgResources", - "tgXhrErrorService", - "$q" + 'tgProjectService', + 'tgAttachmentsService' + 'tgResources', + 'tgXhrErrorService', + '$q' ] constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) -> @._epics = Immutable.List() - taiga.defineImmutableProperty @, "epics", () => return @._epics + taiga.defineImmutableProperty @, 'epics', () => return @._epics clear: () -> @._epics = Immutable.List() fetchEpics: () -> - return @resources.epics.list(@projectService.project.get("id")) + return @resources.epics.list(@projectService.project.get('id')) .then (epics) => @._epics = epics .catch (xhr) => @@ -46,54 +46,54 @@ class EpicsService return @resources.userstories.listInEpic(epic.get('id')) createEpic: (epicData, attachments) -> - @.epicData.project = @projectsService.project.id + epicData.project = @projectService.project.get('id') - return @resources.epics.post(@.epicData) + return @resources.epics.post(epicData) .then (epic) => promises = _.map attachments.toJS(), (attachment) => - @attachmentsService.upload(attachment.file, epic.get("id"), epic.get("project"), 'epic') + @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") + 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 + newOrder = if !previous then 0 else epic.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => - it.get("epics_order") == previous.get("epics_order") + it.get('epics_order') == previous.get('epics_order') setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => - [it.get('id'), it.get("epics_order")] + [it.get('id'), it.get('epics_order')] data = { order: newOrder, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.reorder(epic.get("id"), data, setOrders) + return @resources.epics.reorder(epic.get('id'), data, setOrders) .then () => @.fetchEpics() updateEpicStatus: (epic, statusId) -> data = { status: statusId, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.patch(epic.get("id"), data) + return @resources.epics.patch(epic.get('id'), data) .then () => @.fetchEpics() updateEpicAssignedTo: (epic, userId) -> data = { assigned_to: userId, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.patch(epic.get("id"), data) + return @resources.epics.patch(epic.get('id'), data) .then () => @.fetchEpics() -angular.module("taigaEpics").service("tgEpicsService", EpicsService) +angular.module('taigaEpics').service('tgEpicsService', EpicsService) From 8b5830a33f6a08eead47a76bfed6687773fe1e88 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 8 Sep 2016 09:31:10 +0200 Subject: [PATCH 071/137] Fixing initial color in color selector --- .../color-selector/color-selector.controller.coffee | 9 ++++----- .../color-selector/color-selector.directive.coffee | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index fc1214d0..c30d9729 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -24,13 +24,12 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController constructor: () -> @.colorList = getDefaulColorList() - - if @.initColor - @.color = @.initColor - @.displayColorList = false - resetColor: () -> + setColor: (color) -> + @.color = @.initColor + + resetColor: () -> if @.isRequired and not @.color @.color = @.initColor diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index 817d2e9b..fb091711 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -17,6 +17,8 @@ # File: color-selector.directive.coffee ### +bindOnce = @.taiga.bindOnce + ColorSelectorDirective = ($timeout) -> link = (scope, el, attrs, ctrl) -> # Animation @@ -42,6 +44,9 @@ ColorSelectorDirective = ($timeout) -> .mouseenter(cancel) .mouseleave(close) + bindOnce scope, 'vm.initColor', (color) -> + ctrl.setColor(color) + return { link: link, templateUrl:"components/color-selector/color-selector.html", From ac751f801997431d58d318bfcc3f9bb06b9bcb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:50:38 +0200 Subject: [PATCH 072/137] Change epics resource to return Immutable objects in post and patch calls --- app/modules/resources/epics-resource.service.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 8b39793e..a63029c9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -45,11 +45,13 @@ Resource = (urlsService, http) -> url = urlsService.resolve("epics") + "/#{id}" return http.patch(url, patch) + .then (result) -> Immutable.fromJS(result.data) service.post = (params) -> url = urlsService.resolve("epics") return http.post(url, params) + .then (result) -> Immutable.fromJS(result.data) service.reorder = (id, data, setOrders) -> url = urlsService.resolve("epics") + "/#{id}" From 9f6df07a405e4212150268d3b0930d9b502fe1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:59:19 +0200 Subject: [PATCH 073/137] Fix epic progress bar in epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.controller.coffee | 2 ++ 1 file changed, 2 insertions(+) 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 06d79ca4..52902e63 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -34,6 +34,8 @@ class EpicRowController # the code compatible with the old code @.project = @projectService.project.toJS() + @._calculateProgressBar() + _calculateProgressBar: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" From 7848cf826e188be7cacb082be1a0fd1e2eaa3f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 11:40:11 +0200 Subject: [PATCH 074/137] Fix epics sort --- app/modules/epics/epics.service.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 01b3de08..09eb9a96 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -58,14 +58,15 @@ class EpicsService 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 + + newOrder = if !previous then 0 else previous.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => it.get('epics_order') == previous.get('epics_order') - setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + setOrders = _.fromPairs previousWithTheSameOrder.map((it) => [it.get('id'), it.get('epics_order')] + ).toJS() data = { order: newOrder, From 1d22477410b838a7349f716dcbf4f2792aa74e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 19:10:39 +0200 Subject: [PATCH 075/137] Fix epics module unit tests --- .../create-epic/create-epic.controller.coffee | 1 - .../create-epic.controller.spec.coffee | 58 ++-- .../epic-row/epic-row.controller.coffee | 10 +- .../epic-row/epic-row.controller.spec.coffee | 287 ++++++++---------- .../epics-dashboard.controller.coffee | 14 +- .../epics-dashboard.controller.spec.coffee | 145 +++++---- .../epics/dashboard/epics-dashboard.jade | 2 +- .../epics-table.controller.spec.coffee | 46 +-- ...related-userstories.controller.spec.coffee | 17 +- app/modules/services/project.service.coffee | 28 +- 10 files changed, 289 insertions(+), 319 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index ba4ee2f0..8ab9ef15 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -39,7 +39,6 @@ class CreateEpicController @.newEpic = { color: getRandomDefaultColor() - project: @.project.id status: @.project.default_epic_status tags: [] } diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index 7dac8ef5..cd588888 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -23,42 +23,32 @@ describe "EpicRow", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - epics: { - post: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } provide.value "$tgConfirm", mocks.tgConfirm - _mockTgAttachmentsService = () -> - mocks.tgAttachmentsService = { - upload: sinon.stub() + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: { + toJS: sinon.stub() + } } - provide.value "tgAttachmentsService", mocks.tgAttachmentsService + provide.value "tgProjectService", mocks.tgProjectService - _mockQ = () -> - mocks.q = { - all: sinon.spy() + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + createEpic: sinon.stub() } - - provide.value "$q", mocks.q - + provide.value "tgEpicsService", mocks.tgEpicsService _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgConfirm() - _mockTgAttachmentsService() - _mockQ() + _mockTgProjectService() + _mockTgEpicsService() return null beforeEach -> @@ -70,8 +60,11 @@ describe "EpicRow", -> controller = $controller it "create Epic with invalid form", () -> + mocks.tgProjectService.project.toJS.withArgs().returns( + {id: 1, default_epic_status: 1} + ) + data = { - project: {id: 1, default_epic_status: 1} validateForm: sinon.stub() setFormErrors: sinon.stub() onCreateEpic: sinon.stub() @@ -84,11 +77,14 @@ describe "EpicRow", -> createEpicCtrl.createEpic() expect(data.validateForm).have.been.called - expect(mocks.tgResources.epics.post).not.have.been.called + expect(mocks.tgEpicsService.createEpic).not.have.been.called it "create Epic successfully", (done) -> + mocks.tgProjectService.project.toJS.withArgs().returns( + {id: 1, default_epic_status: 1} + ) + data = { - project: {id: 1, default_epic_status: 1} validateForm: sinon.stub() setFormErrors: sinon.stub() onCreateEpic: sinon.stub() @@ -97,12 +93,16 @@ describe "EpicRow", -> createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) data.validateForm.withArgs().returns(true) - mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve( - {data: {id: 1, project: 1}} - ) + mocks.tgEpicsService.createEpic + .withArgs( + createEpicCtrl.newEpic, + createEpicCtrl.attachments) + .promise() + .resolve( + {data: {id: 1, project: 1}} + ) createEpicCtrl.createEpic().then () -> expect(data.validateForm).have.been.called - expect(mocks.tgAttachmentsService.upload).have.been.calledTwice expect(createEpicCtrl.onCreateEpic).have.been.called done() 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 52902e63..65a82333 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -40,13 +40,13 @@ class EpicRowController if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" else - @.opened = @.epic.getIn(['user_stories_counts', 'opened']) - @.closed = @.epic.getIn(['user_stories_counts', 'closed']) - @.total = @.opened + @.closed - if @.total == 0 + opened = @.epic.getIn(['user_stories_counts', 'opened']) + closed = @.epic.getIn(['user_stories_counts', 'closed']) + total = opened + closed + if total == 0 @.percentage = "0%" else - @.percentage = "#{@.closed * 100 / @.total}%" + @.percentage = "#{closed * 100 / total}%" canEditEpics: () -> return @projectService.hasPermission("modify_epic") diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee index 6205a6df..6e287471 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -23,31 +23,34 @@ describe "EpicRow", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - epics: { - patch: sinon.stub() - }, - userstories: { - listInEpic: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } - provide.value "$tgConfirm", mocks.tgConfirm + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: { + toJS: sinon.stub() + } + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + listRelatedUserStories: sinon.stub() + updateEpicStatus: sinon.stub() + updateEpicAssignedTo: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgConfirm() - + _mockTgProjectService() + _mockTgEpicsService() return null beforeEach -> @@ -58,183 +61,139 @@ describe "EpicRow", -> inject ($controller) -> controller = $controller - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = false - EpicRowCtrl.displayAssignedTo = false - EpicRowCtrl.loadingStatus = false - it "calculate progress bar in open US", () -> - - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: false - } - user_stories_counts: { - opened: 10, - closed: 10 - } - }) - - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.opened).to.be.equal(10) - expect(EpicRowCtrl.closed).to.be.equal(10) - expect(EpicRowCtrl.total).to.be.equal(20) - expect(EpicRowCtrl.percentage).to.be.equal("50%") - - it "calculate progress bar in zero US", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: false - } - user_stories_counts: { - opened: 0, - closed: 0 - } - }) - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.opened).to.be.equal(0) - expect(EpicRowCtrl.closed).to.be.equal(0) - expect(EpicRowCtrl.total).to.be.equal(0) - expect(EpicRowCtrl.percentage).to.be.equal("0%") - - it "calculate progress bar in zero US", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: true - } - }) - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.percentage).to.be.equal("100%") - - it "Update Epic Status Success", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - - EpicRowCtrl.patch = { - 'status': 'new', - 'version': EpicRowCtrl.epic.get('version') + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 10, + closed: 10 + } + }) } - EpicRowCtrl.loadingStatus = true - EpicRowCtrl.onUpdateEpic = sinon.stub() + ctrl._calculateProgressBar() + expect(ctrl.percentage).to.be.equal("50%") - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + it "calculate progress bar in zero US", () -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 0, + closed: 0 + } + }) + } + expect(ctrl.percentage).to.be.equal("0%") - status = "new" - EpicRowCtrl.updateEpicStatus(status).then () -> - expect(EpicRowCtrl.loadingStatus).to.be.false - expect(EpicRowCtrl.displayStatusList).to.be.false - expect(EpicRowCtrl.onUpdateEpic).to.be.called + it "calculate progress bar in zero US", () -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: true + } + }) + } + expect(ctrl.percentage).to.be.equal("100%") + + it "Update Epic Status Success", (done) -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + version: 1 + }) + } + + statusId = 1 + + promise = mocks.tgEpicsService.updateEpicStatus + .withArgs(ctrl.epic, statusId) + .promise() + .resolve() + + ctrl.loadingStatus = true + ctrl.displayStatusList = true + + ctrl.updateStatus(statusId).then () -> + expect(ctrl.loadingStatus).to.be.false + expect(ctrl.displayStatusList).to.be.false done() it "Update Epic Status Error", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - - EpicRowCtrl.patch = { - 'status': 'new', - 'version': EpicRowCtrl.epic.get('version') + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + version: 1 + }) } - EpicRowCtrl.loadingStatus = true - EpicRowCtrl.onUpdateEpic = sinon.stub() + statusId = 1 - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().reject(new Error('error')) + promise = mocks.tgEpicsService.updateEpicStatus + .withArgs(ctrl.epic, statusId) + .promise() + .reject(new Error('error')) - status = "new" - EpicRowCtrl.updateEpicStatus(status).then () -> + ctrl.updateStatus(statusId).then () -> + expect(ctrl.loadingStatus).to.be.false + expect(ctrl.displayStatusList).to.be.false expect(mocks.tgConfirm.notify).have.been.calledWith('error') done() it "display User Stories", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } - EpicRowCtrl.displayUserStories = false - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) - data = true + ctrl.displayUserStories = false - promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().resolve(data) + data = Immutable.List() - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> - expect(EpicRowCtrl.displayUserStories).to.be.true - expect(EpicRowCtrl.epicStories).is.equal(data) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .resolve(data) + + ctrl.toggleUserStoryList().then () -> + expect(ctrl.displayUserStories).to.be.true + expect(ctrl.epicStories).is.equal(data) done() it "display User Stories error", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = false + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) + ctrl.displayUserStories = false - promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().reject(new Error('error')) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .reject(new Error('error')) - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + ctrl.toggleUserStoryList().then () -> + expect(ctrl.displayUserStories).to.be.false expect(mocks.tgConfirm.notify).have.been.calledWith('error') done() - it "DO NOT display User Stories", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = true - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic) - expect(EpicRowCtrl.displayUserStories).to.be.false - - it "On remove assigned", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - EpicRowCtrl.patch = { - 'assigned_to': null, - 'version': EpicRowCtrl.epic.get('version') - } - EpicRowCtrl.onUpdateEpic = sinon.stub() - - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() - - EpicRowCtrl.onRemoveAssigned().then () -> - expect(EpicRowCtrl.onUpdateEpic).to.have.been.called - - it "On assign to", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - id = EpicRowCtrl.epic.get('id') - version = EpicRowCtrl.epic.get('version') - member = { - id: 1 - } - EpicRowCtrl.patch = { - assigned_to: member.id - version: EpicRowCtrl.epic.get('version') + it "display User Stories error", -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) } - EpicRowCtrl.onUpdateEpic = sinon.stub() + ctrl.displayUserStories = true - promise = mocks.tgResources.epics.patch.withArgs(id, EpicRowCtrl.patch).promise().resolve(member) - EpicRowCtrl.onAssignTo(member).then () -> - expect(EpicRowCtrl.onUpdateEpic).to.have.been.called - expect(mocks.tgConfirm.notify).have.been.calledWith('success') - done() + ctrl.toggleUserStoryList() + + expect(ctrl.displayUserStories).to.be.false diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index b7cc1b7b..043d8e92 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -39,16 +39,16 @@ class EpicsDashboardController taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - @._loadInitialData() - - _loadInitialData: () -> + loadInitialData: () -> @epicsService.clear() - @projectService.setProjectBySlug(@params.pslug) + return @projectService.setProjectBySlug(@params.pslug) .then () => - if not @.project.get("is_epics_activated") or not @projectService.hasPermission("view_epics") - @errorHandlingService.permissionDenied() + if not @projectService.isEpicsDashboardEnabled() + return @errorHandlingService.notFound() + if not @projectService.hasPermission("view_epics") + return @errorHandlingService.permissionDenied() - @epicsService.fetchEpics() + return @epicsService.fetchEpics() canCreateEpics: () -> return @projectService.hasPermission("add_epic") diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index 12b98e64..a18b7c4a 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -23,41 +23,38 @@ describe "EpicsDashboard", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - projects: { - getBySlug: sinon.stub() - } - } - - provide.value "$tgResources", mocks.tgResources - - _mockTgResourcesNew = () -> - mocks.tgResourcesNew = { - epics: { - list: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResourcesNew - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } - provide.value "$tgConfirm", mocks.tgConfirm + _mockTgProjectService = () -> + mocks.tgProjectService = { + setProjectBySlug: sinon.stub() + hasPermission: sinon.stub() + isEpicsDashboardEnabled: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + clear: sinon.stub() + fetchEpics: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mockRouteParams = () -> - mocks.routeparams = { + mocks.routeParams = { pslug: sinon.stub() } - provide.value "$routeParams", mocks.routeparams + provide.value "$routeParams", mocks.routeParams _mockTgErrorHandlingService = () -> mocks.tgErrorHandlingService = { permissionDenied: sinon.stub() + notFound: sinon.stub() } provide.value "tgErrorHandlingService", mocks.tgErrorHandlingService @@ -76,23 +73,16 @@ describe "EpicsDashboard", -> provide.value "lightboxService", mocks.lightboxService - _mockTgConfirm = () -> - mocks.tgConfirm = { - notify: sinon.stub() - } - - provide.value "$tgConfirm", mocks.tgConfirm - _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() - _mockTgResourcesNew() + _mockTgConfirm() + _mockTgProjectService() + _mockTgEpicsService() _mockRouteParams() _mockTgErrorHandlingService() _mockTgLightboxFactory() _mockLightboxService() - _mockTgConfirm() return null @@ -104,39 +94,70 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + it "load data because epics panel is enabled and user has permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") - it "load projects", (done) -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - params = mocks.routeparams.pslug - EpicsDashboardCtrl.loadEpics = sinon.stub() - project = { - is_epics_activated: false - } - promise = mocks.tgResources.projects.getBySlug.withArgs(params).promise().resolve(project) - EpicsDashboardCtrl.loadProject().then () -> + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(true) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(true) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).not.have.been.called + expect(mocks.tgEpicsService.fetchEpics).have.been.called + done() + + it "not load data because epics panel is not enabled", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(true) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(false) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called + done() + + it "not load data because user has not permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(false) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(true) + + ctrl.loadInitialData().then () -> expect(mocks.tgErrorHandlingService.permissionDenied).have.been.called - expect(EpicsDashboardCtrl.project).is.equal(project) - expect(EpicsDashboardCtrl.loadEpics).have.been.called + expect(mocks.tgErrorHandlingService.notFound).not.have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called done() - it "load epics", (done) -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - EpicsDashboardCtrl.project = { - id: 1 - } - epics = { - id: 1 - } - promise = mocks.tgResourcesNew.epics.list.withArgs(EpicsDashboardCtrl.project.id).promise().resolve(epics) - EpicsDashboardCtrl.loadEpics().then () -> - expect(EpicsDashboardCtrl.epics).is.equal(epics) - done() + it "not load data because epics panel is not enabled and user has not permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") - it "on create epic", () -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - EpicsDashboardCtrl.loadEpics = sinon.stub() - EpicsDashboardCtrl._onCreateEpic() - expect(mocks.lightboxService.closeAll).have.been.called - expect(mocks.tgConfirm.notify).have.been.calledWith("success") - expect(EpicsDashboardCtrl.loadEpics).have.been.called + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(false) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(false) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called + done() diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index f9747455..1fa89a3e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,4 +1,4 @@ -.wrapper(ng-init="vm.loadProject()") +.wrapper(ng-init="vm.loadInitialData()") tg-project-menu section.main(role="main") header.header-with-actions diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 98c5f3a0..cdd83c6c 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -23,10 +23,23 @@ describe "EpicTable", -> controller = null mocks = {} + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + createEpic: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide - + _mockTgConfirm() + _mockTgEpicsService() return null beforeEach -> @@ -38,36 +51,7 @@ describe "EpicTable", -> controller = $controller it "toggle table options", () -> - data = { - project: { - my_permissions: [ - 'modify_epic' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data + epicTableCtrl = controller "EpicsTableCtrl" epicTableCtrl.displayOptions = true epicTableCtrl.toggleEpicTableOptions() expect(epicTableCtrl.displayOptions).to.be.false - - it "can edit", () -> - data = { - project: { - my_permissions: [ - 'modify_epic' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data - expect(epicTableCtrl.permissions.canEdit).to.be.true - - it "can NOT edit", () -> - data = { - project: { - my_permissions: [ - 'modify_us' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data - expect(epicTableCtrl.permissions.canEdit).to.be.false diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 9162c935..3498e404 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -32,11 +32,17 @@ describe "RelatedUserStories", -> provide.value "tgResources", mocks.tgResources + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + } + + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() - + _mockTgEpicsService() return null beforeEach -> @@ -47,20 +53,19 @@ describe "RelatedUserStories", -> inject ($controller) -> controller = $controller - RelatedUserStoriesCtrl = controller "RelatedUserStoriesCtrl" - it "load related userstories", (done) -> + ctrl = controller "RelatedUserStoriesCtrl" userstories = Immutable.fromJS([ { id: 1 } ]) - RelatedUserStoriesCtrl.epic = Immutable.fromJS({ + ctrl.epic = Immutable.fromJS({ id: 66 }) promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) - RelatedUserStoriesCtrl.loadRelatedUserstories().then () -> - expect(RelatedUserStoriesCtrl.userstories).is.equal(userstories) + ctrl.loadRelatedUserstories().then () -> + expect(ctrl.userstories).is.equal(userstories) done() diff --git a/app/modules/services/project.service.coffee b/app/modules/services/project.service.coffee index a6640ac5..649147b4 100644 --- a/app/modules/services/project.service.coffee +++ b/app/modules/services/project.service.coffee @@ -36,6 +36,12 @@ class ProjectService taiga.defineImmutableProperty @, "sectionsBreadcrumb", () => return @._sectionsBreadcrumb taiga.defineImmutableProperty @, "activeMembers", () => return @._activeMembers + cleanProject: () -> + @._project = null + @._activeMembers = Immutable.List() + @._section = null + @._sectionsBreadcrumb = Immutable.List() + setSection: (section) -> @._section = section @@ -44,6 +50,10 @@ class ProjectService else @._sectionsBreadcrumb = Immutable.List() + setProject: (project) -> + @._project = project + @._activeMembers = @._project.get('members').filter (member) -> member.get('is_active') + setProjectBySlug: (pslug) -> return new Promise (resolve, reject) => if !@.project || @.project.get('slug') != pslug @@ -57,23 +67,15 @@ class ProjectService else resolve() - setProject: (project) -> - @._project = project - @._activeMembers = @._project.get('members').filter (member) -> member.get('is_active') - - cleanProject: () -> - @._project = null - @._activeMembers = Immutable.List() - @._section = null - @._sectionsBreadcrumb = Immutable.List() - - hasPermission: (permission) -> - return @._project.get('my_permissions').indexOf(permission) != -1 - fetchProject: () -> pslug = @.project.get('slug') return @projectsService.getProjectBySlug(pslug).then (project) => @.setProject(project) + hasPermission: (permission) -> + return @._project.get('my_permissions').indexOf(permission) != -1 + + isEpicsDashboardEnabled: -> + return @._project.get("is_epics_activated") angular.module("taigaCommon").service("tgProjectService", ProjectService) From a275d88506e4cb712c9047eae3bcbcf30efb40f2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 8 Sep 2016 15:16:03 +0200 Subject: [PATCH 076/137] Epic detail page --- app/modules/epics/epics.service.coffee | 22 ++++ .../related-userstories-controller.coffee | 8 +- ...ated-userstories-sortable.directive.coffee | 65 ++++++++++ ...related-userstories.controller.spec.coffee | 35 ++++++ .../related-userstories.jade | 23 ++-- .../related-userstories.scss | 108 ----------------- .../related-userstory-row.jade | 4 + .../related-userstory-row.scss | 112 ++++++++++++++++++ .../resources/epics-resource.service.coffee | 7 ++ 9 files changed, 264 insertions(+), 120 deletions(-) create mode 100644 app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 09eb9a96..27e8f0b2 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -77,6 +77,28 @@ class EpicsService .then () => @.fetchEpics() + reorderRelatedUserstory: (epic, epicUserstories, userstory, newIndex) -> + withoutMoved = epicUserstories.filter (it) => it.get('id') != userstory.get('id') + beforeDestination = withoutMoved.slice(0, newIndex) + + previous = beforeDestination.last() + newOrder = if !previous then 0 else previous.get('epic_order') + 1 + + previousWithTheSameOrder = beforeDestination.filter (it) => + it.get('epic_order') == previous.get('epic_order') + + setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + [it.get('id'), it.get('epic_order')] + + data = { + order: newOrder + } + epicId = epic.get('id') + userstoryId = userstory.get('id') + return @resources.epics.reorderRelatedUserstory(epicId, userstoryId, data, setOrders) + .then () => + return @.listRelatedUserStories(epic) + updateEpicStatus: (epic, statusId) -> data = { status: statusId, diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 8042fa8f..4b9be953 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,9 +20,9 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgResources"] + @.$inject = ["tgResources", "tgEpicsService"] - constructor: (@rs) -> + constructor: (@rs, @epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false @@ -30,4 +30,8 @@ class RelatedUserStoriesController @rs.userstories.listInEpic(@.epic.get('id')).then (data) => @.userstories = data + reorderRelatedUserstory: (us, newIndex) -> + @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex).then (userstories) => + @.userstories = userstories + module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee b/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee new file mode 100644 index 00000000..1989e7d5 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee @@ -0,0 +1,65 @@ +### +# 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: related-userstories-sortable.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoriesSortableDirective = ($parse, projectService) -> + link = (scope, el, attrs) -> + return if not projectService.hasPermission("modify_epic") + + callback = $parse(attrs.tgRelatedUserstoriesSortable) + + drake = dragula([el[0]], { + copySortSource: false + copy: false + mirrorContainer: el[0] + moves: (item) -> + return $(item).is('tg-related-userstory-row') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + us = itemEl.scope().us + newIndex = itemEl.index() + + scope.$apply () -> + callback(scope, {us: us, newIndex: newIndex}) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging + }) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +RelatedUserstoriesSortableDirective.$inject = [ + "$parse", + "tgProjectService" +] + +module.directive("tgRelatedUserstoriesSortable", RelatedUserstoriesSortableDirective) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 3498e404..2543b085 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -34,6 +34,7 @@ describe "RelatedUserStories", -> _mockTgEpicsService = () -> mocks.tgEpicsService = { + reorderRelatedUserstory: sinon.stub() } provide.value "tgEpicsService", mocks.tgEpicsService @@ -69,3 +70,37 @@ describe "RelatedUserStories", -> ctrl.loadRelatedUserstories().then () -> expect(ctrl.userstories).is.equal(userstories) done() + + it "reorderRelatedUserstory", (done) -> + ctrl = controller "RelatedUserStoriesCtrl" + userstories = Immutable.fromJS([ + { + id: 1 + }, + { + id: 2 + } + ]) + + reorderedUserstories = Immutable.fromJS([ + { + id: 2 + }, + { + id: 1 + } + ]) + + ctrl.epic = Immutable.fromJS({ + id: 66 + }) + + + promise = mocks.tgEpicsService.reorderRelatedUserstory + .withArgs(ctrl.epic, ctrl.userstories, userstories.get(1), 0) + .promise() + .resolve(reorderedUserstories) + + ctrl.reorderRelatedUserstory(userstories.get(1), 0).then () -> + expect(ctrl.userstories).is.equal(reorderedUserstories) + done() diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index ecf642de..35a848b7 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -10,14 +10,17 @@ section.related-userstories load-related-userstories="vm.loadRelatedUserstories()" ) - .related-userstories-body - div(tg-repeat="us in vm.userstories track by us.get('id')") - tg-related-userstory-row.row( - ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" - userstory="us" - epic="vm.epic" - project="vm.project" - load-related-userstories="vm.loadRelatedUserstories()" - ) + .related-userstories-body( + tg-related-userstories-sortable="vm.reorderRelatedUserstory(us, newIndex)" + ) + tg-related-userstory-row.row( + tg-repeat="us in vm.userstories track by us.get('id')" + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + userstory="us" + epic="vm.epic" + project="vm.project" + load-related-userstories="vm.loadRelatedUserstories()" + tg-bind-scope + ) - div(tg-related-userstories-create-form) + div(tg-related-userstories-create-form) diff --git a/app/modules/epics/related-userstories/related-userstories.scss b/app/modules/epics/related-userstories/related-userstories.scss index 62bc0b46..67ba81a0 100644 --- a/app/modules/epics/related-userstories/related-userstories.scss +++ b/app/modules/epics/related-userstories/related-userstories.scss @@ -36,112 +36,4 @@ .related-userstories-body { width: 100%; - .row { - @include font-size(small); - align-items: center; - border-bottom: 1px solid $whitish; - display: flex; - padding: .5rem 0 .5rem .5rem; - &:hover { - .userstory-settings { - opacity: 1; - transition: all .2s ease-in; - } - } - .userstory-name { - flex: 1; - } - .userstory-settings { - flex-shrink: 0; - width: 60px; - } - .status { - flex-shrink: 0; - width: 125px; - } - .assigned-to-column { - flex-shrink: 0; - width: 150px; - img { - flex-basis: 35px; - // width & height they are only required for IE - height: 35px; - width: 35px; - } - } - .project { - flex-basis: 100px; - img { - width: 40px; - } - } - } - - .userstory-name { - display: flex; - margin-right: 1rem; - - span { - margin-right: .25rem; - } - } - .status { - position: relative; - } - .closed { - border-left: 10px solid $whitish; - color: $whitish; - a, - svg { - fill: $whitish; - } - .userstory-name a { - color: $whitish; - text-decoration: line-through; - - } - } - .blocked { - background: rgba($red-light, .2); - border-left: 10px solid $red-light; - } - .userstory-settings { - align-items: center; - display: flex; - opacity: 0; - svg { - @include svg-size(1.1rem); - fill: $gray-light; - margin-right: .5rem; - transition: fill .2s ease-in; - &:hover { - fill: $gray; - } - } - a { - &:hover { - cursor: pointer; - } - } - } - .delete-userstory { - &:hover { - .icon-trash { - fill: $red-light; - } - } - } - .avatar { - align-items: center; - display: flex; - img { - flex-basis: 35px; - // width & height they are only required for IE - height: 35px; - width: 35px; - } - figcaption { - margin-left: .5rem; - } - } } diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index 7c7b8a41..c7790332 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -1,3 +1,7 @@ +tg-svg.icon-drag( + svg-icon="icon-drag" +) + .userstory-name - var hash = "#"; a( diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss new file mode 100644 index 00000000..64b82340 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -0,0 +1,112 @@ +tg-related-userstory-row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: .5rem 0 .5rem .5rem; + &:hover { + background: rgba($primary-light, .05); + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + .icon-drag { + opacity: 1; + } + } + .icon-drag { + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; + } + .status { + flex-shrink: 0; + position: relative; + width: 125px; + } + .assigned-to-column { + flex-shrink: 0; + width: 150px; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + } + .project { + flex-basis: 100px; + img { + width: 40px; + } + } + .userstory-name { + display: flex; + flex: 1; + margin-right: 1rem; + + span { + margin-right: .25rem; + } + } + .closed { + border-left: 10px solid $whitish; + color: $whitish; + a, + svg { + fill: $whitish; + } + .userstory-name a { + color: $whitish; + text-decoration: line-through; + + } + } + .blocked { + background: rgba($red-light, .2); + border-left: 10px solid $red-light; + } + .userstory-settings { + align-items: center; + display: flex; + flex-shrink: 0; + opacity: 0; + width: 60px; + svg { + @include svg-size(1.1rem); + fill: $gray-light; + margin-right: .5rem; + transition: fill .2s ease-in; + &:hover { + fill: $gray; + } + } + a { + &:hover { + cursor: pointer; + } + } + } + .delete-userstory { + &:hover { + .icon-trash { + fill: $red-light; + } + } + } + .avatar { + align-items: center; + display: flex; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + figcaption { + margin-left: .5rem; + } + } +} diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index a63029c9..293830b9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -70,6 +70,13 @@ Resource = (urlsService, http) -> return http.post(url, params) + service.reorderRelatedUserstory = (epicId, userstoryId, data, setOrders) -> + url = urlsService.resolve("epic-related-userstories", epicId) + "/#{userstoryId}" + + options = {"headers": {"set-orders": JSON.stringify(setOrders)}} + + return http.patch(url, data, null, options) + service.bulkCreateRelatedUserStories = (epicId, projectId, bulk_userstories) -> url = urlsService.resolve("epic-related-userstories-bulk-create", epicId) From fb6b1ec09556a611501e64d715066e2041190321 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 08:21:31 +0200 Subject: [PATCH 077/137] Fixing epics reorder on epics dashboard --- app/modules/epics/epics.service.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 27e8f0b2..e5129024 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -69,7 +69,7 @@ class EpicsService ).toJS() data = { - order: newOrder, + epics_order: newOrder, version: epic.get('version') } From 802344104e99f1aea76d8be5f69cb559124ad86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 08:27:01 +0200 Subject: [PATCH 078/137] Use tgEpicsService instead of tgResources --- .../related-userstories-controller.coffee | 14 ++++++++------ .../related-userstories.controller.spec.coffee | 18 ++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 4b9be953..0ab2fd1f 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,18 +20,20 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgResources", "tgEpicsService"] + @.$inject = ["tgEpicsService"] - constructor: (@rs, @epicsService) -> + constructor: (@epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false loadRelatedUserstories: () -> - @rs.userstories.listInEpic(@.epic.get('id')).then (data) => - @.userstories = data + @epicsService.listRelatedUserStories(@.epic) + .then (userstories) => + @.userstories = userstories reorderRelatedUserstory: (us, newIndex) -> - @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex).then (userstories) => - @.userstories = userstories + @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex) + .then (userstories) => + @.userstories = userstories module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 2543b085..30611140 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -23,17 +23,9 @@ describe "RelatedUserStories", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - userstories: { - listInEpic: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgEpicsService = () -> mocks.tgEpicsService = { + listRelatedUserStories: sinon.stub() reorderRelatedUserstory: sinon.stub() } @@ -42,7 +34,6 @@ describe "RelatedUserStories", -> _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgEpicsService() return null @@ -66,7 +57,11 @@ describe "RelatedUserStories", -> id: 66 }) - promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .resolve(userstories) + ctrl.loadRelatedUserstories().then () -> expect(ctrl.userstories).is.equal(userstories) done() @@ -95,7 +90,6 @@ describe "RelatedUserStories", -> id: 66 }) - promise = mocks.tgEpicsService.reorderRelatedUserstory .withArgs(ctrl.epic, ctrl.userstories, userstories.get(1), 0) .promise() From c11082d4c192796e420435e9d001e4c9e5db7439 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 08:33:51 +0200 Subject: [PATCH 079/137] Fixing votes css for epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.scss | 4 ++++ app/modules/epics/dashboard/story-row/story-row.scss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index c8d78a16..7c1d7814 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -84,6 +84,10 @@ } .vote { color: $gray; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } } .assigned { img { diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 51aa9fcd..df1e4f7d 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -43,6 +43,10 @@ } .vote { color: $gray; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } } .project, .assigned { From 4fbe44d97ed4bb21844013d9eff140d5a0eeeefb Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 10:10:15 +0200 Subject: [PATCH 080/137] Setting metada on epics dashboard --- app/locales/taiga/locale-en.json | 2 ++ .../epics-dashboard.controller.coffee | 13 ++++++-- .../epics-dashboard.controller.spec.coffee | 33 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 097fcf39..22ce0317 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -407,6 +407,8 @@ "TITLE": "EPICS", "SECTION_NAME": "Epics", "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", "DASHBOARD": { "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 043d8e92..f5e5de26 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -28,17 +28,26 @@ class EpicsDashboardController "lightboxService", "$tgConfirm", "tgProjectService", - "tgEpicsService" + "tgEpicsService", + "tgAppMetaService", + "$translate" ] constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService, - @confirm, @projectService, @epicsService) -> + @confirm, @projectService, @epicsService, @appMetaService, @translate) -> @.sectionName = "EPICS.SECTION_NAME" taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics + title = @translate.instant("EPICS.PAGE_TITLE", {projectName: @.project.get('name')}) + description = @translate.instant("EPICS.PAGE_DESCRIPTION", { + projectName: @.project.get("name"), + projectDescription: @.project.get("description") + }) + @appMetaService.setAll(title, description) + loadInitialData: () -> @epicsService.clear() return @projectService.setProjectBySlug(@params.pslug) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index a18b7c4a..c4025018 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -34,6 +34,10 @@ describe "EpicsDashboard", -> setProjectBySlug: sinon.stub() hasPermission: sinon.stub() isEpicsDashboardEnabled: sinon.stub() + project: Immutable.Map({ + "name": "testing name" + "description": "testing description" + }) } provide.value "tgProjectService", mocks.tgProjectService @@ -73,6 +77,20 @@ describe "EpicsDashboard", -> provide.value "lightboxService", mocks.lightboxService + _mockTgAppMetaService = () -> + mocks.tgAppMetaService = { + setAll: sinon.stub() + } + + provide.value "tgAppMetaService", mocks.tgAppMetaService + + _mockTranslate = () -> + mocks.translate = { + instant: sinon.stub() + } + + provide.value "$translate", mocks.translate + _mocks = () -> module ($provide) -> provide = $provide @@ -83,6 +101,8 @@ describe "EpicsDashboard", -> _mockTgErrorHandlingService() _mockTgLightboxFactory() _mockLightboxService() + _mockTgAppMetaService() + _mockTranslate() return null @@ -94,6 +114,19 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller + it "metada is set", (done) -> + mocks.translate.instant.withArgs("EPICS.PAGE_TITLE", { + projectName: "testing name" + }).returns("TITLE") + mocks.translate.instant.withArgs("EPICS.PAGE_DESCRIPTION", { + projectName: "testing name" + projectDescription: "testing description" + }).returns("DESCRIPTION") + + ctrl = controller("EpicsDashboardCtrl") + expect(mocks.tgAppMetaService.setAll).have.been.calledWith("TITLE", "DESCRIPTION") + done() + it "load data because epics panel is enabled and user has permissions", (done) -> ctrl = controller("EpicsDashboardCtrl") From b527d394645588bdc5d8b03afc5357377397efef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 10:18:19 +0200 Subject: [PATCH 081/137] Fix tg-color-selector in admin panel --- .../modules/admin/project-values.coffee | 12 ++++++++-- .../includes/components/select-color.jade | 24 ++----------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 89797b06..42df12a3 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -31,6 +31,8 @@ joinStr = @.taiga.joinStr groupBy = @.taiga.groupBy bindOnce = @.taiga.bindOnce debounce = @.taiga.debounce +getDefaulColorList = @.taiga.getDefaulColorList + module = angular.module("taigaAdmin") @@ -179,7 +181,9 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra } initializeTextTranslations = -> - $scope.addNewElementText = $translate.instant("ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD") + $scope.addNewElementText = $translate.instant( + "ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD" + ) initializeNewValue() initializeTextTranslations() @@ -320,7 +324,8 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra return {link:link} -module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectValuesDirective]) +module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", + "$translate", "$rootScope", ProjectValuesDirective]) ############################################################################# @@ -331,6 +336,8 @@ ColorSelectionDirective = () -> ## Color selection Link link = ($scope, $el, $attrs, $model) -> + $scope.colorList = getDefaulColorList() + $scope.allowEmpty = false if $attrs.tgAllowEmpty $scope.allowEmpty = true @@ -369,6 +376,7 @@ ColorSelectionDirective = () -> $el.find(".select-color").hide() $el.on "keyup", "input", (event) -> + event.stopPropagation() if event.keyCode == 13 $scope.$apply -> $model.$modelValue.color = $scope.color diff --git a/app/partials/includes/components/select-color.jade b/app/partials/includes/components/select-color.jade index 448455fa..16e3e777 100644 --- a/app/partials/includes/components/select-color.jade +++ b/app/partials/includes/components/select-color.jade @@ -1,27 +1,7 @@ div.popover.select-color ul - li.color(style="background: #fce94f", data-color="#fce94f") - li.color(style="background: #edd400", data-color="#edd400") - li.color(style="background: #c4a000", data-color="#c4a000") - li.color(style="background: #8ae234", data-color="#8ae234") - li.color(style="background: #73d216", data-color="#73d216") - li.color(style="background: #4e9a06", data-color="#4e9a06") - li.color(style="background: #d3d7cf", data-color="#d3d7cf") - li.color(style="background: #fcaf3e", data-color="#fcaf3e") - li.color(style="background: #f57900", data-color="#f57900") - li.color(style="background: #ce5c00", data-color="#ce5c00") - li.color(style="background: #729fcf", data-color="#729fcf") - li.color(style="background: #3465a4", data-color="#3465a4") - li.color(style="background: #204a87", data-color="#204a87") - li.color(style="background: #888a85", data-color="#888a85") - li.color(style="background: #ad7fa8", data-color="#ad7fa8") - li.color(style="background: #75507b", data-color="#75507b") - li.color(style="background: #5c3566", data-color="#5c3566") - li.color(style="background: #ef2929", data-color="#ef2929") - li.color(style="background: #cc0000", data-color="#cc0000") - li.color(style="background: #a40000", data-color="#a40000") - li.color(style="background: #2e3436", data-color="#2e3436", ng-if="!allowEmpty") - li.color(data-color="", ng-class="{'empty-color': allowEmpty}") + li.color(ng-repeat="c in colorList" ng-style="::{background: c}", data-color="{{::c }}") + li.color.empty-color(ng-if="allowEmpty", data-color="") input(type="text", placeholder="personalized colors", ng-model="color") div.selected-color(ng-style="{'background-color': color}", ng-if="color !== null") From fb287301502bef7f25c1ba92a4947999663d445e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 12:50:33 +0200 Subject: [PATCH 082/137] Add more tests --- .../epic-row/epic-row.controller.spec.coffee | 1 - .../epics-dashboard.controller.spec.coffee | 1 - .../story-row/story-row.controller.coffee | 5 +- .../story-row.controller.spec.coffee | 71 +++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee index 6e287471..f3df7c64 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -74,7 +74,6 @@ describe "EpicRow", -> }) } - ctrl._calculateProgressBar() expect(ctrl.percentage).to.be.equal("50%") it "calculate progress bar in zero US", () -> diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index c4025018..419396b5 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -18,7 +18,6 @@ ### describe "EpicsDashboard", -> - EpicsDashboardCtrl = null provide = null controller = null mocks = {} 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 db82f81b..ce959248 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -31,6 +31,9 @@ class StoryRowController else totalTasks = @.story.get('tasks').size totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size - @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" + if totalTasks == 0 + @.percentage = "0%" + else + @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee new file mode 100644 index 00000000..9f6fe6b5 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee @@ -0,0 +1,71 @@ +### +# Copyright (C) 2014-2015 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: story-row.controller.spec.coffee +### + +describe "StoryRowCtrl", -> + controller = null + + beforeEach -> + module "taigaEpics" + + inject ($controller) -> + controller = $controller + + it "calculate percentage for some closed tasks", () -> + data = { + story: Immutable.fromJS( + tasks: [ + {is_closed: true}, + {is_closed: true}, + {is_closed: true}, + {is_closed: false}, + {is_closed: false}, + ] + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("60%") + + it "calculate percentage for closed story", () -> + data = { + story: Immutable.fromJS( + tasks: [ + {is_closed: true}, + {is_closed: true}, + {is_closed: true}, + {is_closed: false}, + {is_closed: false}, + ] + is_closed: true + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("100%") + + it "calculate percentage for closed story", () -> + data = { + story: Immutable.fromJS( + tasks: [] + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("0%") + From fa485e4d77fe2b324abfd1fb28a90a1697aec141 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 12:41:06 +0200 Subject: [PATCH 083/137] Adding tests for epics service --- .../attachments-full.service.coffee | 2 +- app/modules/epics/epics.service.coffee | 15 +- app/modules/epics/epics.service.spec.coffee | 233 ++++++++++++++++++ 3 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 app/modules/epics/epics.service.spec.coffee diff --git a/app/modules/components/attachments-full/attachments-full.service.coffee b/app/modules/components/attachments-full/attachments-full.service.coffee index 9e1fd874..2635f7c4 100644 --- a/app/modules/components/attachments-full/attachments-full.service.coffee +++ b/app/modules/components/attachments-full/attachments-full.service.coffee @@ -109,7 +109,7 @@ class AttachmentsFullService extends taiga.Service patch = {order: attachment.getIn(['file', 'order'])} promises.push @attachmentsService.patch(attachment.getIn(['file', 'id']), type, patch) - + return Promise.all(promises).then () => @._attachments = attachments diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index e5129024..4095bbf1 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -24,11 +24,10 @@ class EpicsService 'tgProjectService', 'tgAttachmentsService' 'tgResources', - 'tgXhrErrorService', - '$q' + 'tgXhrErrorService' ] - constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) -> + constructor: (@projectService, @attachmentsService, @resources, @xhrError) -> @._epics = Immutable.List() taiga.defineImmutableProperty @, 'epics', () => return @._epics @@ -52,14 +51,15 @@ class EpicsService .then (epic) => promises = _.map attachments.toJS(), (attachment) => @attachmentsService.upload(attachment.file, epic.get('id'), epic.get('project'), 'epic') - @q.all(promises).then () => + + Promise.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() + previous = beforeDestination.last() newOrder = if !previous then 0 else previous.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => @@ -72,7 +72,6 @@ class EpicsService epics_order: newOrder, version: epic.get('version') } - return @resources.epics.reorder(epic.get('id'), data, setOrders) .then () => @.fetchEpics() @@ -86,9 +85,9 @@ class EpicsService previousWithTheSameOrder = beforeDestination.filter (it) => it.get('epic_order') == previous.get('epic_order') - - setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + setOrders = _.fromPairs previousWithTheSameOrder.map((it) => [it.get('id'), it.get('epic_order')] + ).toJS() data = { order: newOrder diff --git a/app/modules/epics/epics.service.spec.coffee b/app/modules/epics/epics.service.spec.coffee new file mode 100644 index 00000000..58efa075 --- /dev/null +++ b/app/modules/epics/epics.service.spec.coffee @@ -0,0 +1,233 @@ +### +# 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.spec.coffee +### + +describe "tgEpicsService", -> + epicsService = provide = null + mocks = {} + + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: Immutable.Map({ + "id": 1 + }) + } + + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgAttachmentsService = () -> + mocks.tgAttachmentsService = { + upload: sinon.stub() + } + + provide.value "tgAttachmentsService", mocks.tgAttachmentsService + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + list: sinon.stub() + post: sinon.stub() + patch: sinon.stub() + reorder: sinon.stub() + reorderRelatedUserstory: sinon.stub() + } + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgXhrErrorService = () -> + mocks.tgXhrErrorService = { + response: sinon.stub() + } + + provide.value "tgXhrErrorService", mocks.tgXhrErrorService + + _inject = (callback) -> + inject (_tgEpicsService_) -> + epicsService = _tgEpicsService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProjectService() + _mockTgAttachmentsService() + _mockTgResources() + _mockTgXhrErrorService() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaEpics" + _setup() + _inject() + + it "clear epics", () -> + epicsService._epics = Immutable.List(Immutable.Map({ + 'id': 1 + })) + + epicsService.clear() + expect(epicsService._epics.size).to.be.equal(0) + + it "fetch epics success", () -> + epics = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + promise = mocks.tgResources.epics.list.withArgs(1).promise().resolve(epics) + epicsService.fetchEpics().then () -> + expect(epicsService.epics).to.be.equal(epics) + + it "fetch epics error", () -> + epics = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + promise = mocks.tgResources.epics.list.withArgs(1).promise().reject(new Error("error")) + epicsService.fetchEpics().then () -> + expect(mocks.tgXhrErrorService.response.withArgs(new Error("error"))).have.been.calledOnce + + it "list related userstories", () -> + epic = Immutable.fromJS({ + id: 1 + }) + epicsService.listRelatedUserStories(epic) + expect(mocks.tgResources.userstories.listInEpic.withArgs(epic.get('id'))).have.been.calledOnce + + it "createEpic", () -> + epicData = {} + epic = Immutable.fromJS({ + id: 111 + project: 1 + }) + attachments = Immutable.fromJS([ + {file: "f1"}, + {file: "f2"} + ]) + + mocks.tgResources.epics + .post + .withArgs({project: 1}) + .promise() + .resolve(epic) + + mocks.tgAttachmentsService + .upload + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.createEpic(epicData, attachments).then () -> + expect(mocks.tgAttachmentsService.upload.withArgs("f1", 111, 1, "epic")).have.been.calledOnce + expect(mocks.tgAttachmentsService.upload.withArgs("f2", 111, 1, "epic")).have.been.calledOnce + expect(epicsService.fetchEpics).have.been.calledOnce + + it "Update epic status", () -> + epic = Immutable.fromJS({ + id: 1 + version: 1 + }) + + mocks.tgResources.epics + .patch + .withArgs(1, {status: 33, version: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.updateEpicStatus(epic, 33).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "Update epic assigned to", () -> + epic = Immutable.fromJS({ + id: 1 + version: 1 + }) + + mocks.tgResources.epics + .patch + .withArgs(1, {assigned_to: 33, version: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.updateEpicAssignedTo(epic, 33).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "reorder epic", () -> + epicsService._epics = Immutable.fromJS([ + { + id: 1 + epics_order: 1 + version: 1 + }, + { + id: 2 + epics_order: 2 + version: 1 + }, + { + id: 3 + epics_order: 3 + version: 1 + }, + ]) + + mocks.tgResources.epics.reorder + .withArgs(3, {epics_order: 2, version: 1}, {1: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.reorderEpic(epicsService._epics.get(2), 1).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "reorder related userstory in epic", () -> + epic = Immutable.fromJS({ + id: 1 + }) + + epicUserstories = Immutable.fromJS([ + { + id: 1 + epic_order: 1 + }, + { + id: 2 + epic_order: 2 + }, + { + id: 3 + epic_order: 3 + }, + ]) + + mocks.tgResources.epics.reorderRelatedUserstory + .withArgs(1, 3, {order: 2}, {1: 1}) + .promise() + .resolve() + + epicsService.listRelatedUserStories = sinon.stub() + epicsService.reorderRelatedUserstory(epic, epicUserstories, epicUserstories.get(2), 1).then () -> + expect(epicsService.listRelatedUserStories.withArgs(epic)).have.been.calledOnce From e8a215517787d7dd0d427b1c95baf0ebe8a0e84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 12 Sep 2016 12:48:19 +0200 Subject: [PATCH 084/137] Fix Epics header styles --- .../components/color-selector/color-selector.scss | 1 - app/partials/epic/epic-detail.jade | 5 ++--- app/styles/modules/epics/epic-detail.scss | 10 ++++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 app/styles/modules/epics/epic-detail.scss diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 2f06ccb0..6bf35ad9 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -15,7 +15,6 @@ .tag-color { @include color-selector-option; border: 1px solid $gray-light; - border-left: 0; border-radius: 0; margin: 0; transition: background .3s ease-out; diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index edee4440..977bdf35 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -18,8 +18,8 @@ div.wrapper( on-downvote="ctrl.onDownvote" ) - .detail-header-container - tg-color-selector( + .detail-header-container.epic-header-container(ng-class="{blocked: epic.is_blocked}") + tg-color-selector.color-selector( is-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" @@ -28,7 +28,6 @@ div.wrapper( item="epic" project="project" required-perm="modify_epic" - ng-class="{blocked: epic.is_blocked}" ng-if="project && epic" format="text" ) diff --git a/app/styles/modules/epics/epic-detail.scss b/app/styles/modules/epics/epic-detail.scss new file mode 100644 index 00000000..fe7a80b5 --- /dev/null +++ b/app/styles/modules/epics/epic-detail.scss @@ -0,0 +1,10 @@ +.epic-header-container { + display: flex; + .color-selector { + margin-right: .5rem; + } + tg-detail-header { + flex: 1; + width: 100%; + } +} From 19d0a5d0ac0ec8b15f011c0388b43c95b6ef5dda Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 14:35:31 +0200 Subject: [PATCH 085/137] Epics in timeline --- app/locales/taiga/locale-en.json | 10 ++- app/modules/history/history/history-diff.jade | 7 +- .../history-templates/history-color.jade | 9 +++ .../profile/profile-favs/items/ticket.jade | 10 +++ .../profile-favs.controller.coffee | 9 +++ .../profile-favs.controller.spec.coffee | 76 +++++++++++++++++-- .../profile/profile-favs/profile-favs.jade | 13 ++++ .../user-timeline-item-title.service.coffee | 20 ++++- .../user-timeline-item-type.service.coffee | 37 +++++++++ .../user-timeline.service.coffee | 3 +- 10 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 app/modules/history/history/history-templates/history-color.jade diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 22ce0317..6f7a4823 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -847,6 +847,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projects", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Tasks", @@ -1202,7 +1204,8 @@ "SPRINT_ORDER": "sprint order", "KANBAN_ORDER": "kanban order", "TASKBOARD_ORDER": "taskboard order", - "US_ORDER": "us order" + "US_ORDER": "us order", + "COLOR": "color" } }, "BACKLOG": { @@ -1576,6 +1579,8 @@ "TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} created the project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1588,9 +1593,12 @@ "TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}", "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} has a new member", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}", diff --git a/app/modules/history/history/history-diff.jade b/app/modules/history/history/history-diff.jade index aa6d53c8..58ea6d6a 100644 --- a/app/modules/history/history/history-diff.jade +++ b/app/modules/history/history/history-diff.jade @@ -43,6 +43,11 @@ ) include history-templates/history-custom-attributes +.diff-wrapper( + ng-if="vm.type == 'color'" +) + include history-templates/history-color + .diff-wrapper( ng-if="vm.type == 'team_requirement'" ) @@ -57,5 +62,3 @@ ng-if="vm.type == 'is_blocked'" ) include history-templates/blocked - - diff --git a/app/modules/history/history/history-templates/history-color.jade b/app/modules/history/history/history-templates/history-color.jade new file mode 100644 index 00000000..d97314bb --- /dev/null +++ b/app/modules/history/history/history-templates/history-color.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.FIELDS.COLOR" + ) + span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade index b5539492..60bee680 100644 --- a/app/modules/profile/profile-favs/items/ticket.jade +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -24,6 +24,10 @@ p span.ticket-project | {{:: vm.item.get('project_name') }} + span.ticket-type( + ng-if="::vm.item.get('type') === 'epic'" + translate="COMMON.EPIC" + ) span.ticket-type( ng-if="::vm.item.get('type') === 'userstory'" translate="COMMON.USER_STORY" @@ -44,6 +48,12 @@ ) h2 span.ticket-id(tg-bo-ref="vm.item.get('ref')") + a.ticket-title( + href="#" + ng-if="::vm.item.get('type') === 'epic'" + tg-nav="project-epics-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')" + title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}" + ) {{ ::vm.item.get('subject') }} a.ticket-title( href="#" ng-if="::vm.item.get('type') === 'userstory'" diff --git a/app/modules/profile/profile-favs/profile-favs.controller.coffee b/app/modules/profile/profile-favs/profile-favs.controller.coffee index f47f7823..2fc3ac7c 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.coffee @@ -28,6 +28,7 @@ class FavsBaseController _init: -> @.enableFilterByAll = true @.enableFilterByProjects = true + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true @@ -101,6 +102,12 @@ class FavsBaseController @._resetList() @.loadItems() + showEpicsOnly: -> + if @.type isnt "epic" + @.type = "epic" + @._resetList() + @.loadItems() + showUserStoriesOnly: -> if @.type isnt "userstory" @.type = "userstory" @@ -134,6 +141,7 @@ class ProfileLikedController extends FavsBaseController @.tabName = 'likes' @.enableFilterByAll = false @.enableFilterByProjects = false + @.enableFilterByEpics = false @.enableFilterByUserStories = false @.enableFilterByTasks = false @.enableFilterByIssues = false @@ -158,6 +166,7 @@ class ProfileVotedController extends FavsBaseController @.tabName = 'upvotes' @.enableFilterByAll = true @.enableFilterByProjects = false + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true diff --git a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee index 89ccd289..a761fe64 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee @@ -11,7 +11,7 @@ # 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 +# You showld have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # File: profile-favs.controller.spec.coffee @@ -127,7 +127,7 @@ describe "ProfileLiked", -> expect(ctrl.q).to.be.equal(textQuery) done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -154,7 +154,7 @@ describe "ProfileLiked", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -282,6 +282,37 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.equal(textQuery) done() + it "show only items of epics", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + type = "epic" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items) + + expect(ctrl.items.size).to.be.equal(0) + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.showEpicsOnly().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.type + expect(ctrl.q).to.be.null + done() + it "show only items of user stories", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -375,7 +406,7 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.null done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -402,7 +433,7 @@ describe "ProfileVoted", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -560,6 +591,37 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null done() + it "show only items of epics", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + type = "epic" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items) + + expect(ctrl.items.size).to.be.equal(0) + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.showEpicsOnly().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.type + expect(ctrl.q).to.be.null + done() + it "show only items of user stories", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -653,7 +715,7 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -680,7 +742,7 @@ describe "ProfileWatched", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) diff --git a/app/modules/profile/profile-favs/profile-favs.jade b/app/modules/profile/profile-favs/profile-favs.jade index 0317836b..7f4d1d59 100644 --- a/app/modules/profile/profile-favs/profile-favs.jade +++ b/app/modules/profile/profile-favs/profile-favs.jade @@ -26,6 +26,14 @@ section.profile-favs title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}" translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}" ) + a( + href="" + ng-if="::vm.enableFilterByEpics" + ng-click="vm.showEpicsOnly()" + ng-class="{active: vm.type === 'epic'}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS'|translate }}" + ) a( href="" ng-if="::vm.enableFilterByUserStories" @@ -64,6 +72,11 @@ section.profile-favs tg-fav-item="item" item-type="project" ) + div( + ng-switch-when="epic" + tg-fav-item="item" + item-type="epic" + ) div( ng-switch-when="userstory" tg-fav-item="item" diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee index b19769ed..84a189c3 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee @@ -35,7 +35,8 @@ class UserTimelineItemTitle 'priority': 'ISSUES.FIELDS.PRIORITY', 'type': 'ISSUES.FIELDS.TYPE', 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE', - 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED' + 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED', + 'color': 'COMMON.FIELDS.COLOR' } _params: { @@ -89,6 +90,18 @@ class UserTimelineItemTitle return @._getLink(url, text) + related_us_name: (timeline, event) -> + obj = timeline.getIn(["data", "userstory"]) + url = "project-userstories-detail:project=timeline.getIn(['data', 'userstory', 'project', 'slug']),ref=timeline.getIn(['data', 'userstory', 'ref'])" + text = '#' + obj.get('ref') + ' ' + obj.get('subject') + return @._getLink(url, text) + + epic_name: (timeline, event) -> + obj = timeline.getIn(["data", "epic"]) + url = "project-epics-detail:project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['data', 'epic', 'ref'])" + text = '#' + obj.get('ref') + ' ' + obj.get('subject') + return @._getLink(url, text) + obj_name: (timeline, event) -> obj = @._getTimelineObj(timeline, event) url = @._getDetailObjUrl(event) @@ -122,9 +135,9 @@ class UserTimelineItemTitle "task": ["project-tasks-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "parent_userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'userstory', 'ref'])"], - "milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"] + "milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"], + "epic": ["project-epics-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"] } - return url[event.obj][0] + url[event.obj][1] _getLink: (url, text, title) -> @@ -153,7 +166,6 @@ class UserTimelineItemTitle timeline_type.translate_params.forEach (param) => params[param] = @._translateTitleParams(param, timeline, event) - return params getTitle: (timeline, event, type) -> diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee index 5306ac5f..b13d6de4 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee @@ -82,6 +82,18 @@ timelineType = (timeline, event) -> key: 'TIMELINE.MILESTONE_CREATED', translate_params: ['username', 'project_name', 'obj_name'] }, + { # NewEpic + check: (timeline, event) -> + return event.obj == 'epic' && event.type == 'create' + key: 'TIMELINE.EPIC_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewEpicRelatedUserstory + check: (timeline, event) -> + return event.obj == 'relateduserstory' && event.type == 'create' + key: 'TIMELINE.EPIC_RELATED_USERSTORY_CREATED', + translate_params: ['username', 'project_name', 'related_us_name', 'epic_name'] + }, { # NewUsComment check: (timeline, event) -> return timeline.getIn(['data', 'comment']) && event.obj == 'userstory' @@ -109,6 +121,15 @@ timelineType = (timeline, event) -> text = timeline.getIn(['data', 'comment_html']) return $($.parseHTML(text)).text() }, + { # NewEpicComment + check: (timeline, event) -> + return timeline.getIn(['data', 'comment']) && event.obj == 'epic' + key: 'TIMELINE.NEW_COMMENT_EPIC' + translate_params: ['username', 'obj_name'], + description: (timeline) -> + text = timeline.getIn(['data', 'comment_html']) + return $($.parseHTML(text)).text() + }, { # UsMove check: (timeline, event) -> return timeline.hasIn(['data', 'value_diff']) && @@ -258,6 +279,22 @@ timelineType = (timeline, event) -> key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE', translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value'] }, + { # EpicUpdated description + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' && + timeline.hasIn(['data', 'value_diff']) && + timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff' + key: 'TIMELINE.EPIC_UPDATED', + translate_params: ['username', 'field_name', 'obj_name'] + }, + { # EpicUpdated general + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' + key: 'TIMELINE.EPIC_UPDATED_WITH_NEW_VALUE', + translate_params: ['username', 'field_name', 'obj_name', 'new_value'] + }, { # New User check: (timeline, event) -> return event.obj == 'user' && event.type == 'create' diff --git a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee index 61adc2a4..aecf5724 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -47,7 +47,8 @@ class UserTimelineService extends taiga.Service # customs 'blocked', 'moveInBacklog', - 'milestone' + 'milestone', + 'color' ] _invalid: [ From 8e3e49c1a7baa18889fc9f40800c23e8b22a19d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 12 Sep 2016 14:52:55 +0200 Subject: [PATCH 086/137] Color selector component has a full color range if color is required --- app/coffee/utils.coffee | 2 +- .../color-selector/color-selector.controller.coffee | 9 +++++++-- .../color-selector/color-selector.controller.spec.coffee | 6 ++++++ .../color-selector/color-selector.directive.coffee | 2 +- .../components/color-selector/color-selector.jade | 2 +- app/modules/epics/create-epic/create-epic.jade | 2 +- app/partials/epic/epic-detail.jade | 2 +- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index b4580029..a2d02c99 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -242,7 +242,7 @@ patch = (oldImmutable, newImmutable) -> DEFAULT_COLOR_LIST = [ '#fce94f', '#edd400', '#c4a000', '#8ae234', '#73d216', '#4e9a06', '#d3d7cf', '#fcaf3e', '#f57900', '#ce5c00', '#729fcf', '#3465a4', '#204a87', '#888a85', - '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000' + '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000', '#222222' ] getRandomDefaultColor = () -> diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index c30d9729..a52ed21b 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -24,13 +24,18 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController constructor: () -> @.colorList = getDefaulColorList() + @.checkIsColorRequired() @.displayColorList = false + checkIsColorRequired: () -> + if !@.isColorRequired + @.colorList = _.dropRight(@.colorList); + setColor: (color) -> @.color = @.initColor resetColor: () -> - if @.isRequired and not @.color + if @.isColorRequired and not @.color @.color = @.initColor toggleColorList: () -> @@ -45,7 +50,7 @@ class ColorSelectorController onKeyDown: (event) -> if event.which == 13 # ENTER event.stopPropagation() - if @.color or not @.isRequired + if @.color or not @.isColorRequired @.onSelectDropdownColor(@.color) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index 09597fee..f12b85cd 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,6 +36,12 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller + it.only "require Color on Selector", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" + colorSelectorCtrl.colorList = ["#000000", "#123123"] + colorSelectorCtrl.isColorRequired = false + colorSelectorCtrl.checkIsColorRequired() + expect(colorSelectorCtrl.colorList).to.be.eql(["#000000"]) it "display Color Selector", () -> colorSelectorCtrl = controller "ColorSelectorCtrl" diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index fb091711..fc9df40a 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -53,7 +53,7 @@ ColorSelectorDirective = ($timeout) -> controller: "ColorSelectorCtrl", controllerAs: "vm", bindToController: { - isRequired: "=", + isColorRequired: "=", onSelectColor: "&", initColor: "=" }, diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 80025485..7a472f02 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -13,7 +13,7 @@ ng-click="vm.onSelectDropdownColor(color)" ) li.empty-color( - ng-if="!vm.isRequired" + ng-if="!vm.isColorRequired" ng-click="vm.onSelectDropdownColor(null)" ) .custom-color-selector diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 504c7216..e93a63fe 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -9,7 +9,7 @@ tg-lightbox-close .color-selector fieldset tg-color-selector( - is-required="true" + is-color-required="true" init-color="vm.newEpic.color" on-select-color="vm.selectColor(color)" ) diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index 977bdf35..dcaa775e 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -20,7 +20,7 @@ div.wrapper( .detail-header-container.epic-header-container(ng-class="{blocked: epic.is_blocked}") tg-color-selector.color-selector( - is-required="true" + is-color-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" ) From 182dd4720e027f0d3e82cb64168c9fbf55a2307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 12:58:59 +0200 Subject: [PATCH 087/137] Layout refactor for epics lightbox --- app/locales/taiga/locale-en.json | 5 +- .../related-userstories-create.jade | 79 ++++++------ .../related-userstories-create.scss | 117 +++++++++--------- 3 files changed, 100 insertions(+), 101 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 6f7a4823..e90ba578 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1075,11 +1075,10 @@ "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", - "CREATE_RELATED_USERSTORIES": "Create a relationship with a user story", - "RELATED_WITH": "Related with", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", "NEW_USERSTORY": "New user story", "EXISTING_USERSTORY": "Existing user story", - "CHOOSE_PROJECT_FOR_CREATION": "Whats' the project?", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", "SUBJECT": "Subject", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 468acbbb..9f549da1 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -4,17 +4,14 @@ a.add-button.e2e-add-userstory-button( ) tg-svg(svg-icon="icon-add") -div.lightbox.lightbox-create-related-user-stories +.lightbox.lightbox-create-related-user-stories tg-lightbox-close - div.form + .lightbox-create-related-user-stories-wrapper h2.title(translate="EPIC.CREATE_RELATED_USERSTORIES") - .related-with-selector-title - legend(translate="EPIC.RELATED_WITH") - .related-with-selector - fieldset + .related-with-selector-single input( type="radio" name="related-with-selector" @@ -26,7 +23,7 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-new-userstory-label(for="new-user-story") span.name {{ 'EPIC.NEW_USERSTORY' | translate}} - fieldset + .related-with-selector-single input( type="radio" name="related-with-selector" @@ -37,40 +34,39 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-existing-user-story-label(for="existing-user-story") span.name {{ 'EPIC.EXISTING_USERSTORY' | translate}} - .project-selector-title - legend( + fieldset.project-selector + label( ng-if="relatedWithSelector=='new-user-story'" translate="EPIC.CHOOSE_PROJECT_FOR_CREATION" + for="project-selector-dropdown" ) - - legend( + label( ng-if="relatedWithSelector=='existing-user-story'" translate="EPIC.CHOOSE_PROJECT_FROM" + for="project-selector-dropdown" ) - - .project-selector() select( ng-model="selectedProject" ng-change="selectProject(selectedProject)" data-required="true" required ng-options="p.id as p.name for p in vm.projects | toMutable" + id="project-selector-dropdown" ) - div(ng-show="relatedWithSelector=='new-user-story'") - .new-user-story-selector - .new-user-story-title - legend( - ng-show="creationMode=='single-new-user-story'" - translate="EPIC.SUBJECT" - ) + fieldset(ng-show="relatedWithSelector=='new-user-story'") + .new-user-story-title + label( + ng-show="creationMode=='single-new-user-story'" + translate="EPIC.SUBJECT" + ) - legend( - ng-show="creationMode=='bulk-new-user-stories'" - translate="EPIC.SUBJECT_BULK_MODE" - ) + label( + ng-show="creationMode=='bulk-new-user-stories'" + translate="EPIC.SUBJECT_BULK_MODE" + ) .new-user-story-options - fieldset + .new-user-story-option-single input( type="radio" name="new-user-story-selector" @@ -82,7 +78,7 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-single-creation-label(for="single-new-user-story") tg-svg(svg-icon="icon-add") - fieldset + .new-user-story-option-single input( type="radio" name="new-user-story-selector" @@ -92,35 +88,37 @@ div.lightbox.lightbox-create-related-user-stories ) label.e2e-bulk-creation-label(for="bulk-new-user-stories") tg-svg(svg-icon="icon-bulk") + form.new-user-story-form .single-creation(ng-show="creationMode=='single-new-user-story'") input.e2e-new-userstory-input-text( type="text" ng-model="relatedUserstoriesText" - data-required="true" + required ) .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") textarea.e2e-new-userstories-input-textarea( ng-model="relatedUserstoriesText" - data-required="true" + required ) - a.button-green.e2e-create-userstory-button( + button.button-green.create-user-story.e2e-create-userstory-button.ng-animate-disabled( href="" ng-click="vm.bulkCreateRelatedUserStories(selectedProject, relatedUserstoriesText, closeLightbox)" tg-loading="vm.loading" + translate="COMMON.SAVE" + ng-show="relatedWithSelector=='new-user-story'" ) - span( - translate="COMMON.SAVE" - ) - .existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") - .existing-user-story-title - legend(translate="EPIC.CHOOSE_USERSTORY") - - input.userstory.e2e-filter-userstories-input( + fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + label( + translate="EPIC.CHOOSE_USERSTORY" + for="userstory-filter" + ) + input.userstory-filter.e2e-filter-userstories-input( + id="userstory-filter" type="text" placeholder="{{'EPIC.FILTER_USERSTORIES' | translate}}" ng-model="searchUserstory" @@ -132,7 +130,6 @@ div.lightbox.lightbox-create-related-user-stories size="5" ng-model="selectedUserstory" required - data-required="true" ) - var hash = "#"; option.hidden( @@ -143,11 +140,9 @@ div.lightbox.lightbox-create-related-user-stories value="{{ ::us.id }}" ) #{hash}{{::us.ref}} {{::us.subject}} - a.button-green.e2e-select-related-userstory-button( + button.button-green.e2e-select-related-userstory-button( href="" ng-click="vm.saveRelatedUserStory(selectedUserstory, closeLightbox)" tg-loading="vm.loading" + translate="COMMON.SAVE" ) - span( - translate="COMMON.SAVE" - ) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss index 82412585..1e397dbf 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss @@ -1,78 +1,83 @@ .lightbox-create-related-user-stories { - .related-with-selector-title, - .project-selector-title, - .new-user-story-title, - .existing-user-story-title { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; + .lightbox-create-related-user-stories-wrapper { + max-width: 600px; + width: 90%; } - .related-with-selector, - .new-user-story-selector { + .related-with-selector { display: flex; + margin-bottom: 1rem; input { display: none; + &:checked+label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + } + &:checked+label:hover { + background: $primary-light; + } + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: 2rem 1rem; + text-align: center; + text-transform: uppercase; + transition: background .2s ease-in; + } + +label:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } } - fieldset { + .related-with-selector-single { + flex: 1; &:first-child { margin-right: .5rem; } } } - .project-selector, - .single-creation { + fieldset { + label { + display: inline-block; + margin-bottom: .5rem; + } + } + .new-user-story-title { + align-items: flex-end; + display: flex; + } + .existing-user-story-form, + .new-user-story-form { margin-bottom: 1rem; } - input { - &:checked+label { - background: $primary-light; - color: $white; - transition: background .2s ease-in; - &:hover { + .new-user-story-options { + display: flex; + margin-left: auto; + input { + display: none; + &:checked+label { background: $primary-light; - } - } - +label { - background: rgba($whitish, .7); - cursor: pointer; - display: block; - padding: 2rem 1rem; - text-align: center; - transition: background .2s ease-in; - &:hover { - background: rgba($primary-light, .3); + color: $white; + fill: $white; transition: background .2s ease-in; } - .icon { - fill: currentColor; - margin-top: .25rem; - vertical-align: text-top; + +label { + background: $mass-white; + color: $grayer; + cursor: pointer; + display: block; + padding: .5rem; + transition: background .2s ease-in; } - .name { - @include font-size(large); - text-transform: uppercase; + +label:hover { + background: $primary-light; + color: $white; + fill: $white; } } } - .new-user-story-selector { - display: flex; - justify-content: space-between; - .new-user-story-options { - display: flex; - } - fieldset { - width: auto; - } - label { - height: 1.5rem; - padding: 0; - width: 1.5rem; - } - } - - .existing-user-story { - .button-green { - margin-top: 1rem; - } + button { + width: 100%; } } From be715cc9e4b7baa32b329c39d4cfe2caf594b2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 13:06:28 +0200 Subject: [PATCH 088/137] Fix pointer in story row --- app/modules/epics/dashboard/story-row/story-row.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index df1e4f7d..9ecc112c 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -6,7 +6,6 @@ align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; display: flex; margin-left: 4rem; transition: background .2s; @@ -24,6 +23,9 @@ } .name { flex-basis: 17.5vw; + a { + cursor: pointer; + } } .progress-bar, .progress-status { @@ -48,6 +50,9 @@ fill: $primary-light; } } + .project { + cursor: pointer; + } .project, .assigned { img { From f70296ae43a274f6f92b1a190ccdb2d85ab76662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 14:50:06 +0200 Subject: [PATCH 089/137] Fix style behaviour on related user stories --- .../related-userstory-row/related-userstory-row.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss index 64b82340..ad3cabc3 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -2,6 +2,7 @@ tg-related-userstory-row { @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; + cursor: move; display: flex; padding: .5rem 0 .5rem .5rem; &:hover { @@ -31,12 +32,12 @@ tg-related-userstory-row { width: 150px; img { flex-basis: 35px; - // width & height they are only required for IE height: 35px; width: 35px; } } .project { + cursor: pointer; flex-basis: 100px; img { width: 40px; @@ -46,9 +47,12 @@ tg-related-userstory-row { display: flex; flex: 1; margin-right: 1rem; - + a { + cursor: pointer; + } span { - margin-right: .25rem; + display: inline-block; + margin-left: .25rem; } } .closed { From 61e42eb4fa45d04c0b9850e0ad6e2a5a4931cd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 08:39:06 +0200 Subject: [PATCH 090/137] Color templates in history --- .../history/history-templates/history-color.jade | 14 +++++++++++--- .../history-templates/history-templates.scss | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/modules/history/history/history-templates/history-color.jade b/app/modules/history/history/history-templates/history-color.jade index d97314bb..89b69d87 100644 --- a/app/modules/history/history/history-templates/history-color.jade +++ b/app/modules/history/history/history-templates/history-color.jade @@ -1,9 +1,17 @@ -.diff-status-wrapper +.diff-color-wrapper span.key( translate="ACTIVITY.FIELDS.COLOR" ) - span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + span.diff( + ng-if="vm.diff[0]" + ng-style="{background: vm.diff[0]}" + title="{{vm.diff[0]}}" + ) tg-svg( svg-icon="icon-arrow-right" ) - span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} + span.diff( + ng-if="vm.diff[1]" + ng-style="{background: vm.diff[1]}" + title="{{vm.diff[1]}}" + ) diff --git a/app/modules/history/history/history-templates/history-templates.scss b/app/modules/history/history/history-templates/history-templates.scss index 77d5d7ed..504bd69a 100644 --- a/app/modules/history/history/history-templates/history-templates.scss +++ b/app/modules/history/history/history-templates/history-templates.scss @@ -25,4 +25,13 @@ background: rgba($red-light, .3); } } + .diff-color-wrapper { + align-items: center; + display: flex; + .diff { + display: inline-block; + height: 1.2rem; + width: 1.2rem; + } + } } From 10533a933dfe604d11835f8e7aeec9c2262d4b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 09:36:36 +0200 Subject: [PATCH 091/137] Add color to timeline --- app/locales/taiga/locale-en.json | 1 + .../user-timeline-item-type.service.coffee | 9 +++++++++ .../user-timeline-item/user-timeline-item.jade | 18 +++++++++--------- .../user-timeline/user-timeline.jade | 15 ++++++++++++--- .../user-timeline/user-timeline.scss | 9 +++++++++ 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index e90ba578..86bb1688 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1594,6 +1594,7 @@ "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}", diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee index b13d6de4..9e33dcd0 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-type.service.coffee @@ -288,6 +288,15 @@ timelineType = (timeline, event) -> key: 'TIMELINE.EPIC_UPDATED', translate_params: ['username', 'field_name', 'obj_name'] }, + { # EpicUpdated color + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' && + timeline.hasIn(['data', 'value_diff']) && + timeline.getIn(['data', 'value_diff', 'key']) == 'color' + key: 'TIMELINE.EPIC_UPDATED_WITH_NEW_COLOR', + translate_params: ['username', 'field_name', 'obj_name', 'new_value'] + }, { # EpicUpdated general check: (timeline, event) -> return event.obj == 'epic' && diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade index 43604994..a85bcf0b 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item.jade @@ -1,22 +1,22 @@ -div.activity-item +.activity-item span.activity-date {{::timeline.get('created') | momentFromNow}} - div.activity-info(tg-user-timeline-title="timeline") + .activity-info(tg-user-timeline-title="timeline") - div.activity-info + .activity-info // profile image with url - div.profile-contact-picture(ng-if="timeline.getIn(['data', 'user', 'is_profile_visible'])") + .profile-contact-picture(ng-if="timeline.getIn(['data', 'user', 'is_profile_visible'])") a(tg-nav="user-profile:username=timeline.getIn(['data', 'user', 'username'])", title="{{::timeline.getIn(['data', 'user', 'name']) }}") img( tg-avatar="timeline.getIn(['data', 'user'])" alt="{{::timeline.getIn(['data', 'user', 'name'])}}" ) // profile image without url - div.profile-contact-picture(ng-if="!timeline.getIn(['data', 'user', 'is_profile_visible'])") - img( - tg-avatar="timeline.getIn(['data', 'user'])" - alt="{{::timeline.getIn(['data', 'user', 'name'])}}" - ) + .profile-contact-picture(ng-if="!timeline.getIn(['data', 'user', 'is_profile_visible'])") + img( + tg-avatar="timeline.getIn(['data', 'user'])" + alt="{{::timeline.getIn(['data', 'user', 'name'])}}" + ) p(tg-compile-html="timeline.get('title_html')") diff --git a/app/modules/user-timeline/user-timeline/user-timeline.jade b/app/modules/user-timeline/user-timeline/user-timeline.jade index b227d860..8c8bdcc1 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.jade +++ b/app/modules/user-timeline/user-timeline/user-timeline.jade @@ -1,7 +1,16 @@ section.profile-timeline div(ng-if="!vm.timelineList.size") div.spin - img(src="/#{v}/svg/spinner-circle.svg", alt="Loading...") + img( + src="/#{v}/svg/spinner-circle.svg" + alt="Loading..." + ) - div(infinite-scroll="vm.loadTimeline()", infinite-scroll-disabled="vm.scrollDisabled") - div(tg-repeat="timeline in vm.timelineList", tg-user-timeline-item="timeline") + div( + infinite-scroll="vm.loadTimeline()" + infinite-scroll-disabled="vm.scrollDisabled" + ) + div( + tg-repeat="timeline in vm.timelineList" + tg-user-timeline-item="timeline" + ) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.scss b/app/modules/user-timeline/user-timeline/user-timeline.scss index 54d03b71..bcb89710 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.scss +++ b/app/modules/user-timeline/user-timeline/user-timeline.scss @@ -56,6 +56,15 @@ width: 100%; } } + .new-color { + border-radius: 50%; + display: inline-block; + height: 1rem; + margin-left: .2rem; + position: relative; + top: .1rem; + width: 1rem; + } } .activity-member-view { display: flex; From d16ece1e8079d29c43ee6bccb51335fd750091e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 15:19:07 +0200 Subject: [PATCH 092/137] Add class for toggl --- app/modules/epics/dashboard/epics-dashboard.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 1fa89a3e..b8343a50 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,6 +1,6 @@ .wrapper(ng-init="vm.loadInitialData()") tg-project-menu - section.main(role="main") + section.main.epics(role="main") header.header-with-actions h1( tg-main-title From 9bae924dce8b1c8c6a0ba15e2e2396af14febace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 15 Sep 2016 09:41:21 +0200 Subject: [PATCH 093/137] Fixes color selector layout for empty and full color list --- .../tags/components/add-tag-input.jade | 1 + .../includes/modules/admin/project-tags.jade | 23 +++++-------------- app/styles/layout/admin-project-tags.scss | 5 +++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade index 5e8de537..62b8cf1c 100644 --- a/app/modules/components/tags/components/add-tag-input.jade +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -22,6 +22,7 @@ tg-color-selector( ng-if="!vm.disableColorSelection" on-select-color="vm.selectColor(color)" + is-color-required="false" ) tg-svg.save( diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 2692089e..61538b00 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -6,16 +6,11 @@ section .admin-tags-section-wrapper form.add-tag-container.new-value.hidden - tg-color-selection.color-column( - tg-allow-empty="true" + tg-color-selector.color-column( + is-color-required="false" ng-model="newValue" + on-select-color="newValue.color = color" ) - .current-color( - ng-style="{background: newValue.color}" - ng-if="newValue.color" - ) - .current-color.empty-color(ng-if="!newValue.color") - include ../../components/select-color .tag-name input( @@ -115,17 +110,11 @@ section ) .row.tag-row.table-main.edition.hidden - .color-column( - tg-color-selection - tg-allow-empty="true" + tg-color-selector.color-column( + is-color-required="false" ng-model="tag" + on-select-color="tag.color = color" ) - .current-color( - ng-style="{background: tag.color}" - ng-if="tag.color" - ) - .current-color.empty-color(ng-if="!tag.color") - include ../../components/select-color .status-name input( diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index 731eb430..d9d7fedf 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -27,7 +27,10 @@ background: $white; } .icon { - opacity: 1; + &.icon-close, + &.icon-save { + opacity: 1; + } } } From d9905b47d9b18ed904c8ebec177000ec26637f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 15 Sep 2016 09:52:08 +0200 Subject: [PATCH 094/137] Remove only from tests --- .../color-selector/color-selector.controller.spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index f12b85cd..a8c76c80 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,7 +36,7 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller - it.only "require Color on Selector", () -> + it "require Color on Selector", () -> colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.colorList = ["#000000", "#123123"] colorSelectorCtrl.isColorRequired = false From 278ca823c1a2d6c25f2f05d327aa7591067b44d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 16 Sep 2016 14:56:51 +0200 Subject: [PATCH 095/137] Add epics illustration --- .../epics/dashboard/epics-dashboard.jade | 4 ++-- .../epics/dashboard/epics-dashboard.scss | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index b8343a50..50b88b81 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,9 +18,9 @@ ng-if="vm.epics.size" ) - section.empty-epics(ng-if="!vm.epics.size") + section.empty-epics.empty-large(ng-if="!vm.epics.size") img( - src="/#{v}/images/epics-empty.png" + src="/#{v}/images/empty/empty_des.png" ng-title="EPICS.EMPTY.HELP | translate" ) h1.title(translate="EPICS.EMPTY.TITLE") diff --git a/app/modules/epics/dashboard/epics-dashboard.scss b/app/modules/epics/dashboard/epics-dashboard.scss index b52027dc..159d1671 100644 --- a/app/modules/epics/dashboard/epics-dashboard.scss +++ b/app/modules/epics/dashboard/epics-dashboard.scss @@ -1,23 +1,5 @@ .empty-epics { - margin: 0 auto; - padding: 5vh; text-align: center; - width: 650px; - .title { - @include font-type(normal); - @include font-size(larger); - color: $gray-light; - margin-bottom: .5rem; - text-transform: none; - } - img { - margin: 2rem auto; - text-align: center; - width: 6rem; - } - p { - color: $gray-light; - } a { color: $primary; display: block; From c7c929363c9adbad98364ba43650b8d2efbb2be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 19 Sep 2016 16:04:30 +0200 Subject: [PATCH 096/137] Fix meta in some pages --- .../epics-dashboard.controller.coffee | 18 +++++++++++++----- .../projects/project/project.controller.coffee | 12 +++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index f5e5de26..040a1d47 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -41,12 +41,20 @@ class EpicsDashboardController taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - title = @translate.instant("EPICS.PAGE_TITLE", {projectName: @.project.get('name')}) - description = @translate.instant("EPICS.PAGE_DESCRIPTION", { - projectName: @.project.get("name"), + @appMetaService.setfn @._setMeta.bind(this) + + _setMeta: () -> + return null if !@.project + + ctx = { + projectName: @.project.get("name") projectDescription: @.project.get("description") - }) - @appMetaService.setAll(title, description) + } + + return { + title: @translate.instant("EPICS.PAGE_TITLE", ctx) + description: @translate.instant("EPICS.PAGE_DESCRIPTION", ctx) + } loadInitialData: () -> @epicsService.clear() diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee index 418afda7..af9d2f60 100644 --- a/app/modules/projects/project/project.controller.coffee +++ b/app/modules/projects/project/project.controller.coffee @@ -35,16 +35,14 @@ class ProjectController @appMetaService.setfn @._setMeta.bind(this) - _setMeta: (project)-> + _setMeta: ()-> return null if !@.project - metas = {} - ctx = {projectName: @.project.get("name")} - metas.title = @translate.instant("PROJECT.PAGE_TITLE", ctx) - metas.description = @.project.get("description") - - return metas + return { + title: @translate.instant("PROJECT.PAGE_TITLE", ctx) + description: @.project.get("description") + } angular.module("taigaProjects").controller("Project", ProjectController) From ab21b3f92ebf34ca165a90cab8754baf0ab7d465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:05:10 +0200 Subject: [PATCH 097/137] Redirect to blocked page in epics and project-home pages if the project is blocked --- app/coffee/app.coffee | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 513ee377..9a552542 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -537,8 +537,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: httpResponseError } - $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", "tgErrorHandlingService", - authHttpIntercept]) + $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", + "tgErrorHandlingService", authHttpIntercept]) $httpProvider.interceptors.push("authHttpIntercept") @@ -592,14 +592,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $httpProvider.interceptors.push("versionCheckHttpIntercept") - blockingIntercept = ($q, $routeParams, $location, $navUrls, errorHandlingService) -> + blockingIntercept = ($q, errorHandlingService) -> # API calls can return blocked elements and in that situation the user will be redirected # to the blocked project page # This can happens in two scenarios # - An ok response containing a blocked_code in the data # - An error reponse when updating/creating/deleting including a 451 error code redirectToBlockedPage = -> - pslug = $routeParams.pslug errorHandlingService.block() responseOk = (response) -> @@ -619,7 +618,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: responseError } - $provide.factory("blockingIntercept", ["$q", "$routeParams", "$location", "$tgNavUrls", "tgErrorHandlingService", blockingIntercept]) + $provide.factory("blockingIntercept", ["$q", "tgErrorHandlingService", blockingIntercept]) $httpProvider.interceptors.push("blockingIntercept") @@ -690,7 +689,8 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, loaderService, navigationBarService, errorHandlingService) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, + loaderService, navigationBarService, errorHandlingService) -> $log.debug("Initialize application") $rootscope.$on '$translatePartialLoaderStructureChanged', () -> @@ -733,6 +733,10 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na # Analytics $analytics.initialize() + # Initialize error handling service when location change start + $rootscope.$on '$locationChangeStart', (event) -> + errorHandlingService.init() + # On the first page load the loader is painted in `$routeChangeSuccess` # because we need to hide the tg-navigation-bar. # In the other cases the loader is in `$routeChangeSuccess` @@ -743,9 +747,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na un() - $rootscope.$on '$routeChangeSuccess', (event, next) -> - errorHandlingService.init() - + $rootscope.$on '$routeChangeSuccess', (event, next) -> if next.loader loaderService.start(true) @@ -760,7 +762,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na if next.mobileViewport appMetaService.addMobileViewport() - else + else appMetaService.removeMobileViewport() if next.disableHeader @@ -768,10 +770,13 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na else navigationBarService.enableHeader() -pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module) - +# Config for infinite scroll angular.module('infinite-scroll').value('THROTTLE_MILLISECONDS', 500) +# Load modules +pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module) +pluginsModules = _.map(pluginsWithModule, (plugin) -> plugin.module) + modules = [ # Main Global Modules "taigaBase", @@ -825,7 +830,7 @@ modules = [ "pascalprecht.translate", "infinite-scroll", "tgRepeat" -].concat(_.map(pluginsWithModule, (plugin) -> plugin.module)) +].concat(pluginsModules) # Main module definition module = angular.module("taiga", modules) From 17ef589550db0087898dbe16ec3c6aeb1a56f771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:28:32 +0200 Subject: [PATCH 098/137] Fix tests --- .../epics-dashboard.controller.spec.coffee | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index 419396b5..66f1f11a 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -78,15 +78,13 @@ describe "EpicsDashboard", -> _mockTgAppMetaService = () -> mocks.tgAppMetaService = { - setAll: sinon.stub() + setfn: sinon.stub() } provide.value "tgAppMetaService", mocks.tgAppMetaService _mockTranslate = () -> - mocks.translate = { - instant: sinon.stub() - } + mocks.translate = sinon.stub() provide.value "$translate", mocks.translate @@ -113,18 +111,9 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller - it "metada is set", (done) -> - mocks.translate.instant.withArgs("EPICS.PAGE_TITLE", { - projectName: "testing name" - }).returns("TITLE") - mocks.translate.instant.withArgs("EPICS.PAGE_DESCRIPTION", { - projectName: "testing name" - projectDescription: "testing description" - }).returns("DESCRIPTION") - + it "metada is set", () -> ctrl = controller("EpicsDashboardCtrl") - expect(mocks.tgAppMetaService.setAll).have.been.calledWith("TITLE", "DESCRIPTION") - done() + expect(mocks.tgAppMetaService.setfn).have.been.called it "load data because epics panel is enabled and user has permissions", (done) -> ctrl = controller("EpicsDashboardCtrl") From 1d1543d09ace620d752887dfb50bba9c824ee6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:42:08 +0200 Subject: [PATCH 099/137] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 181 ++++++++++++------ app/locales/taiga/locale-de.json | 237 ++++++++++++++++-------- app/locales/taiga/locale-es.json | 171 ++++++++++++----- app/locales/taiga/locale-fi.json | 171 ++++++++++++----- app/locales/taiga/locale-fr.json | 253 +++++++++++++++++--------- app/locales/taiga/locale-it.json | 171 ++++++++++++----- app/locales/taiga/locale-nl.json | 171 ++++++++++++----- app/locales/taiga/locale-pl.json | 175 +++++++++++++----- app/locales/taiga/locale-pt-br.json | 197 ++++++++++++++------ app/locales/taiga/locale-ru.json | 185 +++++++++++++------ app/locales/taiga/locale-sv.json | 171 ++++++++++++----- app/locales/taiga/locale-tr.json | 171 ++++++++++++----- app/locales/taiga/locale-zh-hant.json | 169 ++++++++++++----- 13 files changed, 1712 insertions(+), 711 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index 977c8ac1..c12f8de2 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "In item per línia", "NEW_BULK": "Nova inserció en grup", "RELATED_TASKS": "Tasques relacionades", + "PREVIOUS": "Previous", + "NEXT": "Següent", "LOGOUT": "Surt", "EXTERNAL_USER": "un usuari extern", "GENERIC_ERROR": "Un Oompa Loompas diu {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Aquest valor pareix invàlid.", "TYPE_EMAIL": "Deu ser un correu vàlid.", @@ -115,6 +122,7 @@ "USER_STORY": "Història d'usuari", "TASK": "Tasca", "ISSUE": "incidència", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Afegir tag", "DELETE": "Elimina l'etiqueta", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtres", + "TITLE": "Filtres", "INPUT_PLACEHOLDER": "Descripció o referència", "TITLE_ACTION_FILTER_BUTTON": "cerca", - "BREADCRUMB_TITLE": "tornar a categories", - "BREADCRUMB_FILTERS": "Filtres", - "BREADCRUMB_STATUS": "estats" + "INPUT_SEARCH_PLACEHOLDER": "Descripció o ref", + "TITLE_ACTION_SEARCH": "Cerca", + "ACTION_SAVE_CUSTOM_FILTER": "Guarda com a filtre", + "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", + "CATEGORIES": { + "TYPE": "Tipus", + "STATUS": "Estats", + "SEVERITY": "Severitat", + "PRIORITIES": "Prioritats", + "TAGS": "Etiquetes", + "ASSIGNED_TO": "Assignat a", + "CREATED_BY": "Creat per", + "CUSTOM_FILTERS": "Filtres personalitzats" + }, + "CONFIRM_DELETE": { + "TITLE": "Esborrar filtre", + "MESSAGE": "el filtre '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Capçcalera de primer nivel", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ajuda de Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Vore sprints", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observant", "DASHBOARD": "Panell principal" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Sense assignar" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Vots", + "NAME": "Nom", + "PROJECT": "Projecte", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Estats", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloquejat", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Els meus projectes - Taiga", "PAGE_DESCRIPTION": "Una llista de tots els teus projects, que pots reordenar o crear nous.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Borrar valor" + "TITLE_ACTION_DELETE_VALUE": "Borrar valor", + "TITLE_ACTION_DELETE_TAG": "Elimina l'etiqueta" }, "HELP": "Necessites ajuda? Mira la nosta pàgina de suport!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Mòdules", "ENABLE": "Activa", "DISABLE": "Desactiva", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Organitza les històries d'usuari per a mantindre una vista organitzada i prioritzada del treball.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -475,9 +544,9 @@ "PRIVATE_PROJECT": "Projecte privat", "PRIVATE_OR_PUBLIC": "What's the difference between public and private projects?", "DELETE": "Esborra aquest projecte", - "LOGO_HELP": "The image will be scaled to 80x80px.", + "LOGO_HELP": "S'escalarà la imatge a 80x80px.", "CHANGE_LOGO": "Change logo", - "ACTION_USE_DEFAULT_LOGO": "Use default image", + "ACTION_USE_DEFAULT_LOGO": "Utilitza la imatge per defecte", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Vas a canviar la URL d'accés al CSV. La URL previa no funcionarà. Estàs segur?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "informes d'històries d'usuari", "SECTION_TITLE_TASK": "infome de tasques", "SECTION_TITLE_ISSUE": "informe d'incidències", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Camps personalitzats", "SUBTITLE": "Especifiqueu els camps personalitzats del les vostres històries d'usuari, tasques i incidències", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Camps personalitzats d'històries d'usuari", "US_ADD": "Afegeix camps personalitzats en històries d'usuari", "TASK_DESCRIPTION": "Camps personalitzats de tasques", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Estat", "SUBTITLE": "Especifica els estats de les vostres històries d'usuari, tasques i incidències", - "US_TITLE": "Estats d'US", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estats de tasques", "ISSUE_TITLE": "Estats d'incidències" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiquetes", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Afegeix l'etiqueta", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rols - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "la invitació a '{{email}}'." }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valor per defecte per a selector de punts", - "LABEL_US": "Valor per defecte per a selector d'estats d'US", "LABEL_TASK_STATUS": "Valor per defecte per a selector d'estats de tasques", - "LABEL_PRIORITY": "Valor per defecte per a selector de prioritat", - "LABEL_SEVERITY": "Valor per defecte per a selector de severitat", "LABEL_ISSUE_TYPE": "Valor per defecte per a selector de tipus", - "LABEL_ISSUE_STATUS": "Valor per defecte per a selector de estats" + "LABEL_ISSUE_STATUS": "Valor per defecte per a selector de estats", + "LABEL_PRIORITY": "Valor per defecte per a selector de prioritat", + "LABEL_SEVERITY": "Valor per defecte per a selector de severitat" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Escriu un nom per a nou estat" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tot", "FILTER_TYPE_PROJECTS": "Projectes", "FILTER_TYPE_PROJECT_TITLES": "Mostra només projectes", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Históries", "FILTER_TYPE_USER_STORIES_TITLES": "Veure només històries d'usuari", "FILTER_TYPE_TASKS": "Tasques", @@ -836,7 +917,7 @@ "CHANGE_PASSWORD": "Canvi de contrasenya", "DASHBOARD_TITLE": "Tauler", "DISCOVER_TITLE": "Discover trending projects", - "NEW_ITEM": "New", + "NEW_ITEM": "Nova", "DISCOVER": "Descobreix", "ACTION_REORDER": "Arrossega els elements per endreçar" }, @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Afegix un text personalizat a la invitació. Dis-li algo divertit als nous membres. ;-)", "PLACEHOLDER_TYPE_EMAIL": "Escriu un correu", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Història d'Usuari {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{userStoryStatus }}. Completat {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tasques tancades). Punts: {{userStoryPoints}}. Descripció: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Aquesta US ha sigut creada desde", "GO_TO_EXTERNAL_REFERENCE": "Anar a l'orige", "BLOCKED": "Aquest història d'usuari està bloquejada", - "PREVIOUS": "previa història d'usuari", - "NEXT": "Pròxima història d'usuari", "TITLE_DELETE_ACTION": "Esborra història d'usuari", "LIGHTBOX_TITLE_BLOKING_US": "Bloquejant US", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tasques completades", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "ordre d'sprint", "KANBAN_ORDER": "ordre de kanban", "TASKBOARD_ORDER": "ordre de panell de tasques", - "US_ORDER": "ordre d'US" + "US_ORDER": "ordre d'US", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtres", "REMOVE": "Esborra filtres", "HIDE": "Amaga filtres", - "SHOW": "Mostra filtres", - "FILTER_CATEGORY_STATUS": "Estats", - "FILTER_CATEGORY_TAGS": "Etiquetes" + "SHOW": "Mostra filtres" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Aquesta tasca ha sigut creada desde", "TITLE_LINK_GO_ORIGIN": "Anar a història d'usuari", "BLOCKED": "Aquesta tasca està bloquejada", - "PREVIOUS": "tasca prèvia", - "NEXT": "pròxima tasca", "TITLE_DELETE_ACTION": "Esborrar tasca", "LIGHTBOX_TITLE_BLOKING_TASK": "Bloquejant tasca", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "incidència", "ACTION_NEW_ISSUE": "+ NOVA INCIDÈNCIA", "ACTION_PROMOTE_TO_US": "Promocionar història d'usuari", - "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", "PROMOTED": "Esta incidència ha sigut promcionada a US:", "EXTERNAL_REFERENCE": "Esta incidència ha sigut creada desde", "GO_TO_EXTERNAL_REFERENCE": "Anar a l'orige", "BLOCKED": "Aquesta incidència està bloquejada", - "TITLE_PREVIOUS_ISSUE": "incidència prèvia", - "TITLE_NEXT_ISSUE": "pròxima incidència", "ACTION_DELETE": "Esborrar incidència", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Bloquejant incidència", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promociona aquesta incidència a història d'usuari", "MESSAGE": "Segur que vols crear una nova US desde aquesta incidència" }, - "FILTERS": { - "TITLE": "Filtres", - "INPUT_SEARCH_PLACEHOLDER": "Descripció o ref", - "TITLE_ACTION_SEARCH": "Busca", - "ACTION_SAVE_CUSTOM_FILTER": "Guarda com a filtre", - "BREADCRUMB": "Filtres", - "TITLE_BREADCRUMB": "Filtres", - "CATEGORIES": { - "TYPE": "Tipus", - "STATUS": "Estats", - "SEVERITY": "Severitat", - "PRIORITIES": "Prioritats", - "TAGS": "Etiquetes", - "ASSIGNED_TO": "Assignat a", - "CREATED_BY": "Creat per", - "CUSTOM_FILTERS": "Filtres personalitzats" - }, - "CONFIRM_DELETE": { - "TITLE": "Esborrar filtre", - "MESSAGE": "el filtre '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipus", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Cerca - {{projectName}}", "PAGE_DESCRIPTION": "Busca qualsevol cosa al projecte {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Històries d'usuari", "FILTER_ISSUES": "Incidències", "FILTER_TASKS": "Tasca", @@ -1422,9 +1493,9 @@ } }, "USER_PROFILE": { - "IMAGE_HELP": "The image will be scaled to 80x80px.", + "IMAGE_HELP": "S'escalarà la imatge a 80x80px.", "ACTION_CHANGE_IMAGE": "Canviar", - "ACTION_USE_GRAVATAR": "Use default image", + "ACTION_USE_GRAVATAR": "Utilitza la imatge per defecte", "ACTION_DELETE_ACCOUNT": "Esborrar compte de Taiga", "CHANGE_EMAIL_SUCCESS": "Mira el teu correu!
Hem enviat un correu al teu conter
amb les instrucciones per a escriure una nova adreça de correu", "CHANGE_PHOTO": "Canviar foto", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creat una nova tasca {{obj_name}} a {{project_name}} provinent de US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} ha creat el projecte {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha actualitzat l'atribut \"{{field_name}}\" de la tasca {{obj_name}} de la història d'usuari {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha actualitzat l'atribut \"{{field_name}}\" de la tasca {{obj_name}} de la història d'usuari {{us_name}} amb el valor {{new_value}}", "WIKI_UPDATED": "{{username}} ha actualitzat la pàgina wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha comentat la història d'usuari {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha comentat la incidència {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha comentat la tasca {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} te un nou membre", "US_ADDED_MILESTONE": "{{username}} ha afegit la història d'usuari {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha mogut la història d'usuari {{obj_name}}", diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 9de20cf3..f9bd86d1 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -5,8 +5,8 @@ "OR": "oder", "LOADING": "Wird geladen...", "LOADING_PROJECT": "Projekt wird geladen...", - "DATE": "DD MMM YYYY", - "DATETIME": "DD MMM YYYY HH:mm", + "DATE": "DD. MMM YYYY", + "DATETIME": "DD. MMM YYYY HH:mm", "SAVE": "Speichern", "CANCEL": "Abbrechen", "ACCEPT": "Akzeptieren", @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Ein Eintrag pro Zeile...", "NEW_BULK": "Neue Massenerstellung", "RELATED_TASKS": "Verbundene Aufgaben", + "PREVIOUS": "Previous", + "NEXT": "Weiter", "LOGOUT": "Ausloggen", "EXTERNAL_USER": "ein externer Benutzer", "GENERIC_ERROR": "Eins unserer Helferlein sagt {{error}}.", @@ -44,7 +46,12 @@ "OWNER": "Projekteigentümer", "CAPSLOCK_WARNING": "Achtung! Sie verwenden Großbuchstaben in einem Eingabefeld, dass Groß- und Kleinschreibung berücksichtigt.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Sind Sie sicher, dass Sie den Bearbeitungsmodus beenden möchten?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Beachten Sie, dass alle Änderungen verloren gehen, wenn Sie den Bearbeitungsmodus schließen, ohne vorher zu speichern.", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Dieser Wert scheint ungültig zu sein.", "TYPE_EMAIL": "Dieser Wert sollte eine gültige E-Mail Adresse enthalten.", @@ -73,7 +80,7 @@ "PIKADAY": "Ungültiges Datumsformat. Bitte nutze DD MMM YYYY (etwa 23 März 1984)" }, "PICKERDATE": { - "FORMAT": "DD MMM YYYY", + "FORMAT": "DD. MMM YYYY", "IS_RTL": "falsch", "FIRST_DAY_OF_WEEK": "1", "PREV_MONTH": "Vorheriger Monat", @@ -115,6 +122,7 @@ "USER_STORY": "User-Story", "TASK": "Aufgabe", "ISSUE": "Ticket", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Schlagwort...", "DELETE": "Schlagwort löschen", @@ -196,9 +204,24 @@ "TITLE": "Filter", "INPUT_PLACEHOLDER": "Betreff oder Verweis", "TITLE_ACTION_FILTER_BUTTON": "suche", - "BREADCRUMB_TITLE": "zurück zu den Kategorien", - "BREADCRUMB_FILTERS": "Filter", - "BREADCRUMB_STATUS": "Status" + "INPUT_SEARCH_PLACEHOLDER": "Thema oder ref", + "TITLE_ACTION_SEARCH": "Suche", + "ACTION_SAVE_CUSTOM_FILTER": "Als Benutzerfilter speichern", + "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", + "CATEGORIES": { + "TYPE": "Arten", + "STATUS": "Status", + "SEVERITY": "Gewichtung", + "PRIORITIES": "Prioritäten", + "TAGS": "Schlagwörter", + "ASSIGNED_TO": "Zugeordnet zu", + "CREATED_BY": "Erstellt durch", + "CUSTOM_FILTERS": "Benutzerfilter" + }, + "CONFIRM_DELETE": { + "TITLE": "Benutzerfilter löschen", + "MESSAGE": "der Benutzerfilter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Überschrift 1", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown syntax Hilfe" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Sprints ansehen", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Beobachtet", "DASHBOARD": "ProjeKte Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ EPIC HINZUFÜGEN", + "UNASSIGNED": "Nicht zugeordnet" + }, + "EMPTY": { + "TITLE": "Es sieht so aus, als hätten Sie noch keine Epics erzeugt", + "EXPLANATION": "Erstellen Sie Epics als übergeordnete Einheit für User Storys. Epics können User Storys aus diesem oder anderen Projekten beinhalten oder aus Ihnen erstellt werden.", + "HELP": "Erfahren Sie mehr über Epics" + }, + "TABLE": { + "VOTES": "Stimmen", + "NAME": "Name", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Zugewiesen", + "STATUS": "Status", + "PROGRESS": "Fortschritt", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "Neues Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team-Anforderung", + "CLIENT_REQUIREMENT": "Kunden-Anforderung", + "BLOCKED": "Blockiert", + "BLOCKED_NOTE_PLACEHOLDER": "Warum ist dieses Epic geblockt?", + "CREATE_EPIC": "Epic erzeugen" + } + }, "PROJECTS": { "PAGE_TITLE": "Meine Projekte - Taiga", "PAGE_DESCRIPTION": "Eine Liste mit all Deinen Projekten. Du kannst sie ordnen oder ein Neues anlegen.", @@ -389,7 +455,7 @@ "HIDE_DEPRECATED": "- verworfene Anhänge verbergen", "COUNT_DEPRECATED": "({{ counter }} verworfen)", "MAX_UPLOAD_SIZE": "Die maximale Dateigröße beträgt {{maxFileSize}}", - "DATE": "DD MMM YYYY [um] hh:mm", + "DATE": "DD. MMM YYYY [um] hh:mm", "ERROR_UPLOAD_ATTACHMENT": "Das Hochladen war uns nicht möglich '{{fileName}}'. {{errorMessage}}", "TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Anhang löschen...", "MSG_LIGHTBOX_DELETE_ATTACHMENT": "der Anhang '{{fileName}}'", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Wert bearbeiten", - "TITLE_ACTION_DELETE_VALUE": "Wert löschen" + "TITLE_ACTION_DELETE_VALUE": "Wert löschen", + "TITLE_ACTION_DELETE_TAG": "Schlagwort löschen" }, "HELP": "Wenn Sie Hilfe benötigen, besuchen Sie unsere Support-Seite!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Module", "ENABLE": "Aktivieren", "DISABLE": "Deaktivieren", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualisieren und verwalten Sie den strategischsten Teil Ihres Projektes", "BACKLOG": "Auftragsliste", "BACKLOG_DESCRIPTION": "Verwalten Sie Ihre User-Stories, um einen organisierten Überblick der anstehenden und priorisierten Aufgaben zu erhalten.", "NUMBER_SPRINTS": "Erwartete Anzahl an Sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Sie sind im Begriff, die CSV data access URL zu ändern. Die vorherige URL wird deaktiviert. Sind Sie sicher?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "User-Stories Berichte", "SECTION_TITLE_TASK": "Aufgabenberichte", "SECTION_TITLE_ISSUE": "Ticket Berichte", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Benutzerfelder", "SUBTITLE": "Spezifizieren Sie die Benutzerfelder für Ihre User-Stories, Aufgaben und Tickets.", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Benutzerdefinierte Felder der User-Story", "US_ADD": "Benutzerdefiniertes Feld bei User-Stories hinzufügen", "TASK_DESCRIPTION": "Aufgaben benutzerdefinierte Felder", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Spezifizieren Sie die Status, die Ihre User-Stories, Aufgaben und Tickets durchlaufen werden.", - "US_TITLE": "User-Story Status", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Aufgaben-Status", "ISSUE_TITLE": "Ticket-Status" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Schlagwörter", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "Es sieht so aus, als konnte zu Ihren Suchkriterien nichts passendes gefunden werden." + "EMPTY_SEARCH": "Es sieht so aus, als konnte zu Ihren Suchkriterien nichts passendes gefunden werden.", + "ACTION_ADD": "Schlagwort hinzufügen", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", @@ -621,7 +699,7 @@ "HEADERS": "Überschriften", "PAYLOAD": "Ladung", "RESPONSE": "Rückmeldung", - "DATE": "DD MMM YYYY [um] hh:mm:ss", + "DATE": "DD. MMM YYYY [um] hh:mm:ss", "ACTION_HIDE_HISTORY": "(Chronik verbergen)", "ACTION_HIDE_HISTORY_TITLE": "Chronik Details verbergen", "ACTION_SHOW_HISTORY": "(Chronik anzeigen)", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "die Einladung an {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Vorgegebener Wert für Punkteauswahl", - "LABEL_US": "Vorgegebener Wert für User-Story-Status Auswahl", "LABEL_TASK_STATUS": "Vorgegebene Auswahl für den Aufgaben-Status", - "LABEL_PRIORITY": "Vorgegebener Wert für Prioritätsauswahl", - "LABEL_SEVERITY": "Vorgegebener Wert für Gewichtungsauswahl", "LABEL_ISSUE_TYPE": "Vorgegebener Wert für Ticketartauswahl", - "LABEL_ISSUE_STATUS": "Vorgegebene Auswahl für den Ticket-Status" + "LABEL_ISSUE_STATUS": "Vorgegebene Auswahl für den Ticket-Status", + "LABEL_PRIORITY": "Vorgegebener Wert für Prioritätsauswahl", + "LABEL_SEVERITY": "Vorgegebener Wert für Gewichtungsauswahl" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Benennen Sie den neuen Status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Alle anzeigen", "FILTER_TYPE_PROJECTS": "Projekte", "FILTER_TYPE_PROJECT_TITLES": "Nur Projekte anzeigen", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Nur User-Stories anzeigen", "FILTER_TYPE_TASKS": "Aufgaben", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optional) Fügen Sie einen persönlichen Text zur Einladung hinzu. Erzählen Sie Ihren neuen Mitgliedern etwas Schönes. ;-)", "PLACEHOLDER_TYPE_EMAIL": "Geben Sie eine E-Mail ein", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben. Wenn Sie die derzeitige Grenze erhöhen möchten, kontaktieren Sie den Administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Das Projekt kann nicht ohne einen Projektleiter existieren.", @@ -985,6 +1066,25 @@ "BUTTON": "Fragen Sie dieses Projektmitglied, um Projektleiter zu werden" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User-Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Abgeschlossen {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} von {{userStoryTotalTasks}} Aufgaben geschlossen). Punkte: {{userStoryPoints}}. Beschreibung: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Dies User-Story wurde angelegt von", "GO_TO_EXTERNAL_REFERENCE": "Zur Quelle wechseln", "BLOCKED": "Diese User-Story wird blockiert", - "PREVIOUS": "Vorherige User-Story", - "NEXT": "nächste User-Story", "TITLE_DELETE_ACTION": "User-Story löschen", "LIGHTBOX_TITLE_BLOKING_US": "Blockiert uns", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} Aufgaben fertiggestellt", @@ -1011,11 +1109,11 @@ "PUBLISH": "Als Gig in Taiga Tribe veröffentlichen", "PUBLISH_INFO": "Weitere Infos", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", - "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story veröffentlicht als Gig in Taiga Tribe", "EDIT_LINK": "Link bearbeiten", "CLOSE": "Schließen", "SYNCHRONIZE_LINK": "mit Taiga Tribe synchronisieren", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TITLE": "Brauchen Sie jemanden für diese Aufgabe?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, "FIELDS": { @@ -1025,15 +1123,15 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Kommentar gelöscht von {{user}}", "TITLE": "Kommentare", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} Kommentare", + "ORDER": "Reihenfolge", + "OLDER_FIRST": "Ältere zuerst", + "RECENT_FIRST": "Letzte zuerst", "COMMENT": "Kommentieren", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", + "EDIT_COMMENT": "Kommentar bearbeiten", + "EDITED_COMMENT": "Bearbeitet:", "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Geben Sie hier einen neuen Kommentar ein", "SHOW_DELETED": "Gelöschten Kommentar anzeigen", @@ -1046,22 +1144,22 @@ }, "ACTIVITY": { "SHOW_ACTIVITY": "Aktivitäten zeigen", - "DATETIME": "DD MMM YYYY HH:mm", + "DATETIME": "DD. MMM YYYY HH:mm", "SHOW_MORE": "+ Vorherige Einträge zeigen ({{showMore}} vorhanden)", "TITLE": "Aktivität", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} Aktivitäten", "REMOVED": "entfernt", "ADDED": "hinzugefügt", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", + "TAGS_ADDED": "Tags hinzugefügt:", + "TAGS_REMOVED": "Tags entfernt:", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", + "NEW_ATTACHMENT": "neuer Anhang:", + "DELETED_ATTACHMENT": "gelöschter Anhang:", "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Machte {size, plural, one{eine Änderung} other{# Änderungen}}", - "BECAME_DEPRECATED": "became deprecated", + "BECAME_DEPRECATED": "ist veraltet", "BECAME_UNDEPRECATED": "became undeprecated", "TEAM_REQUIREMENT": "Team Anforderung", "CLIENT_REQUIREMENT": "Kundenanforderung", @@ -1097,13 +1195,14 @@ "TAGS": "Schlagwörter", "ATTACHMENTS": "Anhänge", "IS_DEPRECATED": "ist veraltet", - "IS_NOT_DEPRECATED": "is not deprecated", + "IS_NOT_DEPRECATED": "ist nicht verworfen", "ORDER": "Befehl", "BACKLOG_ORDER": "Backlog Befehl", "SPRINT_ORDER": "Sprint Befehl", "KANBAN_ORDER": "Kanban Befehl", "TASKBOARD_ORDER": "Taskboard Befehl", - "US_ORDER": "User-Story Befehl" + "US_ORDER": "User-Story Befehl", + "COLOR": "color" } }, "BACKLOG": { @@ -1156,7 +1255,7 @@ "IOCAINE_DOSES": "Iocaine
Dosen", "SHOW_STATISTICS_TITLE": "Statistik anzeigen", "TOGGLE_BAKLOG_GRAPH": "Zeige/Verstecke Burndowngraph", - "POINTS_PER_ROLE": "Points per role" + "POINTS_PER_ROLE": "Points pro Rolle" }, "SUMMARY": { "PROJECT_POINTS": "Projekt
Punkte", @@ -1169,13 +1268,11 @@ "TITLE": "Filter", "REMOVE": "Filter entfernen", "HIDE": "Filter verbergen", - "SHOW": "Filter anzeigen", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Schlagwörter" + "SHOW": "Filter anzeigen" }, "SPRINTS": { "TITLE": "SPRINTS", - "DATE": "DD MMM YYYY", + "DATE": "DD. MMM YYYY", "LINK_TASKBOARD": "Sprint Taskboard", "TITLE_LINK_TASKBOARD": "Gehe zu Taskboard von \"{{name}}\"", "NUMBER_SPRINTS": "
Sprints", @@ -1220,7 +1317,7 @@ "YAXIS_LABEL": "Punkte", "OPTIMAL": "Optimale unerledigte Punkte für Tag {{formattedDate}} sollten sein {{roundedValue}}", "REAL": "Tatsächliche Anzahl unerledigter Punkte für Tag {{formattedDate}} ist {{roundedValue}}", - "DATE": "DD MMMM YYYY" + "DATE": "DD. MMMM YYYY" } }, "TASK": { @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Diese Aufgabe wurde erstellt durch", "TITLE_LINK_GO_ORIGIN": "Zu User-Story wechseln", "BLOCKED": "Diese Aufgabe wird blockiert", - "PREVIOUS": "vorherige Aufgabe", - "NEXT": "nächste Aufgabe", "TITLE_DELETE_ACTION": "Aufgabe löschen", "LIGHTBOX_TITLE_BLOKING_TASK": "Blockierende Aufgabe", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NEUES TICKET", "ACTION_PROMOTE_TO_US": "Zur User-Story aufwerten", - "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", "PROMOTED": "Dieses Ticket wurde aufgewertet zu User-Story:", "EXTERNAL_REFERENCE": "Dieses Ticket wurde erstellt durch", "GO_TO_EXTERNAL_REFERENCE": "Zur Quelle wechseln", "BLOCKED": "Dieses Ticket wird blockiert", - "TITLE_PREVIOUS_ISSUE": "vorheriges Ticket", - "TITLE_NEXT_ISSUE": "nächstes Ticket", "ACTION_DELETE": "Ticket löschen", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blockierendes Ticket", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Dieses Problem zur User-Story aufwerten", "MESSAGE": "Sind Sie sicher, dass Sie aus diesem Ticket eine neue User-Story erstellen möchten?" }, - "FILTERS": { - "TITLE": "Filter", - "INPUT_SEARCH_PLACEHOLDER": "Thema oder ref", - "TITLE_ACTION_SEARCH": "Suche", - "ACTION_SAVE_CUSTOM_FILTER": "Als Benutzerfilter speichern", - "BREADCRUMB": "Filter", - "TITLE_BREADCRUMB": "Filter", - "CATEGORIES": { - "TYPE": "Arten", - "STATUS": "Status", - "SEVERITY": "Gewichtung", - "PRIORITIES": "Prioritäten", - "TAGS": "Schlagwörter", - "ASSIGNED_TO": "Zugeordnet", - "CREATED_BY": "Erstellt durch", - "CUSTOM_FILTERS": "Benutzerfilter" - }, - "CONFIRM_DELETE": { - "TITLE": "Benutzerfilter löschen", - "MESSAGE": "der Benutzerfilter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Arten", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Suche - {{projectName}}", "PAGE_DESCRIPTION": "Suchen Sie User-Stories, Tickets, Aufgaben oder Wiki Seiten im Projekt {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User-Stories", "FILTER_ISSUES": "Tickets", "FILTER_TASKS": "Aufgaben", @@ -1464,24 +1535,24 @@ "DELETE_LIGHTBOX_TITLE": "Wiki Seite löschen", "DELETE_LINK_TITLE": "Entferne Wiki Link", "NAVIGATION": { - "HOME": "Main Page", + "HOME": "Hauptseite", "SECTION_NAME": "BOOKMARKS", - "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "ACTION_ADD_LINK": "Bookmark hinzufügen", + "ALL_PAGES": "Alle Wiki-Seiten" }, "SUMMARY": { "TIMES_EDITED": "mal
bearbeitet", "LAST_EDIT": "letzte
Bearbeitung", "LAST_MODIFICATION": "letzte Änderung" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Alle Seiten", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", + "TITLE": "Titel", "EDITIONS": "Editions", "CREATED": "Erstellt", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "MODIFIED": "Geändert", + "CREATOR": "Ersteller", + "LAST_MODIFIER": "Letzter Bearbeiter" } }, "HINTS": { @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} erstellte die neue Aufgabe {{obj_name}} in {{project_name}}, die zur User-Story {{us_name}} gehört", "WIKI_CREATED": "{{username}} erstellte die neue Wiki Seite {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} erstellte den neuen Sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} erstellte das Projekt {{project_name}}", "MILESTONE_UPDATED": "{{username}} aktualisierte den Sprint {{obj_name}}", "US_UPDATED": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der User-Story {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der Aufgabe {{obj_name}} von User-Story {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der Aufgabe {{obj_name}} die zu der User-Story gehört {{us_name}} zu {{new_value}}", "WIKI_UPDATED": "{{username}} aktualisierte die WIKI Seite {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} schrieb einen Kommentar in der User-Story {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} schrieb einen Kommentar im Ticket {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} schrieb einen Kommentar in der Aufgabe {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} hat ein neues Mitglied", "US_ADDED_MILESTONE": "{{username}} fügte dem Sprint {{sprint_name}} die User-Story {{obj_name}} hinzu", "US_MOVED": "{{username}} wurde in die Story {{obj_name}} verschoben", diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index da2c4135..77c2972e 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un elemento por línea...", "NEW_BULK": "Nueva inserción en bloque", "RELATED_TASKS": "Tareas relacionadas", + "PREVIOUS": "Previous", + "NEXT": "Siguiente", "LOGOUT": "Cerrar sesión", "EXTERNAL_USER": "un usuario externo", "GENERIC_ERROR": "Uno de nuestros Oompa Loompas dice {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "¡Cuidado!. Esta usando mayusculas en un campo sensible a mayusculas", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "¿Seguro que desea cerrar el modo de edición?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Recuerde que si cierra el modo de edicion sin guardar todos los cambios se perderán", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece inválido.", "TYPE_EMAIL": "El valor debe ser un email.", @@ -115,6 +122,7 @@ "USER_STORY": "Historia de usuario", "TASK": "Tarea", "ISSUE": "Petición", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "¿Qué soy? Etiquétame...", "DELETE": "Borrar etiqueta", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Se borrarán todos los valores de este atributo personalizado. \n¿Estás seguro de que quieres continuar?" }, "FILTERS": { - "TITLE": "filtros", + "TITLE": "Filtros", "INPUT_PLACEHOLDER": "Asunto o referencia", "TITLE_ACTION_FILTER_BUTTON": "busqueda", - "BREADCRUMB_TITLE": "Regresar a categorias", - "BREADCRUMB_FILTERS": "Filtros", - "BREADCRUMB_STATUS": "estado" + "INPUT_SEARCH_PLACEHOLDER": "Asunto o referencia", + "TITLE_ACTION_SEARCH": "Buscar", + "ACTION_SAVE_CUSTOM_FILTER": "guardar como filtro personalizado", + "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Estado", + "SEVERITY": "Gravedad", + "PRIORITIES": "Prioridades", + "TAGS": "Etiquetas", + "ASSIGNED_TO": "Asignado a", + "CREATED_BY": "Creada por", + "CUSTOM_FILTERS": "Filtros personalizados" + }, + "CONFIRM_DELETE": { + "TITLE": "Eliminar filtros personalizados", + "MESSAGE": "el filtro personalizado '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Título de primer nivel", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ayuda de sintaxis Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Ver sprints", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observando", "DASHBOARD": "Dashboard de proyecto" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "No asignado" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Votos", + "NAME": "Nombre", + "PROJECT": "Proyecto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Estado", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloqueada", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mis proyectos - Taiga", "PAGE_DESCRIPTION": "Una lista con todos tus proyectos, puedes reordenarla o crear un proyecto nuevo.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Eliminar valor" + "TITLE_ACTION_DELETE_VALUE": "Eliminar valor", + "TITLE_ACTION_DELETE_TAG": "Borrar etiqueta" }, "HELP": "¿Necesitas ayuda? ¡Revisa nuestra pagina de soporte! ", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Módulos", "ENABLE": "Activado", "DISABLE": "Desactivado", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gestiona tus historias de usuario para mantener una vista organizada y priorizada de los próximos trabajos que deberás afrontar. ", "NUMBER_SPRINTS": "Numero esperado de sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Vas a cambiar la url de acceso a los datos en formato CSV. La url anterior se deshabilitará. ¿Estás seguro?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "informes de historias de usuario", "SECTION_TITLE_TASK": "Informes de tareas", "SECTION_TITLE_ISSUE": "informes de peticiones", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Atributos personalizados", "SUBTITLE": "Especifica los atributos personalizados para las historias de usuario, tareas y peticiones", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Atributos personalizados de historias de usuario", "US_ADD": "Añadir un atributo personalizado en las historias de usuario", "TASK_DESCRIPTION": "Atributos personalizados de tareas", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Estado", "SUBTITLE": "Especifica los estado que atravesarán tus historias de usuario, tareas y peticiones", - "US_TITLE": "Estados de historias", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estados de Tarea", "ISSUE_TITLE": "Estados de la petición" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiquetas", - "SUBTITLE": "Ver y editar el color de sus historias", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Actualmente no hay etiquetas", - "EMPTY_SEARCH": "Parece que no se encontro nada con este criterio de busqueda" + "EMPTY_SEARCH": "Parece que no se encontro nada con este criterio de busqueda", + "ACTION_ADD": "Añadir etiqueta", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "la invitación enviada a" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valor por defecto para el selector de puntos", - "LABEL_US": "Valor por defecto para el selector de estado de historia", "LABEL_TASK_STATUS": "Valor por defecto para el selector de estado de tarea", - "LABEL_PRIORITY": "Valor por defecto para el selector de prioridad", - "LABEL_SEVERITY": "Valor por defecto para el selector de gravedad", "LABEL_ISSUE_TYPE": "Valor por defecto para el selector de tipo de la petición", - "LABEL_ISSUE_STATUS": "Valor por defecto para el selector de estado de petición" + "LABEL_ISSUE_STATUS": "Valor por defecto para el selector de estado de petición", + "LABEL_PRIORITY": "Valor por defecto para el selector de prioridad", + "LABEL_SEVERITY": "Valor por defecto para el selector de gravedad" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Escribe un nombre para el nuevo estado" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar todos", "FILTER_TYPE_PROJECTS": "Proyectos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar sólo proyectos", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Historias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar sólo historias de usuario", "FILTER_TYPE_TASKS": "Tareas", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Añade un texto personalizado a la invitación. Dile algo encantador a tus nuevos miembros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Escribe un email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros.
Si desea aumentar este limite, por favor contacte al administrador.", - "LIMIT_USERS_WARNING_MESSAGE": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Por desgracia, este proyecto no puede ser dejado sin dueño", @@ -985,6 +1066,25 @@ "BUTTON": "Pregunte a este usuario para convertirlo en el nuero dueño del proyecto" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Historia de Usuario {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completado el {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tareas cerradas). Puntos: {{userStoryPoints}}. Descripción: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Esta historia ha sido creada desde", "GO_TO_EXTERNAL_REFERENCE": "Ir al origen", "BLOCKED": "Esta historia de usuario está bloqueada", - "PREVIOUS": "anterior historia de usuario", - "NEXT": "siguiente historia de usuario", "TITLE_DELETE_ACTION": "Borrar Historia de Usuario", "LIGHTBOX_TITLE_BLOKING_US": "Historia bloqueada", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tareas completadas", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "orden en sprint", "KANBAN_ORDER": "orden en kanban", "TASKBOARD_ORDER": "orden en panel de tareas", - "US_ORDER": "orden en historia" + "US_ORDER": "orden en historia", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtros", "REMOVE": "Borrar Filtros", "HIDE": "Ocultar filtros", - "SHOW": "Ver Filtros", - "FILTER_CATEGORY_STATUS": "Estado", - "FILTER_CATEGORY_TAGS": "Etiquetas" + "SHOW": "Ver Filtros" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Esta tarea pertenece a ", "TITLE_LINK_GO_ORIGIN": "Ir a historia de usuario", "BLOCKED": "Esta tarea está bloqueada", - "PREVIOUS": "tarea anterior", - "NEXT": "tarea siguiente", "TITLE_DELETE_ACTION": "Eliminar Tarea", "LIGHTBOX_TITLE_BLOKING_TASK": "Tarea bloqueada", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Petición", "ACTION_NEW_ISSUE": "+ NUEVA PETICIÓN", "ACTION_PROMOTE_TO_US": "Promover a Historia de Usuario", - "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", "PROMOTED": "Esta petición ha sido promovida a la historia:", "EXTERNAL_REFERENCE": "Esta petición ha sido creada a partir de ", "GO_TO_EXTERNAL_REFERENCE": "Ir al origen", "BLOCKED": "La petición está bloqueada", - "TITLE_PREVIOUS_ISSUE": "petición anterior", - "TITLE_NEXT_ISSUE": "petición siguiente", "ACTION_DELETE": "Borrar petición", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Petición bloqueada", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promover esta petición a una nueva historia de usuario", "MESSAGE": "¿Está seguro de que desea crear una nueva Historia de Usuario a partir de esta Petición?" }, - "FILTERS": { - "TITLE": "Filtros", - "INPUT_SEARCH_PLACEHOLDER": "Asunto o referencia", - "TITLE_ACTION_SEARCH": "Buscar", - "ACTION_SAVE_CUSTOM_FILTER": "guardar como filtro personalizado", - "BREADCRUMB": "Filtros", - "TITLE_BREADCRUMB": "Filtros", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Estado", - "SEVERITY": "Gravedad", - "PRIORITIES": "Prioridad", - "TAGS": "Etiquetas", - "ASSIGNED_TO": "Asignado a", - "CREATED_BY": "Creada por", - "CUSTOM_FILTERS": "Filtros personalizados" - }, - "CONFIRM_DELETE": { - "TITLE": "Eliminar filtros personalizados", - "MESSAGE": "el filtro personalizado '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", "PAGE_DESCRIPTION": "Busca cualquier cosa: historias de usuario, peticiones, tareas o páginas del wiki en el proyecto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Historias de Usuario", "FILTER_ISSUES": "Peticiones", "FILTER_TASKS": "Tareas", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creado una nueva tarea {{obj_name}} en {{project_name}} que proviene de la historia {{us_name}}", "WIKI_CREATED": "{{username}} ha creado una nueva página de wiki {{obj_name}} en {{project_name}}\n", "MILESTONE_CREATED": "{{username}} ha creado un nuevo sprint {{obj_name}} en {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} creó el proyecto {{project_name}}", "MILESTONE_UPDATED": "{{username}} ha actualizado el sprint {{obj_name}}", "US_UPDATED": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la historia {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}} que proviene de la historia {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}} que pertenece a la historia {{us_name}} a {{new_value}}", "WIKI_UPDATED": "{{username}} ha actualizado la página del wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha añadido un comentado en la historia {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha añadido un comentado en la petición {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha añadido un comentado en la tarea {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} tiene un nuevo miembro", "US_ADDED_MILESTONE": "{{username}} ha añadido la historia {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha movido la historia {{obj_name}}", diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 2492cebc..b8873b04 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Yksi riviä kohti...", "NEW_BULK": "Lisää monta", "RELATED_TASKS": "Liittyvät tehtävät", + "PREVIOUS": "Previous", + "NEXT": "Seuraava", "LOGOUT": "Kirjaudu ulos", "EXTERNAL_USER": "ulkoinen käyttäjä", "GENERIC_ERROR": "Oompa Loompas havaitsivat virheen {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Tämä arvo vaikuttaa virheelliseltä.", "TYPE_EMAIL": "Tämän pitäisi olla toimiva sähköpostiosoite.", @@ -115,6 +122,7 @@ "USER_STORY": "Käyttäjätarina", "TASK": "Task", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Anna avainsana...", "DELETE": "Poista avainsana", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "suodattimet", + "TITLE": "Suodattimet", "INPUT_PLACEHOLDER": "Aihe tai viittaus", "TITLE_ACTION_FILTER_BUTTON": "hae", - "BREADCRUMB_TITLE": "takaisin kategorioihin", - "BREADCRUMB_FILTERS": "Suodattimet", - "BREADCRUMB_STATUS": "tila" + "INPUT_SEARCH_PLACEHOLDER": "Otsikko tai viittaus", + "TITLE_ACTION_SEARCH": "Hae", + "ACTION_SAVE_CUSTOM_FILTER": "tallenna omaksi suodattimeksi", + "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", + "CATEGORIES": { + "TYPE": "Tyyppi", + "STATUS": "Tila", + "SEVERITY": "Vakavuus", + "PRIORITIES": "Kiireellisyydet", + "TAGS": "Avainsanat", + "ASSIGNED_TO": "Tekijä", + "CREATED_BY": "Luoja", + "CUSTOM_FILTERS": "Omat suodattimet" + }, + "CONFIRM_DELETE": { + "TITLE": "Poista oma suodatin", + "MESSAGE": "oma suodatin '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Päätason otsikko", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Merkintätavan ohjeet" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Kierrokset", "VIEW_SPRINTS": "Katso kierroksia", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Watching", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Tekijä puuttuu" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Ääniä", + "NAME": "Nimi", + "PROJECT": "Projekti", + "SPRINT": "Kierros", + "ASSIGNED_TO": "Assigned", + "STATUS": "Tila", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Suljettu", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", "PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Muokkaa arvoa", - "TITLE_ACTION_DELETE_VALUE": "Poista arvo" + "TITLE_ACTION_DELETE_VALUE": "Poista arvo", + "TITLE_ACTION_DELETE_TAG": "Poista avainsana" }, "HELP": "Tarvitsetko apua? Katso tukisivuilta.", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modulit", "ENABLE": "Aktivoi", "DISABLE": "Passivoi", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Odottavat", "BACKLOG_DESCRIPTION": "Hallinnoi käyttäjätarinoita: järjestele ja priorisoi työtä.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Jos muutata CSV-datan URLia, edellien lakkaa toimimasta. Oletko varma?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "käyttäjätarinoiden raportit", "SECTION_TITLE_TASK": "tehtävien raportit", "SECTION_TITLE_ISSUE": "pyyntöjen raportit", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Omat kentät", "SUBTITLE": "Määritele omia kenttiä käyttäjätarinoihin, tehtäviin ja pyytöihin", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Käyttäjätarinoiden omat kentät", "US_ADD": "Lisää käyttäjätarinoihin oma kenttä", "TASK_DESCRIPTION": "Tehtävien omat kentät", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Tila", "SUBTITLE": "Määrittele tilat joiden kautta käyttäjätarinasi, tehtäväsi ja pyyntösi kulkevat", - "US_TITLE": "Kt tilat", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Tehtävien tilat", "ISSUE_TITLE": "Pyyntöjen tilat" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Avainsanat", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Lisää avainsana", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "kutsu sähköpostiin {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Oletukset pisteiden valintaan", - "LABEL_US": "Oletukset käyttäjätarinoiden tiloiksi", "LABEL_TASK_STATUS": "Oletukset tehtävien tilaksi", - "LABEL_PRIORITY": "Oletus arvo tärkeyden valiintaan", - "LABEL_SEVERITY": "Oletukset vakavuudeksi", "LABEL_ISSUE_TYPE": "Oletukset pyyntöjen tyypeiksi", - "LABEL_ISSUE_STATUS": "Oletukset pyyntöjen statuksiksi" + "LABEL_ISSUE_STATUS": "Oletukset pyyntöjen statuksiksi", + "LABEL_PRIORITY": "Oletus arvo tärkeyden valiintaan", + "LABEL_SEVERITY": "Oletukset vakavuudeksi" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Anna uuden tilan nimi" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projektit", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Tehtävät", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Vapaaehtoinen) Lisää oma kuvaus kutsuusi uusille jäsenille ;-)", "PLACEHOLDER_TYPE_EMAIL": "Anna sähköposti", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Tämä Kt oon luotu täältä: ", "GO_TO_EXTERNAL_REFERENCE": "Palaa alkuun", "BLOCKED": "Tämä käyttäjätarina on suljettu", - "PREVIOUS": "edellinen käyttäjätarina", - "NEXT": "seuraava käyttäjätarina", "TITLE_DELETE_ACTION": "Poista käyttäjätarina", "LIGHTBOX_TITLE_BLOKING_US": "Meitä estää", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tehtyä tehtävää", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "kierroksen järjestys", "KANBAN_ORDER": "kanban järjestys", "TASKBOARD_ORDER": "Tehtävätaulun järjestys", - "US_ORDER": "kt järjestys" + "US_ORDER": "kt järjestys", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Suodattimet", "REMOVE": "Poista suodattimet", "HIDE": "Piilota suodattimet", - "SHOW": "Näytä suodattimet", - "FILTER_CATEGORY_STATUS": "Tila", - "FILTER_CATEGORY_TAGS": "Avainsanat" + "SHOW": "Näytä suodattimet" }, "SPRINTS": { "TITLE": "KIERROKSET", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Tämä tehtävä on luotu", "TITLE_LINK_GO_ORIGIN": "Siirry käyttäjätarinaan", "BLOCKED": "Tämä tehtävä on suljettu", - "PREVIOUS": "edellinen tehtävä", - "NEXT": "seuraava tehtävä", "TITLE_DELETE_ACTION": "Poista tehtävä", "LIGHTBOX_TITLE_BLOKING_TASK": "Estävä tehtävä", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ UUSI PYYNTÖ", "ACTION_PROMOTE_TO_US": "Liitä käyttäjätarinaan", - "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", "PROMOTED": "Tämä pyyntö on liitetty Kthen:", "EXTERNAL_REFERENCE": "Tämä pyyntö on luotu täältä:", "GO_TO_EXTERNAL_REFERENCE": "Palaa alkuun", "BLOCKED": "Tämä pyyntö on estetty", - "TITLE_PREVIOUS_ISSUE": "edellinen pyyntö", - "TITLE_NEXT_ISSUE": "seuraava pyyntö", "ACTION_DELETE": "Poista pyyntö", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Estävä pyyntö", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Liitä tämä pyyntö uuteen käyttäjätarinaan", "MESSAGE": "Haluatko varmasti lisätä uuden käyttäjätarinan tästä pyynnöstä?" }, - "FILTERS": { - "TITLE": "Suodattimet", - "INPUT_SEARCH_PLACEHOLDER": "Otsikko tai viittaus", - "TITLE_ACTION_SEARCH": "Hae", - "ACTION_SAVE_CUSTOM_FILTER": "tallenna omaksi suodattimeksi", - "BREADCRUMB": "Suodattimet", - "TITLE_BREADCRUMB": "Suodattimet", - "CATEGORIES": { - "TYPE": "Tyyppi", - "STATUS": "Tila", - "SEVERITY": "Vakavuus", - "PRIORITIES": "Tärkeydet", - "TAGS": "Avainsanat", - "ASSIGNED_TO": "Tekijä", - "CREATED_BY": "Luoja", - "CUSTOM_FILTERS": "Omat suodattimet" - }, - "CONFIRM_DELETE": { - "TITLE": "Poista oma suodatin", - "MESSAGE": "oma suodatin '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tyyppi", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Search - {{projectName}}", "PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Käyttäjätarinat", "FILTER_ISSUES": "Pyynnöt", "FILTER_TASKS": "Tehtävät", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} created the project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}", "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} has commented in the US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} has commented in the issue {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} has commented in the task {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} has a new member", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}", diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 7040e1b2..dfbff49f 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un élément par ligne...", "NEW_BULK": "Nouvel ajout en bloc", "RELATED_TASKS": "Tâches associées", + "PREVIOUS": "Précédent", + "NEXT": "Suivant", "LOGOUT": "Déconnexion", "EXTERNAL_USER": "un utilisateur externe", "GENERIC_ERROR": "L'un de nos Oompa Loompas dit {{error}}.", @@ -43,8 +45,13 @@ "TEAM_REQUIREMENT": "Un besoin projet est un besoin qui est nécessaire au projet mais qui ne doit avoir aucun impact pour le client", "OWNER": "Propriétaire du Projet", "CAPSLOCK_WARNING": "Attention ! Vous utilisez des majuscules dans un champ qui est sensible à la casse.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Êtes-vous sûr de vouloir fermer le mode Édition ?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Souvenez-vous que si vous fermez le mode édition sans enregistrer, toutes vos modifications seront perdues", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Affecter à", + "EDIT": "Modifier la carte" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Cette valeur semble être invalide.", "TYPE_EMAIL": "Cette valeur devrait être une adresse courriel valide.", @@ -115,6 +122,7 @@ "USER_STORY": "Récit utilisateur", "TASK": "Tâche", "ISSUE": "Ticket", + "EPIC": "Épopée", "TAGS": { "PLACEHOLDER": "Taggez moi !", "DELETE": "Supprimer le mot-clé", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Souvenez-vous que toutes les valeurs de ce champ personnalisé vont être effacées.\nEtes-vous sûr de vouloir continuer ?" }, "FILTERS": { - "TITLE": "filtres", + "TITLE": "Filtres", "INPUT_PLACEHOLDER": "Objet ou référence", "TITLE_ACTION_FILTER_BUTTON": "recherche", - "BREADCRUMB_TITLE": "retour aux catégories", - "BREADCRUMB_FILTERS": "Filtres", - "BREADCRUMB_STATUS": "état" + "INPUT_SEARCH_PLACEHOLDER": "Objet ou réf.", + "TITLE_ACTION_SEARCH": "Rechercher", + "ACTION_SAVE_CUSTOM_FILTER": "sauvegarder en tant que filtre personnalisé", + "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Statut", + "SEVERITY": "Gravité", + "PRIORITIES": "Priorités", + "TAGS": "Mots-clés", + "ASSIGNED_TO": "Affecté à", + "CREATED_BY": "Créé par", + "CUSTOM_FILTERS": "Filtres personnalisés" + }, + "CONFIRM_DELETE": { + "TITLE": "Supprime le filtre personnalisé", + "MESSAGE": "le filtre personnalisé '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Premier niveau de titre", @@ -227,11 +250,19 @@ "CODE_BLOCK_SAMPLE_TEXT": "Votre texte ici…", "PREVIEW_BUTTON": "Aperçu", "EDIT_BUTTON": "Modifier", - "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", - "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP": "Joindre des fichiers en glissant et déposant ceux-ci sur la zone de texte ci-dessus.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Enregistrez d'abord si vous voulez joindre des fichiers en glissant et déposant ceux-ci sur la zone de texte ci-dessus.", "MARKDOWN_HELP": "Aide sur la syntaxe Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Voir les sprints", @@ -244,7 +275,7 @@ "VIEW_USER_STORIES": "Afficher les récits utilisateur", "ADD_USER_STORIES": "Ajouter des récits utilisateur", "MODIFY_USER_STORIES": "Modifier les récits utilisateur", - "COMMENT_USER_STORIES": "Comment user stories", + "COMMENT_USER_STORIES": "Commenter les histoires utilisateur", "DELETE_USER_STORIES": "Supprimer des récits utilisateur" }, "TASKS": { @@ -252,7 +283,7 @@ "VIEW_TASKS": "Voir les tâches", "ADD_TASKS": "Ajouter des tâches", "MODIFY_TASKS": "Modifier des tâches", - "COMMENT_TASKS": "Comment tasks", + "COMMENT_TASKS": "Commenter les tâches", "DELETE_TASKS": "Supprimer des tâches" }, "ISSUES": { @@ -260,7 +291,7 @@ "VIEW_ISSUES": "Voir les tickets", "ADD_ISSUES": "Ajouter des tickets", "MODIFY_ISSUES": "Modifier des tickets", - "COMMENT_ISSUES": "Comment issues", + "COMMENT_ISSUES": "Commenter les tickets", "DELETE_ISSUES": "Supprimer des tickets" }, "WIKI": { @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Suivi", "DASHBOARD": "Tableau de bord des projets" }, + "EPICS": { + "TITLE": "ÉPOPÉES", + "SECTION_NAME": "Epics", + "EPIC": "ÉPOPÉE", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ AJOUTER ÉPOPÉE", + "UNASSIGNED": "Non affecté" + }, + "EMPTY": { + "TITLE": "On dirait que vous n'avez pas encore créé d'épopée", + "EXPLANATION": "Créer une épopée pour avoir un niveau au dessus des histoires utilisateur. Les épopées peuvent contenir ou être composées d'histoires utilisateur dans ce projet, ou dans tout autre projet.", + "HELP": "En savoir plus sur les épopées" + }, + "TABLE": { + "VOTES": "Votes", + "NAME": "Nom", + "PROJECT": "Projet", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Affecté", + "STATUS": "Statut", + "PROGRESS": "Avancement", + "VIEW_OPTIONS": "Voir les options" + }, + "CREATE": { + "TITLE": "Nouvelle épopée", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Besoin projet", + "CLIENT_REQUIREMENT": "Besoin client", + "BLOCKED": "Bloqué", + "BLOCKED_NOTE_PLACEHOLDER": "Pourquoi cette épopée est-elle bloquée ?", + "CREATE_EPIC": "Créer une épopée" + } + }, "PROJECTS": { "PAGE_TITLE": "Mes projets - Taiga", "PAGE_DESCRIPTION": "Une liste de tous vos projets, vous pouvez la trier ou en créer une nouvelle.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Modifier la valeur", - "TITLE_ACTION_DELETE_VALUE": "Supprimer" + "TITLE_ACTION_DELETE_VALUE": "Supprimer", + "TITLE_ACTION_DELETE_TAG": "Supprimer le mot-clé" }, "HELP": "Avez-vous besoin d'aide ? Consultez notre page d'assistance !", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modules", "ENABLE": "Activer", "DISABLE": "Désactiver", + "EPICS": "Épopées", + "EPICS_DESCRIPTION": "Visualiser et gérer les aspects les plus stratégiques de votre projet", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gérez votre récits utilisateur pour garder une vue organisée des travaux à venir et priorisés.", "NUMBER_SPRINTS": "Nombre prévu de sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Vous êtes sur le point de changer l'url d'accès aux données CSV. L'url précédente sera désactivée. Êtes-vous sûr ?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "rapports des récits utilisateur", "SECTION_TITLE_TASK": "rapports des tâches", "SECTION_TITLE_ISSUE": "Rapports des tickets", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Champs personnalisés", "SUBTITLE": "Spécifiez les champs personnalisés de vos récits utilisateur, tâches et tickets", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Champs personnalisés des récits utilisateur", "US_ADD": "Ajouter un champ personnalisé dans les récits utilisateur", "TASK_DESCRIPTION": "Champs personnalisés de tâches", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Statut", "SUBTITLE": "Spécifiez les statuts que vont prendre vos récits utilisateur, tâches et tickets", - "US_TITLE": "Statuts des RU", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Statuts des tâches", "ISSUE_TITLE": "Statuts des Tickets" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Mots-clés", - "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "SUBTITLE": "Voir et modifier la couleur de vos mots-clés", + "EMPTY": "Il n'y a pas de mots-clés pour l'instant", + "EMPTY_SEARCH": "Il semble qu'aucun résultat ne correspond à vos critères de recherche", + "ACTION_ADD": "Ajouter un mot-clé", + "NEW_TAG": "Nouveau mot-clé", + "MIXING_HELP_TEXT": "Sélectionnez les mots-clés que vous voulez fusionner", + "MIXING_MERGE": "Fusionner des mots-clés", + "SELECTED": "Sélectionné" }, "ROLES": { "PAGE_TITLE": "Rôles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "l'invitation à {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valeur par défaut pour la sélection des points", - "LABEL_US": "Valeur par défaut pour la sélection du récit utilisateur", "LABEL_TASK_STATUS": "Valeur par défaut pour la sélection de l'état des tâches", - "LABEL_PRIORITY": "Valeur par défaut de la sélection des priorités", - "LABEL_SEVERITY": "Valeur par défaut pour le sélecteur de gravité", "LABEL_ISSUE_TYPE": "Valeur par défaut pour le sélecteur de type", - "LABEL_ISSUE_STATUS": "Valeur par défaut pour le sélecteur de statut de bug" + "LABEL_ISSUE_STATUS": "Valeur par défaut pour le sélecteur de statut de bug", + "LABEL_PRIORITY": "Valeur par défaut de la sélection des priorités", + "LABEL_SEVERITY": "Valeur par défaut pour le sélecteur de gravité" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Entrez le nom du nouvel état" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Voir tous", "FILTER_TYPE_PROJECTS": "Projets", "FILTER_TYPE_PROJECT_TITLES": "Voir uniquement les projets", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Récits", "FILTER_TYPE_USER_STORIES_TITLES": "Voir uniquement les user stories", "FILTER_TYPE_TASKS": "Tâches", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optionnel) Ajoutez un texte personnalisé à l'invitation. Dites quelque chose de gentil à vos nouveaux membres ;-)", "PLACEHOLDER_TYPE_EMAIL": "Saisissez une adresse courriel", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres.
Si vous désirez augmenter cette limite, merci de contacter l'administrateur.", - "LIMIT_USERS_WARNING_MESSAGE": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Malheureusement, ce projet ne peut pas être laissé sans propriétaire", @@ -985,6 +1066,25 @@ "BUTTON": "Demander à ce membre du projet de devenir le nouveau propriétaire" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Récit utilisateur {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{userStoryStatus }}. Achevé {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} sur {{userStoryTotalTasks}} tâches fermées). Points : {{userStoryPoints}}. Description : {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Ce récit utilisateur a été créé depuis", "GO_TO_EXTERNAL_REFERENCE": "Allez à l'origine", "BLOCKED": "Ce récit utilisateur est bloqué", - "PREVIOUS": "récit utilisateur précédent", - "NEXT": "récit utilisateur suivant", "TITLE_DELETE_ACTION": "Supprimer le récit utilisateur", "LIGHTBOX_TITLE_BLOKING_US": "Bloque le RU", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tâches complétées", @@ -1012,10 +1110,10 @@ "PUBLISH_INFO": "Plus d'informations", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", + "EDIT_LINK": "Modifier le lien", + "CLOSE": "Fermer", "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TITLE": "Avez-vous besoin de quelqu'un pour cette tâche ?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, "FIELDS": { @@ -1025,16 +1123,16 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Commentaire supprimé par {{user}}", "TITLE": "Commentaires", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} commentaires", + "ORDER": "Trier", + "OLDER_FIRST": "Plus ancien d'abord", + "RECENT_FIRST": "Plus récent d'abord", "COMMENT": "Commentaire", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", - "SHOW_HISTORY": "View historic", + "EDIT_COMMENT": "Modifier le commentaire", + "EDITED_COMMENT": "Modifié :", + "SHOW_HISTORY": "Voir l'historique", "TYPE_NEW_COMMENT": "Entrez un nouveau commentaire ici", "SHOW_DELETED": "Afficher le commentaire supprimé", "HIDE_DELETED": "Cacher le commentaire supprimé", @@ -1049,20 +1147,20 @@ "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Montrer les entrées précédentes ({{showMore}} plus)", "TITLE": "Activité", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} activités", "REMOVED": "supprimé", "ADDED": "ajouté", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", + "TAGS_ADDED": "Mots-clés ajoutés :", + "TAGS_REMOVED": "Mots-clés supprimés", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", - "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", - "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", - "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", + "NEW_ATTACHMENT": "Nouvelle pièce jointe", + "DELETED_ATTACHMENT": "Pièces jointes supprimées :", + "UPDATED_ATTACHMENT": "Pièces jointes mises à jour ({{filename}}) :", + "CREATED_CUSTOM_ATTRIBUTE": "Attribut personnalisé créé", + "UPDATED_CUSTOM_ATTRIBUTE": "Attribut personnalisé mis à jour", "SIZE_CHANGE": "A fait {size, plural, one{une modification} other{# modifications}}", - "BECAME_DEPRECATED": "became deprecated", - "BECAME_UNDEPRECATED": "became undeprecated", + "BECAME_DEPRECATED": "devenu obsolète", + "BECAME_UNDEPRECATED": "n'est plus obsolète", "TEAM_REQUIREMENT": "Besoin projet", "CLIENT_REQUIREMENT": "Besoin client", "BLOCKED": "Bloqué", @@ -1097,13 +1195,14 @@ "TAGS": "mots-clés", "ATTACHMENTS": "pièces jointes", "IS_DEPRECATED": "est obsolète", - "IS_NOT_DEPRECATED": "is not deprecated", + "IS_NOT_DEPRECATED": "n'est pas obsolète", "ORDER": "classement", "BACKLOG_ORDER": "classement du backlog", "SPRINT_ORDER": "classement du sprint", "KANBAN_ORDER": "Classement du Kanban", "TASKBOARD_ORDER": "trier le tableau des tâches", - "US_ORDER": "classement des récits utilisateur" + "US_ORDER": "classement des récits utilisateur", + "COLOR": "color" } }, "BACKLOG": { @@ -1156,7 +1255,7 @@ "IOCAINE_DOSES": "doses
de iocaine", "SHOW_STATISTICS_TITLE": "Afficher les statistiques", "TOGGLE_BAKLOG_GRAPH": "Afficher/masquer le graphique d'avancement", - "POINTS_PER_ROLE": "Points per role" + "POINTS_PER_ROLE": "Points par rôle" }, "SUMMARY": { "PROJECT_POINTS": "projet
points", @@ -1169,9 +1268,7 @@ "TITLE": "Filtres", "REMOVE": "Supprimer les filtres", "HIDE": "Cacher les filtres", - "SHOW": "Afficher les filtres", - "FILTER_CATEGORY_STATUS": "Etat", - "FILTER_CATEGORY_TAGS": "Mots-clés" + "SHOW": "Afficher les filtres" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Cette tâche a été créée par", "TITLE_LINK_GO_ORIGIN": "Aller au récit utilisateur", "BLOCKED": "Cette tâche est bloquée", - "PREVIOUS": "tâche précédente", - "NEXT": "tâche suivante", "TITLE_DELETE_ACTION": "Supprimer une tâche", "LIGHTBOX_TITLE_BLOKING_TASK": "Tâche bloquante", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NOUVEAU TICKET", "ACTION_PROMOTE_TO_US": "Promouvoir en récit utilisateur", - "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", "PROMOTED": "Le ticket a été promu en récit utilisateur", "EXTERNAL_REFERENCE": "Ce ticket a été créé à partir de", "GO_TO_EXTERNAL_REFERENCE": "Aller à l'origine", "BLOCKED": "Ce bug est bloqué", - "TITLE_PREVIOUS_ISSUE": "ticket précédent", - "TITLE_NEXT_ISSUE": "ticket suivant", "ACTION_DELETE": "Supprimer le ticket", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Ticket bloquant", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promouvoir ce ticket en nouveau récit utilisateur", "MESSAGE": "Êtes-vous sure de vouloir créer un nouveau récit utilisateur à partir de ce ticket ?" }, - "FILTERS": { - "TITLE": "Filtres", - "INPUT_SEARCH_PLACEHOLDER": "Objet ou réf.", - "TITLE_ACTION_SEARCH": "Rechercher", - "ACTION_SAVE_CUSTOM_FILTER": "sauvegarder en tant que filtre personnalisé", - "BREADCRUMB": "Filtres", - "TITLE_BREADCRUMB": "Filtres", - "CATEGORIES": { - "TYPE": "Type", - "STATUS": "Statut", - "SEVERITY": "Gravité", - "PRIORITIES": "Priorités", - "TAGS": "Mots-clés", - "ASSIGNED_TO": "Affecté à", - "CREATED_BY": "Créé par", - "CUSTOM_FILTERS": "Filtres personnalisés" - }, - "CONFIRM_DELETE": { - "TITLE": "Supprime le filtre personnalisé", - "MESSAGE": "le filtre personnalisé '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Type", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Chercher - {{projectName}}", "PAGE_DESCRIPTION": "Chercher tout, récits utilisateurs, tickets, tâches ou pages de wiki, dans le projet {{projectName}} : {{projectDescription}}", + "FILTER_EPICS": "Épopées", "FILTER_USER_STORIES": "Récits utilisateur", "FILTER_ISSUES": "Tickets", "FILTER_TASKS": "Tâches", @@ -1464,24 +1535,24 @@ "DELETE_LIGHTBOX_TITLE": "Supprimer la page Wiki", "DELETE_LINK_TITLE": "Supprimer un lien Wiki", "NAVIGATION": { - "HOME": "Main Page", - "SECTION_NAME": "BOOKMARKS", - "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "HOME": "Page principale", + "SECTION_NAME": "SIGNETS", + "ACTION_ADD_LINK": "Ajouter un signet", + "ALL_PAGES": "Toutes les pages wiki" }, "SUMMARY": { "TIMES_EDITED": "modifications", "LAST_EDIT": "dernière
modification", "LAST_MODIFICATION": "dernière modification" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Toutes les pages", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", - "EDITIONS": "Editions", + "TITLE": "Titre", + "EDITIONS": "Modifications", "CREATED": "Créé le", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "MODIFIED": "Modifié", + "CREATOR": "Créateur", + "LAST_MODIFIER": "Dernier modificateur" } }, "HINTS": { @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} a créé une nouvelle tâche {{obj_name}} dans le projet {{project_name}} pour le récit utilisateur {{us_name}}", "WIKI_CREATED": "{{username}} a créé une nouvelle page wiki {{obj_name}} dans {{project_name}}", "MILESTONE_CREATED": "{{username}} a créé un nouveau sprint {{obj_name}} dans {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} a créé le projet {{project_name}}", "MILESTONE_UPDATED": "{{username}} a mis à jour le sprint {{obj_name}}", "US_UPDATED": "{{username}} a mis à jour l'attribut «{{field_name}}» du récit utilisateur {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} a mis à jour l'attribut «{{field_name}}» de la tâche {{obj_name}} qui appartient au récit utilisateur {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} a mis l'attribut \"{{field_name}}\" à {{new_value}} pour la tâche {{obj_name}} appartenant au récit utilisateur {{us_name}}", "WIKI_UPDATED": "{{username}} a mis à jour la page wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} a commenté le récit utilisateur {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} a commenté le ticket {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} a commenté la tâche {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} a un nouveau membre", "US_ADDED_MILESTONE": "{{username}} a ajouté le récit utilisateur {{obj_name}} à {{sprint_name}}", "US_MOVED": "{{username}} a déplacé le RU {{obj_name}}", diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index cbf22204..05561ca3 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un elemento per riga...", "NEW_BULK": "Nuovo inserimento nel carico", "RELATED_TASKS": "Compiti correlati", + "PREVIOUS": "Previous", + "NEXT": "Successivo", "LOGOUT": "Esci", "EXTERNAL_USER": "un utente esterno", "GENERIC_ERROR": "C'é uno dei nostri Oompa Loompa che dice {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Questo valore non è valido.", "TYPE_EMAIL": "Questo valore dovrebbe corrispondere ad una mail valida", @@ -115,6 +122,7 @@ "USER_STORY": "Storia utente", "TASK": "Compito", "ISSUE": "Problema", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Eccomi! taggami", "DELETE": "Elimina tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Ricorda che tutti i valori in questo campo custom andranno persi.\nSei sicuro di voler continuare?" }, "FILTERS": { - "TITLE": "filtri", + "TITLE": "Filtri", "INPUT_PLACEHOLDER": "Soggetto o referenza", "TITLE_ACTION_FILTER_BUTTON": "cerca", - "BREADCRUMB_TITLE": "Indietro alle categorie", - "BREADCRUMB_FILTERS": "Filtri", - "BREADCRUMB_STATUS": "stato" + "INPUT_SEARCH_PLACEHOLDER": "Soggetto o referenza", + "TITLE_ACTION_SEARCH": "Cerca", + "ACTION_SAVE_CUSTOM_FILTER": "salva come filtro personalizzato", + "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Stato", + "SEVERITY": "Gravità", + "PRIORITIES": "Priorità", + "TAGS": "Tag", + "ASSIGNED_TO": "Assegnato a", + "CREATED_BY": "Creato da", + "CUSTOM_FILTERS": "Filtri personalizzati" + }, + "CONFIRM_DELETE": { + "TITLE": "Elimina il filtro personalizzato", + "MESSAGE": "Il filtro personalizzato '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Intestazione di primo livello", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Aiuto per la sintassi Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Vedi gli sprint", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Osservando", "DASHBOARD": "Dashboard Progetti" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Non assegnato" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Voti", + "NAME": "Nome", + "PROJECT": "Progetto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Stato", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloccato", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "I miei progetti - Taiga", "PAGE_DESCRIPTION": "Una lista di tutti i tuoi progetti, la puoi riordinare o crearne una nuova.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Modifica valore", - "TITLE_ACTION_DELETE_VALUE": "Elimina valore" + "TITLE_ACTION_DELETE_VALUE": "Elimina valore", + "TITLE_ACTION_DELETE_TAG": "Elimina tag" }, "HELP": "Hai bisogno di aiuto? Controlla la nostra pagina di supporto!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduli", "ENABLE": "Abilita", "DISABLE": "Disabilita", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Amministra le storie degli utenti per mantenere una visione organizzata dei lavori in arrivo e di quelli ad alta priorità ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Stai per modificare l'url di accesso al CSV. il precedente url verrá disabilitato. Sei sicuro?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Report delle storie utente", "SECTION_TITLE_TASK": "Analisi dei compiti", "SECTION_TITLE_ISSUE": "Report criticitá", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Campi Personalizzati", "SUBTITLE": "Specifica i campi personalizzati per le tue Storie Utente, compiti e problemi", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Campi personalizzati delle storie utente", "US_ADD": "Aggiungi un campo personalizzato nelle storie utente", "TASK_DESCRIPTION": "Campi personalizzati dei Compiti", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Stato", "SUBTITLE": "Specifica lo stato delle storie utente, i compiti e i problemi saranno affrontati", - "US_TITLE": "Stato delle storie utente", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Stato dei compiti", "ISSUE_TITLE": "Stato dei problemi" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tag", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Aggiungi un tag", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Ruoli - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "L'invito a {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valore standard per punti di selezione", - "LABEL_US": "Valore predefinito per la selezione di stati delle storie utente", "LABEL_TASK_STATUS": "Valore predefinito per la selezione degli stati del compito", - "LABEL_PRIORITY": "Valore predefinito per la selezione prioritaria", - "LABEL_SEVERITY": "Valore predefinito per la selezione di rigore", "LABEL_ISSUE_TYPE": "Valore predefinito per il tipo di selezione del problema", - "LABEL_ISSUE_STATUS": "Valore predefinito per la selezione di stato del problema" + "LABEL_ISSUE_STATUS": "Valore predefinito per la selezione di stato del problema", + "LABEL_PRIORITY": "Valore predefinito per la selezione prioritaria", + "LABEL_SEVERITY": "Valore predefinito per la selezione di rigore" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Scrivi un nome per il nuovo status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostra tutto", "FILTER_TYPE_PROJECTS": "Progetti", "FILTER_TYPE_PROJECT_TITLES": "Mostra solo i progetti", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Resoconti", "FILTER_TYPE_USER_STORIES_TITLES": "Mostra solo resoconti utente", "FILTER_TYPE_TASKS": "Compiti", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(facoltativo) aggiungi un testo personalizzato all'invito. Di qualcosa di simpatico ai tuoi nuovi membri ;-)", "PLACEHOLDER_TYPE_EMAIL": "Scrivi una mail", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completata per il {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} di {{userStoryTotalTasks}} tasks closed). Punti: {{userStoryPoints}}. Descrizione: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Questo US é stato creato da", "GO_TO_EXTERNAL_REFERENCE": "Ritorna all'inizio", "BLOCKED": "Questa storia utente è bloccata", - "PREVIOUS": "Storia utente precedente ", - "NEXT": "Prossima storia utente", "TITLE_DELETE_ACTION": "Elimina la storia utente", "LIGHTBOX_TITLE_BLOKING_US": "Blocco la storia utente", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} compiti completati", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "Ordine dello sprint", "KANBAN_ORDER": "ordina kanban", "TASKBOARD_ORDER": "Ordine del pannello dei compiti", - "US_ORDER": "Ordine delle storie utente" + "US_ORDER": "Ordine delle storie utente", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtri", "REMOVE": "Rimuovi filtri", "HIDE": "Nascondi Filtri", - "SHOW": "Mostra Filtri", - "FILTER_CATEGORY_STATUS": "Stato", - "FILTER_CATEGORY_TAGS": "Tag" + "SHOW": "Mostra Filtri" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Questo compito è stato creato da", "TITLE_LINK_GO_ORIGIN": "Vai alla storia utente", "BLOCKED": "Questo compito è bloccato", - "PREVIOUS": "Compito precedente", - "NEXT": "Prossimo compito", "TITLE_DELETE_ACTION": "Rimuovi compito", "LIGHTBOX_TITLE_BLOKING_TASK": "Sto bloccando il compito", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NUOVA CRITICITÁ", "ACTION_PROMOTE_TO_US": "Promuovi la storia utente", - "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", "PROMOTED": "Il problema è stato promosso a storia utente", "EXTERNAL_REFERENCE": "Questo problema è stato creato da ", "GO_TO_EXTERNAL_REFERENCE": "Ritorna all'inizio", "BLOCKED": "Questo problema è bloccato", - "TITLE_PREVIOUS_ISSUE": "problema precedente", - "TITLE_NEXT_ISSUE": "Problema successivo", "ACTION_DELETE": "Elimina problema", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Issue bloccante", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promuovi questo problema come nuova storia utente", "MESSAGE": "Sei sicuro di voler creare una nuova storia utente da questo problema?" }, - "FILTERS": { - "TITLE": "Filtri", - "INPUT_SEARCH_PLACEHOLDER": "Soggetto o referenza", - "TITLE_ACTION_SEARCH": "Cerca", - "ACTION_SAVE_CUSTOM_FILTER": "salva come filtro personalizzato", - "BREADCRUMB": "Filtri", - "TITLE_BREADCRUMB": "Filtri", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Stato", - "SEVERITY": "Gravità", - "PRIORITIES": "priorità", - "TAGS": "Tag", - "ASSIGNED_TO": "Assegna a", - "CREATED_BY": "Creato da", - "CUSTOM_FILTERS": "Filtri personalizzati" - }, - "CONFIRM_DELETE": { - "TITLE": "Elimina il filtro personalizzato", - "MESSAGE": "Il filtro personalizzato '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Cerca - {{projectName}}", "PAGE_DESCRIPTION": "Cerca storie utenti, problemi, compiti o pagine wiki, all'interno del progetto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Storie Utente", "FILTER_ISSUES": "problemi", "FILTER_TASKS": "Compiti", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creato un nuovo compito {{obj_name}} in {{project_name}} che appartiene alla storia utente {{us_name}}", "WIKI_CREATED": "{{username}} ha creato una nuova pagina wiki {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} ha creato un nuovo sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} ha creato il progetto {{project_name}}", "MILESTONE_UPDATED": "{{username}} ha aggiornato lo sprint {{obj_name}}", "US_UPDATED": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" alla storia utente {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" del compito {{obj_name}} che appartiene alla storia utente {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" del compito {{obj_name}} che appartiene alla storia utente {{us_name}} a {{new_value}}", "WIKI_UPDATED": "{{username}} ha aggiornato la pagina wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha commentato nella storia utente {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha commentato nel problema {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha commentato nel compito {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} ha un nuovo membro", "US_ADDED_MILESTONE": "{{username}} ha aggiunto la storia utente {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha spostato la storia utente {{obj_name}}", diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index fc3bd95c..d637041e 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Eén item per regel...", "NEW_BULK": "Nieuwe bulk toevoeging", "RELATED_TASKS": "Gerelateerde taken", + "PREVIOUS": "Previous", + "NEXT": "Volgende", "LOGOUT": "Afmelden", "EXTERNAL_USER": "een extern gebruiker", "GENERIC_ERROR": "Een van onze Oempa Loempa's zegt {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Deze waarde lijkt ongeldig te zijn", "TYPE_EMAIL": "Deze waarde moet een geldig emailadres bevatten", @@ -115,6 +122,7 @@ "USER_STORY": "User story", "TASK": "Taak", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Ik ben 'm! Tag me...", "DELETE": "Verwijder tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filters", + "TITLE": "Filters", "INPUT_PLACEHOLDER": "Onderwerp of referentie", "TITLE_ACTION_FILTER_BUTTON": "zoek", - "BREADCRUMB_TITLE": "terug naar categorieën", - "BREADCRUMB_FILTERS": "Filters", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Onderwerp of ref.", + "TITLE_ACTION_SEARCH": "Zoek", + "ACTION_SAVE_CUSTOM_FILTER": "Als eigen filter opslaan", + "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Status", + "SEVERITY": "Ernst", + "PRIORITIES": "Prioriteit", + "TAGS": "Tags", + "ASSIGNED_TO": "Toegewezen aan", + "CREATED_BY": "Aangemaakt door", + "CUSTOM_FILTERS": "Eigen filters" + }, + "CONFIRM_DELETE": { + "TITLE": "Verwijder eigen filter", + "MESSAGE": "de eigen filter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Eerste niveau heading", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Sprints bekijken", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Volgers", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Niet toegewezen" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Stemmen", + "NAME": "Naam", + "PROJECT": "Project", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Geblokkeerd", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mijn projecten - Taiga", "PAGE_DESCRIPTION": "Een lijst met al jouw projecten, je kunt deze herodenen of een nieuwe aanmaken.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Bewerk waarde", - "TITLE_ACTION_DELETE_VALUE": "Verwijder waarde" + "TITLE_ACTION_DELETE_VALUE": "Verwijder waarde", + "TITLE_ACTION_DELETE_TAG": "Verwijder tag" }, "HELP": "Help je hulp nodig? Bekijk onze support pagina!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modules", "ENABLE": "Inschakelen", "DISABLE": "Uitschakelen", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Organiseer je user stories om een duidelijk overzicht van aankomend en geprioritiseerd werk te behouden.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Je staat op het punt de CSV data toegang url te veranderen. De vorige url zal worden uitgeschakeld. Ben je zeker dat je ermee door wil gaan?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "user stories rapporten", "SECTION_TITLE_TASK": "taak rapporten", "SECTION_TITLE_ISSUE": "Issues rapport", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Eigen velden", "SUBTITLE": "Specifieer de aangepaste velden voor je user stories, taken en issues", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Eigen velden user stories", "US_ADD": "Voeg eigen velden toe in user stories", "TASK_DESCRIPTION": "Eigen velden taken", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specifieer de statussen waar je user stories, taken en issues door zullen gaan", - "US_TITLE": "US statussen", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Taak statussen", "ISSUE_TITLE": "Issue statussen" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Tag tovoegen", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "de uitnodiging naar {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Standaard waarde voor punten selectie", - "LABEL_US": "Standaard waarde voor US status selectie", "LABEL_TASK_STATUS": "Standaard waarde voor taak status selectie", - "LABEL_PRIORITY": "Standaard waarde voor prioriteit selectie", - "LABEL_SEVERITY": "Standaard waarde voor ernst selectie", "LABEL_ISSUE_TYPE": "Standaard waarde voor issue type selectie", - "LABEL_ISSUE_STATUS": "Standaard waarde voor issue status selectie" + "LABEL_ISSUE_STATUS": "Standaard waarde voor issue status selectie", + "LABEL_PRIORITY": "Standaard waarde voor prioriteit selectie", + "LABEL_SEVERITY": "Standaard waarde voor ernst selectie" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Geef een naam voor de nieuwe status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Alles weergeven", "FILTER_TYPE_PROJECTS": "Projecten", "FILTER_TYPE_PROJECT_TITLES": "Enkel projecten weergeven", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Verhalen", "FILTER_TYPE_USER_STORIES_TITLES": "Enkel verhalen van gebruikers weergeven", "FILTER_TYPE_TASKS": "Taken", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optioneel) Voeg een gepersonaliseerd bericht toe aan je uitnodiging. Vertel iets leuks aan je nieuwe leden ;-)", "PLACEHOLDER_TYPE_EMAIL": "Type en E-mail", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Voltooid {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} van {{userStoryTotalTasks}} taken gesloten). Punten: {{userStoryPoints}}. Omschrijving: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Deze US is aangemaakt vanaf", "GO_TO_EXTERNAL_REFERENCE": "Ga naar bron", "BLOCKED": "Deze user story is geblokkeerd", - "PREVIOUS": "Vorige user story", - "NEXT": "volgende user story", "TITLE_DELETE_ACTION": "Verwijder user story", "LIGHTBOX_TITLE_BLOKING_US": "User story blokkeren", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} taken afgewerkt", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "sprint volgorde", "KANBAN_ORDER": "kanban volgorde", "TASKBOARD_ORDER": "taakbord volgorde", - "US_ORDER": "us volgorde" + "US_ORDER": "us volgorde", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filters", "REMOVE": "Filters verwijderd", "HIDE": "Filters verbergen", - "SHOW": "Toon filters", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tags" + "SHOW": "Toon filters" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Deze taak werd aangemaakt vanaf", "TITLE_LINK_GO_ORIGIN": "Ga naar user story", "BLOCKED": "Deze taak is geblokkeerd", - "PREVIOUS": "vorige taak", - "NEXT": "volgende taak", "TITLE_DELETE_ACTION": "Verwijder taak", "LIGHTBOX_TITLE_BLOKING_TASK": "Blokkerende taak", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ nieuw probleem", "ACTION_PROMOTE_TO_US": "Promoveer tot User Story", - "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", "PROMOTED": "Dit issue is gepromoveerd tot US:", "EXTERNAL_REFERENCE": "Dit issue is aangemaakt vanaf", "GO_TO_EXTERNAL_REFERENCE": "Ga naar bron", "BLOCKED": "Dit issue is geblokkeerd", - "TITLE_PREVIOUS_ISSUE": "vorig issue", - "TITLE_NEXT_ISSUE": "volgend issue", "ACTION_DELETE": "Verwijderd issue", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blokkerend issue", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Bevorder dit issue tot een nieuwe user story", "MESSAGE": "Weet je zeker dat je een nieuw US van dit issue wilt maken?" }, - "FILTERS": { - "TITLE": "Filters", - "INPUT_SEARCH_PLACEHOLDER": "Onderwerp of ref.", - "TITLE_ACTION_SEARCH": "Zoek", - "ACTION_SAVE_CUSTOM_FILTER": "Als eigen filter opslaan", - "BREADCRUMB": "Filters", - "TITLE_BREADCRUMB": "Filters", - "CATEGORIES": { - "TYPE": "Type", - "STATUS": "Status", - "SEVERITY": "Ernst", - "PRIORITIES": "Prioriteiten", - "TAGS": "Tags", - "ASSIGNED_TO": "Toegewezen aan", - "CREATED_BY": "Aangemaakt door", - "CUSTOM_FILTERS": "Eigen filters" - }, - "CONFIRM_DELETE": { - "TITLE": "Verwijder eigen filter", - "MESSAGE": "de eigen filter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Type", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Zoek - {{projectName}}", "PAGE_DESCRIPTION": "Zoek op alles, user stories, issues, taken, wiki pagina's, in het project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User Stories", "FILTER_ISSUES": "Issues", "FILTER_TASKS": "Taken", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} heeft de nieuwe taak {{obj_name}} aangemakt in {{project_name}} die hoort bij de US {{us_name}}", "WIKI_CREATED": "{{username}} heeft een nieuwe Wiki-pagina aangemaakt {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} heeft een nieuwe sprint aangemaakt {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} heeft een nieuw project aangemaakt {{project_name}}", "MILESTONE_UPDATED": "{{username}} heeft de sprint {{obj_name}} bijgewerkt", "US_UPDATED": "{{username}} heeft de eigenschap \"{{field_name}}\" van de US {{obj_name}} bijgewerkt", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} heeft de eigenschap \"{{field_name}}\" van de taak {{obj_name}} die behoort tot de US {{us_name}} bijgewerkt", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} heeft de eigenschap \"{{field_name}}\" van de taak {{obj_name}} die behoort tot de US {{us_name}} gewijzigd naar {{new_value}}", "WIKI_UPDATED": "{{username}} heeft de wiki pagina {{obj_name}} bijgewerkt", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} heeft gereageerd op de US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} heeft gereageerd op het issue {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} heeft gereageerd op de taak {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} heeft een nieuw lid", "US_ADDED_MILESTONE": "{{username}} heeft de US {{obj_name}} toegevoegd aan {{sprint_name}}", "US_MOVED": "{{username}} heeft de user story {{obj_name}} verplaatst", diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 385461c5..fceae681 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Jedna pozycja na wiersz...", "NEW_BULK": "Nowe zbiorcze dodawanie", "RELATED_TASKS": "Zadania pokrewne", + "PREVIOUS": "Previous", + "NEXT": "Następny", "LOGOUT": "Wyloguj", "EXTERNAL_USER": "zewnętrzny użytkownik", "GENERIC_ERROR": "Umpa Lumpa mówi {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Nieprawidłowa wartość", "TYPE_EMAIL": "Podaj prawidłowy adres email.", @@ -115,6 +122,7 @@ "USER_STORY": "Historyjka użytkownika", "TASK": "Zadania", "ISSUE": "Zgłoszenie", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Otaguj mnie!...", "DELETE": "Usuń tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtry", + "TITLE": "Filtry", "INPUT_PLACEHOLDER": "Temat lub odniesienie", "TITLE_ACTION_FILTER_BUTTON": "szukaj", - "BREADCRUMB_TITLE": "wróć do kategorii", - "BREADCRUMB_FILTERS": "Filtry", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Temat lub referencja", + "TITLE_ACTION_SEARCH": "Szukaj", + "ACTION_SAVE_CUSTOM_FILTER": "zapisz jako filtr niestandardowy", + "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", + "CATEGORIES": { + "TYPE": "Typ", + "STATUS": "Statusy", + "SEVERITY": "Ważność", + "PRIORITIES": "Priorytety", + "TAGS": "Tagi", + "ASSIGNED_TO": "Przypisane do", + "CREATED_BY": "Stworzona przez", + "CUSTOM_FILTERS": "Filtry niestandardowe" + }, + "CONFIRM_DELETE": { + "TITLE": "Usuń filtr niestandardowy", + "MESSAGE": "filtr niestandardowy '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Nagłówek pierwszego poziomu", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Składnia Markdown pomoc" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprinty", "VIEW_SPRINTS": "Przeglądaj Sprinty", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Obserwujesz", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPIKI", + "SECTION_NAME": "Epics", + "EPIC": "EPIK", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+DODAJ EPIK", + "UNASSIGNED": "Nieprzypisane" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Głosy", + "NAME": "Nazwa", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Statusy", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "Nowy epik", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Wymaganie zespołu", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Zablokowane", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Moje projekty - Taiga", "PAGE_DESCRIPTION": "Lista wszystkich Twoich projektów, możesz zmieniać ich kolejność lub tworzyć nowe.", @@ -378,8 +444,8 @@ "ATTACHMENT": { "SECTION_NAME": "załączniki", "TITLE": "{{ plik }} załadowany dnia {{ data }}", - "LIST_VIEW_MODE": "List view mode", - "GALLERY_VIEW_MODE": "Gallery view mode", + "LIST_VIEW_MODE": "Tryb listy", + "GALLERY_VIEW_MODE": "Tryb galerii", "DESCRIPTION": "Wpisz krótki opis", "DEPRECATED": "(przestarzały)", "DEPRECATED_FILE": "Przestarzałe?", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Edytuj wartość", - "TITLE_ACTION_DELETE_VALUE": "Usuń wartość" + "TITLE_ACTION_DELETE_VALUE": "Usuń wartość", + "TITLE_ACTION_DELETE_TAG": "Usuń tag" }, "HELP": "Potrzebujesz pomocy? Sprawdź naszą stronę wsparcia!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduły", "ENABLE": "Włącz", "DISABLE": "Wyłącz", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Dziennik", "BACKLOG_DESCRIPTION": "Zarządzaj swoimi historyjkami użytkownika aby utrzymać zorganizowany widok i priorytety zadań", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Zamierzasz zmienić link dostępu do danych CSV. Poprzedni link będzie niedostępny. Czy jesteś pewien?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "raporty historii użytkownika", "SECTION_TITLE_TASK": "Raporty zadań", "SECTION_TITLE_ISSUE": "raporty zgłoszeń", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Własne Pola", "SUBTITLE": "Zdefiniuj własne dodatkowe pola dla historyjek użytkownika, zadań i zgłoszeń.", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Własne pola dla historyjek użytkownika", "US_ADD": "Dodaj własne pole dla historyjek użytkownika", "TASK_DESCRIPTION": "Własne pola dla zadań", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Zdefiniuj statusy dla historyjek użytkownika, zadań i zgłoszeń.", - "US_TITLE": "Statusy", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Statusy zadań", "ISSUE_TITLE": "Statusy zgłoszeń" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tagi", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Dodaj tag", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Role - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "zaproszenie do {{e-mail}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Domyślna wartość dla selektora punktów", - "LABEL_US": "Domyślna wartość dla selektora statusu historyjek użytkownika", "LABEL_TASK_STATUS": "Domyśla wartość dla selektora statusu zadań", - "LABEL_PRIORITY": "Domyślna wartość dla selektora priorytetu", - "LABEL_SEVERITY": "Domyślna wartość dla selektora ważności", "LABEL_ISSUE_TYPE": "Domyślna wartość dla selektora typu zgłoszenia", - "LABEL_ISSUE_STATUS": "Domyślna wartość dla selektora statusu zgłoszenia" + "LABEL_ISSUE_STATUS": "Domyślna wartość dla selektora statusu zgłoszenia", + "LABEL_PRIORITY": "Domyślna wartość dla selektora priorytetu", + "LABEL_SEVERITY": "Domyślna wartość dla selektora ważności" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Wpisz nazwę nowego statusu" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projekty", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Zadania", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcjonalne) Dodaj spersonalizowany tekst do zaproszenia. Napisz coś słodziachnego do nowego członka zespołu :)", "PLACEHOLDER_TYPE_EMAIL": "Wpisz Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Historyjka użytkownika {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Zakończono {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} z {{userStoryTotalTasks}} zadań). Punktów: {{userStoryPoints}}. Opis: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Ta historyjka została utworzona z", "GO_TO_EXTERNAL_REFERENCE": "Idź do źródła", "BLOCKED": "Ta historia użytkownika jest zablokowana", - "PREVIOUS": "poprzednia historia użytkownika", - "NEXT": "następna historia użytkownika", "TITLE_DELETE_ACTION": "Usuń historyjkę użytkownika", "LIGHTBOX_TITLE_BLOKING_US": "Blokuje nas", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} zadanie zakończone", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "kolejność sprintów", "KANBAN_ORDER": "kolejność kanban", "TASKBOARD_ORDER": "kolejność tablicy zadań", - "US_ORDER": "Kolejność HU" + "US_ORDER": "Kolejność HU", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtry", "REMOVE": "Usuń filtry", "HIDE": "Ukryj filtry", - "SHOW": "Pokaż filtry", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tagi" + "SHOW": "Pokaż filtry" }, "SPRINTS": { "TITLE": "SPRINTY", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Źródło tego zadania to", "TITLE_LINK_GO_ORIGIN": "Idź do historyjki użytkownika", "BLOCKED": "To zadanie jest zablokowane", - "PREVIOUS": "poprzednie zadanie", - "NEXT": "następne zadanie", "TITLE_DELETE_ACTION": "Usuń zadanie", "LIGHTBOX_TITLE_BLOKING_TASK": "Blokowanie zadania", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Zgłoszenie", "ACTION_NEW_ISSUE": "+ NOWE ZGŁOSZENIE", "ACTION_PROMOTE_TO_US": "Awansuj na historyjkę użytkownika", - "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", "PROMOTED": "To zgłoszenie zostało wypromowane na HU:", "EXTERNAL_REFERENCE": "Źródło zgłoszenia", "GO_TO_EXTERNAL_REFERENCE": "Idź do źródła", "BLOCKED": "To zgłoszenie jest zablokowane", - "TITLE_PREVIOUS_ISSUE": "poprzednie zgłoszenie", - "TITLE_NEXT_ISSUE": "następne zgłoszenie", "ACTION_DELETE": "Usuń zgłoszenie", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blokowanie zgłoszenia", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Awansuj to zgłoszenie na historyjkę użytkownika", "MESSAGE": "Jesteś pewny, że chcesz wypromować to zgłoszenie na historyjkę użytkownika?" }, - "FILTERS": { - "TITLE": "Filtry", - "INPUT_SEARCH_PLACEHOLDER": "Temat lub referencja", - "TITLE_ACTION_SEARCH": "Szukaj", - "ACTION_SAVE_CUSTOM_FILTER": "zapisz jako filtr niestandardowy", - "BREADCRUMB": "Filtry", - "TITLE_BREADCRUMB": "Filtry", - "CATEGORIES": { - "TYPE": "Typy", - "STATUS": "Statusy", - "SEVERITY": "Ważność", - "PRIORITIES": "Priorytety", - "TAGS": "Tagi", - "ASSIGNED_TO": "Przypisane do", - "CREATED_BY": "Stworzona przez", - "CUSTOM_FILTERS": "Filtry niestandardowe" - }, - "CONFIRM_DELETE": { - "TITLE": "Usuń filtr niestandardowy", - "MESSAGE": "filtr niestandardowy '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Typ", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Szukaj - {{projectName}}", "PAGE_DESCRIPTION": "Możesz przeszukiwać wszystko, historyjki użytkownika, zgłoszenia, zadania oraz strony Wiki w projekcie {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Historyjki użytkownika", "FILTER_ISSUES": "Zgłoszenia", "FILTER_TASKS": "Zadania", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "Użytkownik {{username}} utworzył nowe zadanie {{obj_name}} w projekcie {{project_name}} należące do HU {{us_name}}", "WIKI_CREATED": "Użytkownik {{username}} utworzył nową stronę Wiki {{obj_name}} w projekcie {{project_name}}", "MILESTONE_CREATED": "Użytkownik {{username}} utworzył nowy sprint {{obj_name}} w projekcie {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "Użytkownik {{username}} utworzył projekt {{project_name}}", "MILESTONE_UPDATED": "Użytkownik {{username}} zaktualizował sprint {{obj_name}}", "US_UPDATED": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} historyjki użytkownika {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} zadania {{obj_name}} należącego do HU {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} zadania {{obj_name}} należącego do HU {{us_name}} na {{new_value}}", "WIKI_UPDATED": "Użytkownik {{username}} zaktualizował stronę Wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "Użytkownik {{username}} skomentował historyjkę użytkownika {{obj_name}}", "NEW_COMMENT_ISSUE": "Użytkownik {{username}} skomentował zgłoszenie {{obj_name}}", "NEW_COMMENT_TASK": "Użytkownik {{username}} skomentował zadanie {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "Projekt {{project_name}} ma nowego członka", "US_ADDED_MILESTONE": "Użytkownik{{username}} dodał HU {{obj_name}} do {{sprint_name}}", "US_MOVED": "{{username}} przeniósł historyjkę użytkownika {{obj_name}}", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 97becb3d..7c2d54b4 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Um item por linha...", "NEW_BULK": "Nova inserção em lote", "RELATED_TASKS": "Tarefas relacionadas", + "PREVIOUS": "Anterior", + "NEXT": "Próximo", "LOGOUT": "Sair", "EXTERNAL_USER": "um usuário externo", "GENERIC_ERROR": "Um Oompa Loompas disse {{error}}.", @@ -43,8 +45,13 @@ "TEAM_REQUIREMENT": "Requisito de time é um requisito que deve existir no projeto, mas que não deve ter nenhum custo para o cliente.", "OWNER": "Dono do Projeto", "CAPSLOCK_WARNING": "Seja cuidadoso! Você está escrevendo em letras maiúsculas e esse campo é case sensitive, ou seja, trata com distinção as letras maiúsculas das minúsculas.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Você tem certeza que quer fechar o modo de edição?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Lembre-se que se você fechar o modo de edição sem salvar, todas as mudanças serão perdidas", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece ser inválido.", "TYPE_EMAIL": "Este valor deve ser um e-mail válido.", @@ -69,7 +76,7 @@ "MAX_CHECK": "Você deve selecionar %s escolhas ou menos.", "RANGE_CHECK": "Você deve selecionar entre %s e %s escolhas.", "EQUAL_TO": "Esse valor deveria ser o mesmo.", - "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", + "LINEWIDTH": "Talvez uma ou mais linhas estejam muito grandes. Tente usar menos de %s caracteres.", "PIKADAY": "Formato de data inválido, por favor, use DD MMM YYYY (exemplo: 23 Mar 1984)" }, "PICKERDATE": { @@ -115,6 +122,7 @@ "USER_STORY": "História de usuário", "TASK": "Tarefa", "ISSUE": "Problema", + "EPIC": "Épico", "TAGS": { "PLACEHOLDER": "Adicionar tags...", "DELETE": "Apagar tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtros", + "TITLE": "Filtros", "INPUT_PLACEHOLDER": "Assunto ou referência", "TITLE_ACTION_FILTER_BUTTON": "procurar", - "BREADCRUMB_TITLE": "voltar para categorias", - "BREADCRUMB_FILTERS": "Filtros", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Assunto ou ref", + "TITLE_ACTION_SEARCH": "Procurar", + "ACTION_SAVE_CUSTOM_FILTER": "salve como filtro personalizado", + "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Status", + "SEVERITY": "Gravidade", + "PRIORITIES": "Prioridades", + "TAGS": "Tags", + "ASSIGNED_TO": "Atribuído a", + "CREATED_BY": "Criado por", + "CUSTOM_FILTERS": "Filtros personalizados" + }, + "CONFIRM_DELETE": { + "TITLE": "Apagar filtro personalizado", + "MESSAGE": "O filtro personalizado '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Primeira caixa de cabeçalho", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ajuda de sintaxe markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Ver sprints", @@ -244,7 +275,7 @@ "VIEW_USER_STORIES": "Ver histórias de usuários", "ADD_USER_STORIES": "Adicionar histórias de usuários", "MODIFY_USER_STORIES": "Modificar histórias de usuários", - "COMMENT_USER_STORIES": "Comment user stories", + "COMMENT_USER_STORIES": "Comentar histórias de usuário", "DELETE_USER_STORIES": "Apagar histórias de usuários" }, "TASKS": { @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observando", "DASHBOARD": "Painel de Projetos" }, + "EPICS": { + "TITLE": "ÉPICOS", + "SECTION_NAME": "Epics", + "EPIC": "ÉPICO", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADICIONAR ÉPICO", + "UNASSIGNED": "Não-atribuído" + }, + "EMPTY": { + "TITLE": "Parece que você ainda não criou nenhum épico ", + "EXPLANATION": "Crie um épico para ter um nível superior de Histórias de Usuário. Épicos podem conter ou serem compostos por Histórias de Usuário deste ou de qualquer outro projeto", + "HELP": "Saiba mais sobre épicos" + }, + "TABLE": { + "VOTES": "Votos", + "NAME": "Nome", + "PROJECT": "Projeto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progresso", + "VIEW_OPTIONS": "Ver opções" + }, + "CREATE": { + "TITLE": "Novo Épico", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloqueado", + "BLOCKED_NOTE_PLACEHOLDER": "Por que esse épico está bloqueado?", + "CREATE_EPIC": "Criar épico" + } + }, "PROJECTS": { "PAGE_TITLE": "Meus projetos - Taiga", "PAGE_DESCRIPTION": "Uma lista com todos os seus projetos, você pode reorganizá-los ou criar um novo.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Apagar valor" + "TITLE_ACTION_DELETE_VALUE": "Apagar valor", + "TITLE_ACTION_DELETE_TAG": "Apagar tag" }, "HELP": "Você precisa de ajuda? Verifique nossa pagina de suporte!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modulos", "ENABLE": "Habilitar", "DISABLE": "Desabilitar", + "EPICS": "Épicos", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gerencie suas histórias de usuários para manter uma visualização organizada de trabalhos futuros e priorizados.", "NUMBER_SPRINTS": "Número de sprints esperadas", @@ -480,7 +549,7 @@ "ACTION_USE_DEFAULT_LOGO": "Usar imagem padrão", "MAX_PRIVATE_PROJECTS": "Você atingiu o número máximo de projetos privados permitidos para seu plano atual.", "MAX_PRIVATE_PROJECTS_MEMBERS": "O número máximo de membros para projetos privados foi excedido.", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", + "MAX_PUBLIC_PROJECTS": "Infelizmente você atingiu o número máximo de projetos público permitidos para seu plano atual", "MAX_PUBLIC_PROJECTS_MEMBERS": "Este projeto atingiu o seu limite atual de membros para projetos públicos", "PROJECT_OWNER": "Dono do projeto", "REQUEST_OWNERSHIP": "Solicitar propriedade", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Você está prestes a alterar a url de acesso a dados do CSV. A URL anterior será desabilitada. Você está certo disso?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Relatórios de histórias de usuários", "SECTION_TITLE_TASK": "relatórios de tarefas", "SECTION_TITLE_ISSUE": "relatórios de problemas", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Campos Personalizados", "SUBTITLE": "Especificar campos personalizados para histórias de usuários, tarefas e problemas", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Campos personalizados das histórias de usuários", "US_ADD": "Adicionar campo personalizado nas histórias de usuários", "TASK_DESCRIPTION": "Campos personalizados das Tarefas", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Especifique os status pelos quais suas histórias de usuários, tarefas e problemas passarão", - "US_TITLE": "Estados das Histórias de Usuários", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estados da Tarefa", "ISSUE_TITLE": "Estados do problema" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "Ver e editar as cores das stories de seu usuário", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Atualmente não há tags", - "EMPTY_SEARCH": "Parece que nada foi encontrado com os critérios de sua pesquisa." + "EMPTY_SEARCH": "Parece que nada foi encontrado com os critérios de sua pesquisa.", + "ACTION_ADD": "Adicionar tag", + "NEW_TAG": "Nova tag", + "MIXING_HELP_TEXT": "Selecione as tags que você quer mesclar", + "MIXING_MERGE": "Mesclar Tags", + "SELECTED": "Selecionado" }, "ROLES": { "PAGE_TITLE": "Funções - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "o convite para {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valores padrões para o seletor de pontos", - "LABEL_US": "Valor padrão para seletor de status da História de Usuário", "LABEL_TASK_STATUS": "Valor padrão para seletor de status de tarefa", - "LABEL_PRIORITY": "Valor padão para seletor de prioridade", - "LABEL_SEVERITY": "Valor padrão para seletor de gravidade", "LABEL_ISSUE_TYPE": "Valor padrão para seletor de tipo de problema ", - "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de problema" + "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de problema", + "LABEL_PRIORITY": "Valor padão para seletor de prioridade", + "LABEL_SEVERITY": "Valor padrão para seletor de gravidade" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Digite um nome para o novo status" @@ -710,7 +789,7 @@ "TITLE": "Serviços" }, "PROJECT_TRANSFER": { - "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", + "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Você gostaria de se tornar o novo dono do projeto?", "PRIVATE": "Privado", "ACCEPTED_PROJECT_OWNERNSHIP": "Parabéns! Você é o proprietário do projeto agora.", "REJECTED_PROJECT_OWNERNSHIP": "OK. Entraremos em contato com o atual dono do projeto.", @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tudo", "FILTER_TYPE_PROJECTS": "Projetos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar somente projetos", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Histórias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar apenas histórias de usuários", "FILTER_TYPE_TASKS": "Tarefas", @@ -853,7 +934,7 @@ "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) é muito pesado para nossos Oompa Loompas, tente algo menor que ({{maxFileSize}})", "SYNC_SUCCESS": "Seu projeto foi importado com sucesso", "PROJECT_RESTRICTIONS": { - "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", + "PROJECT_MEMBERS_DESC": "O projeto que você está tentando importar tem {{members}} membros e infelizmente seu plano atual tem um limite máximo de {{max_memberships}} membros por projeto. Se você deseja aumentar este limite entre em contato com o administrador.", "PRIVATE_PROJECTS_SPACE": { "TITLE": "Unfortunately, your current plan does not allow for additional private projects", "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." @@ -870,7 +951,7 @@ }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { "TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project", - "DESC": "The project that you are trying to import is private and has {{members}} members." + "DESC": "O projeto que você está tentando importar é privado e tem {{members}} membros." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Adicione uma mensagem de texto ao convite. Diga algo animador para os novos membros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Digite um Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Infelizmente, este projeto não pode ter mais do que {{maxMembers}} membros.
Se você gostaria de aumentar o limite atual, por favor contate o administrador.", - "LIMIT_USERS_WARNING_MESSAGE": "Infelizmente este projeto não pode ter mais do que {{maxMembers}} membros." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Infelizmente, este projeto não pode ficar sem um dono", @@ -985,6 +1066,25 @@ "BUTTON": "Pedir a este membro do projeto para se tornar o novo dono do projeto" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - História de Usuário {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completos {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tarefas encerradas). Pontos: {{userStoryPoints}}. Descrição: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Esta História de Usuário foi criada de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", "BLOCKED": "Esta história de usuário está bloqueada", - "PREVIOUS": "história de usuário anterior", - "NEXT": "proxima história de usuário", "TITLE_DELETE_ACTION": "Apagar história de usuário", "LIGHTBOX_TITLE_BLOKING_US": "História de usuário bloqueadora", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tarefas completas", @@ -1010,11 +1108,11 @@ "TRIBE": { "PUBLISH": "Publicar como Gig no Taiga Tribe", "PUBLISH_INFO": "Mais informações", - "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISH_TITLE": "Mais informações sobre como publicar na Tribo Taiga", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Editar link", "CLOSE": "Fechar", - "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "SYNCHRONIZE_LINK": "sincronizar com a Tribo Taiga", "PUBLISH_MORE_INFO_TITLE": "Você precisa de alguém para esta tarefa?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, @@ -1062,7 +1160,7 @@ "UPDATED_CUSTOM_ATTRIBUTE": "atributo personalizado atualizado", "SIZE_CHANGE": "Feito {size, plural, one{one change} other{# changes}}", "BECAME_DEPRECATED": "foi depreciado", - "BECAME_UNDEPRECATED": "became undeprecated", + "BECAME_UNDEPRECATED": "foi depreciado", "TEAM_REQUIREMENT": "Requisitos da Equipe", "CLIENT_REQUIREMENT": "Requisitos do Cliente", "BLOCKED": "Bloqueado", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "ordem de sprint ", "KANBAN_ORDER": "pedido kanban", "TASKBOARD_ORDER": "Ordem de quadro de tarefa", - "US_ORDER": "ordem da história de usuário" + "US_ORDER": "ordem da história de usuário", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtros", "REMOVE": "Remover filtros", "HIDE": "Esconder Filtros", - "SHOW": "Mostrar Filtros", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tags" + "SHOW": "Mostrar Filtros" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Essa tarefa foi criada a partir de", "TITLE_LINK_GO_ORIGIN": "Ir para história de usuário", "BLOCKED": "Esta tarefa está bloqueada", - "PREVIOUS": "tarefa anterior", - "NEXT": "nova tarefa", "TITLE_DELETE_ACTION": "Apagar Tarefa", "LIGHTBOX_TITLE_BLOKING_TASK": "Tarefa bloqueadora", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", - "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", "PROMOTED": "Esse problema foi promovido para história de usuário", "EXTERNAL_REFERENCE": "Esse problema foi criado a partir de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", "BLOCKED": "Esse apontamento está bloqueado", - "TITLE_PREVIOUS_ISSUE": "problema anterior", - "TITLE_NEXT_ISSUE": "próximo problema", "ACTION_DELETE": "Problema apagado", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Problema que está bloqueando", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promover esse problema para nova história de usuário", "MESSAGE": "Você tem certeza que deseja criar uma nova História de Usuário a partir desse problema?" }, - "FILTERS": { - "TITLE": "Filtros", - "INPUT_SEARCH_PLACEHOLDER": "Assunto ou ref", - "TITLE_ACTION_SEARCH": "Procurar", - "ACTION_SAVE_CUSTOM_FILTER": "salve como filtro personalizado", - "BREADCRUMB": "Filtros", - "TITLE_BREADCRUMB": "Filtros", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Status", - "SEVERITY": "Gravidade", - "PRIORITIES": "Prioridades", - "TAGS": "Tags", - "ASSIGNED_TO": "Atribuído a", - "CREATED_BY": "Criado por", - "CUSTOM_FILTERS": "Filtros personalizados" - }, - "CONFIRM_DELETE": { - "TITLE": "Apagar filtro personalizado", - "MESSAGE": "O filtro personalizado '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", "PAGE_DESCRIPTION": "Busque qualquer coisa, histórias de usuários, problemas, tarefas, ou páginas da wiki, no projeto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Épicos", "FILTER_USER_STORIES": "Histórias de Usuários", "FILTER_ISSUES": "Problemas", "FILTER_TASKS": "Tarefas", @@ -1467,7 +1538,7 @@ "HOME": "Página principal", "SECTION_NAME": "BOOKMARKS", "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "ALL_PAGES": "Todas as páginas wiki" }, "SUMMARY": { "TIMES_EDITED": "vezes
editadas", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} criou nova tarefa {{obj_name}} em {{project_name}} que pertence a História de Usuário {{us_name}}", "WIKI_CREATED": "{{username}} criou uma página wiki {{obj_name}} em {{project_name}}", "MILESTONE_CREATED": "{{username}} criou uma nova sprint {{obj_name}} em {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} criou o projeto {{project_name}}", "MILESTONE_UPDATED": "{{username}} atualizou a sprint {{obj_name}}", "US_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da História de Usuário {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}} para {{new_value}}", "WIKI_UPDATED": "{{username}} atualizou a página wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} comentou na História de Usuário {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} comentou no problema {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} comentou na tarefa {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} tem um membro novo", "US_ADDED_MILESTONE": "{{username}} adicionou a História de Usuário {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} moveu a História de Usuário {{obj_name}}", @@ -1613,7 +1690,7 @@ "VIEW_MORE": "Visualizar mais", "RECRUITING": "Este projeto esta procurando colaboradores", "FEATURED": "Featured Projects", - "EMPTY": "There are no projects to show with this search criteria.
Try again!", + "EMPTY": "Não há projetos para exibir sob esse critério de pesquisa.
Tente novamente!", "FILTERS": { "ALL": "Tudo", "KANBAN": "Kanban", diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 496c408e..5d5c6c53 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Один объект на строку...", "NEW_BULK": "Добавить пакетно", "RELATED_TASKS": "Связанные задачи", + "PREVIOUS": "Предыдущий", + "NEXT": "Следующий", "LOGOUT": "Выйти", "EXTERNAL_USER": "внешний пользователь", "GENERIC_ERROR": "Один из Умпа-Лумп говорит {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Кажется, это значение некорректно.", "TYPE_EMAIL": "Значение должно быть корректной электронной почтой.", @@ -115,6 +122,7 @@ "USER_STORY": "Пользовательская история", "TASK": "Задача", "ISSUE": "Запрос", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Назначьте тэг", "DELETE": "Удалить тэг", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "фильтры", + "TITLE": "Фильтры", "INPUT_PLACEHOLDER": "Название ссылки", "TITLE_ACTION_FILTER_BUTTON": "поиск", - "BREADCRUMB_TITLE": "назад к категориям", - "BREADCRUMB_FILTERS": "Фильтры", - "BREADCRUMB_STATUS": "cтатус" + "INPUT_SEARCH_PLACEHOLDER": "Название ссылки", + "TITLE_ACTION_SEARCH": "Поиск", + "ACTION_SAVE_CUSTOM_FILTER": "сохранить как специальный фильтр", + "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", + "CATEGORIES": { + "TYPE": "Тип", + "STATUS": "Статус", + "SEVERITY": "Важность", + "PRIORITIES": "Приоритеты", + "TAGS": "Тэги", + "ASSIGNED_TO": "Назначено", + "CREATED_BY": "Создано", + "CUSTOM_FILTERS": "Собственные фильтры" + }, + "CONFIRM_DELETE": { + "TITLE": "Удалить фильтр", + "MESSAGE": "специальный фильтр '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Заголовок первого уровня", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Помощь по синтаксису Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Спринты", "VIEW_SPRINTS": "Посмотреть спринты", @@ -363,13 +394,48 @@ "HOME": { "PAGE_TITLE": "Домашняя страница - Taiga", "PAGE_DESCRIPTION": "Главная страница Taiga с вашими основными проектами, назначенными и отслеживаемыми ПИ, задачами и запросами", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Тут кажется пусто, не правда ли? Начинайте использовать Taiga и вы увидите здесь истории, задачи и запросы над которыми вы сейчас работаете.", "EMPTY_WATCHING": "Следите за пользовательскими историями, задачами, запросами в ваших проектах и будьте уведомлены об изменениях :)", "EMPTY_PROJECT_LIST": "У Вас пока нет проектов", "WORKING_ON_SECTION": "Работает над", "WATCHING_SECTION": "Отслеживаемые", "DASHBOARD": "Рабочий стол с проектами" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Не назначено" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Голоса", + "NAME": "Имя", + "PROJECT": "Проект", + "SPRINT": "Спринт", + "ASSIGNED_TO": "Assigned", + "STATUS": "Статус", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Заблокирован", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Мои проекты", "PAGE_DESCRIPTION": "Список Ваших проектов, отсортируйте их или создайте новый.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Изменить значение", - "TITLE_ACTION_DELETE_VALUE": "Удалить значение" + "TITLE_ACTION_DELETE_VALUE": "Удалить значение", + "TITLE_ACTION_DELETE_TAG": "Удалить тэг" }, "HELP": "Вам нужна помощь? Проверьте нашу страницу техподдержки!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Модули", "ENABLE": "Включить", "DISABLE": "Выключить", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Список задач", "BACKLOG_DESCRIPTION": "Управляйте пользовательскими историями, чтобы поддерживать организованное видение важных и приоритетных задач.", "NUMBER_SPRINTS": "Ожидаемое количество спринтов", @@ -478,8 +547,8 @@ "LOGO_HELP": "Изображение будет отмасштабировано до 80x80px.", "CHANGE_LOGO": "Изменить лого", "ACTION_USE_DEFAULT_LOGO": "Использовать картинку по умолчанию", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", + "MAX_PRIVATE_PROJECTS": "Вы достигли максимального числа приватных проектов которое разрешено вашим планом.", + "MAX_PRIVATE_PROJECTS_MEMBERS": "Максимальное количество участников в приватном проекте достигло лимита", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", "PROJECT_OWNER": "Владелец проекта", @@ -489,7 +558,7 @@ "REQUEST_OWNERSHIP_BUTTON": "Запрос", "REQUEST_OWNERSHIP_SUCCESS": "Мы уведомим владельца проекта", "CHANGE_OWNER": "Сменить владельца", - "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", + "CHANGE_OWNER_SUCCESS_TITLE": "ОК, ваш запрос был отправлен!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, "REPORTS": { @@ -501,18 +570,21 @@ "REGENERATE_SUBTITLE": "Вы собираетесь изменить ссылку доступа к данным CSV. Прежний вариант ссылки перестанет работать. Вы уверены?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Отчёты по пользовательским историям", "SECTION_TITLE_TASK": "отчёты о задачах", "SECTION_TITLE_ISSUE": "отчёты о запросах", "DOWNLOAD": "Скачать CSV", "URL_FIELD_PLACEHOLDER": "Упс, забыли пароль?", - "TITLE_REGENERATE_URL": " Сделать CSV ссылку ещё раз", + "TITLE_REGENERATE_URL": "Сделать CSV ссылку ещё раз", "ACTION_GENERATE_URL": "Сгенерировать ссылку", "ACTION_REGENERATE": "Создать заново" }, "CUSTOM_FIELDS": { "TITLE": "Пользовательские поля", "SUBTITLE": "Укажите специальные поля для ваших пользовательских историй, задач и запросов", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Специальные поля для пользовательских историй", "US_ADD": "Добавить специальное поле для пользовательских историй", "TASK_DESCRIPTION": "Специальные поля задач", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Статус", "SUBTITLE": "Укажите, какие статусы будут принимать ваши пользовательские истории, задачи и запросы", - "US_TITLE": "Статусы ПИ", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Статус задач", "ISSUE_TITLE": "Статусы запроса" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Тэги", - "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "SUBTITLE": "Просмотреть и изменить цвет ваших тэгов", + "EMPTY": "В данный момент тэги отсутствуют", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Добавить тэг", + "NEW_TAG": "Новый тэг", + "MIXING_HELP_TEXT": "Выберите тэги которые вы хотели бы объединить", + "MIXING_MERGE": "Объединить Тэги", + "SELECTED": "Выбранные" }, "ROLES": { "PAGE_TITLE": "Роли - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "приглашение на {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Значения по умолчанию для выбора очков", - "LABEL_US": "Значение по умолчанию для статуса ПИ", "LABEL_TASK_STATUS": "Значение по умолчанию для статуса задачи", - "LABEL_PRIORITY": "Значение по умолчанию для выбора приоритета", - "LABEL_SEVERITY": "Значение важности по умолчанию", "LABEL_ISSUE_TYPE": "Значение по умолчанию для типа запроса", - "LABEL_ISSUE_STATUS": "Значение по умолчанию для статуса запроса" + "LABEL_ISSUE_STATUS": "Значение по умолчанию для статуса запроса", + "LABEL_PRIORITY": "Значение по умолчанию для выбора приоритета", + "LABEL_SEVERITY": "Значение важности по умолчанию" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Укажите название для нового статуса" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Показать все", "FILTER_TYPE_PROJECTS": "Проекты", "FILTER_TYPE_PROJECT_TITLES": "Показать только проекты", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Истории", "FILTER_TYPE_USER_STORIES_TITLES": "Показывать только пользовательские истории", "FILTER_TYPE_TASKS": "Задачи", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Необязательно) Добавьте персональный текст в приглашение. Скажите что-нибудь приятное вашим новым участникам ;-)", "PLACEHOLDER_TYPE_EMAIL": "Введите электронную почту", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Предложить участнику проекта стать его новым владельцем" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Пользовательская История {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{userStoryStatus }}. Выполнено {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} из {{userStoryTotalTasks}} задач). Очки: {{userStoryPoints}}. Описание: {{userStoryDescription}}", @@ -998,9 +1098,7 @@ "TITLE_LINK_GO_TO_ISSUE": "Перейти к запросу", "EXTERNAL_REFERENCE": "Эта ПИ была создана из:", "GO_TO_EXTERNAL_REFERENCE": "Перейти в начало", - "BLOCKED": "Эта пользовательская история заблокирована ", - "PREVIOUS": "предыдущая пользовательская история", - "NEXT": "следующая пользовательская история", + "BLOCKED": "Эта пользовательская история заблокирована", "TITLE_DELETE_ACTION": "Удалить пользовательскую историю", "LIGHTBOX_TITLE_BLOKING_US": "Блокирующая ПИ", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} задач выполнено", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "порядок спринтов", "KANBAN_ORDER": "порядок kanban", "TASKBOARD_ORDER": "порядок панели задач", - "US_ORDER": "порядок ПИ" + "US_ORDER": "порядок ПИ", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Фильтры", "REMOVE": "Сбросить фильтры", "HIDE": "Спрятать фильтры", - "SHOW": "Показать фильтры", - "FILTER_CATEGORY_STATUS": "Статус", - "FILTER_CATEGORY_TAGS": "Тэги" + "SHOW": "Показать фильтры" }, "SPRINTS": { "TITLE": "СПРИНТЫ", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Эта задача была создана из", "TITLE_LINK_GO_ORIGIN": "Перейти к пользовательской истории", "BLOCKED": "Эта задача заблокирована", - "PREVIOUS": "предыдущая задача", - "NEXT": "следующая задача", "TITLE_DELETE_ACTION": "Удалить задачу", "LIGHTBOX_TITLE_BLOKING_TASK": "Блокирующее задание", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Запрос", "ACTION_NEW_ISSUE": "+НОВЫЙ ЗАПРОС", "ACTION_PROMOTE_TO_US": "Повысить до пользовательской истории", - "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", "PROMOTED": "Этот запрос был переделан в ПИ:", "EXTERNAL_REFERENCE": "Этот запрос был создан из", "GO_TO_EXTERNAL_REFERENCE": "Перейти в начало", "BLOCKED": "Этот запрос заблокирована", - "TITLE_PREVIOUS_ISSUE": "предыдущий запрос", - "TITLE_NEXT_ISSUE": "следующий запрос", "ACTION_DELETE": "Удалить запрос", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Блокирующий запрос", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Превратить этот запрос в новую пользовательскую историю", "MESSAGE": "Вы уверены, что хотите создать новую ПИ из этого запроса?" }, - "FILTERS": { - "TITLE": "Фильтры", - "INPUT_SEARCH_PLACEHOLDER": "Название ссылки", - "TITLE_ACTION_SEARCH": "Поиск", - "ACTION_SAVE_CUSTOM_FILTER": "сохранить как специальный фильтр", - "BREADCRUMB": "Фильтры", - "TITLE_BREADCRUMB": "Фильтры", - "CATEGORIES": { - "TYPE": "Тип", - "STATUS": "Статус", - "SEVERITY": "Важность", - "PRIORITIES": "Приоритет", - "TAGS": "Тэги", - "ASSIGNED_TO": "Назначено", - "CREATED_BY": "Создано", - "CUSTOM_FILTERS": "Собственные фильтры" - }, - "CONFIRM_DELETE": { - "TITLE": "Удалить фильтр", - "MESSAGE": "специальный фильтр '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Тип", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Поиск - {{projectName}}", "PAGE_DESCRIPTION": "Ищите что угодно, пользовательские истории, задачи, запросы и вики-страницы, в проекте {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Пользовательские Истории", "FILTER_ISSUES": "Запросы", "FILTER_TASKS": "Задачи", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} создал новую задачу {{obj_name}} в {{project_name}}, которая принадлежит ПИ {{us_name}}", "WIKI_CREATED": "{{username}} создал новую вики-страницу {{obj_name}} в {{project_name}}", "MILESTONE_CREATED": "{{username}} создал новый спринт {{obj_name}} в {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} создал проект {{project_name}}", "MILESTONE_UPDATED": "{{username}} обновил спринт {{obj_name}}", "US_UPDATED": "{{username}} обновил атрибут \"{{field_name}}\" ПИ {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} изменил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} установил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}, на {{new_value}}", "WIKI_UPDATED": "{{username}} обновил вики-страницу {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} прокомментировал ПИ {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} прокомментировал запрос {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} прокомментировал задачу {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "У {{project_name}} появился новый участник", "US_ADDED_MILESTONE": "{{username}} добавил ПИ {{obj_name}} для {{sprint_name}}", "US_MOVED": "{{username}} переместил ПИ {{obj_name}}", diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 9f8a4118..61bcc031 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "En post per rad ...", "NEW_BULK": "Lägg till flera nya", "RELATED_TASKS": "Besläktade uppgifter", + "PREVIOUS": "Previous", + "NEXT": "Nästa", "LOGOUT": "Logga ut", "EXTERNAL_USER": "en extern användare", "GENERIC_ERROR": "En av våra Oompa Loompier säger {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Det här värdet är felaktigt. ", "TYPE_EMAIL": "Värdet måste vara en giltig e-postadress", @@ -115,6 +122,7 @@ "USER_STORY": "Användarhistorie", "TASK": "Uppgift", "ISSUE": "ärende", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Det är jag! Tagga mig ...", "DELETE": "Ta bort etikett", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filter", + "TITLE": "Filter", "INPUT_PLACEHOLDER": "Titel eller referens", "TITLE_ACTION_FILTER_BUTTON": "sök", - "BREADCRUMB_TITLE": "tillbaka till kategorierna", - "BREADCRUMB_FILTERS": "Filter", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Titel eller referens", + "TITLE_ACTION_SEARCH": "Sök", + "ACTION_SAVE_CUSTOM_FILTER": "spara som anpassad filter", + "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", + "CATEGORIES": { + "TYPE": "Typ", + "STATUS": "Status", + "SEVERITY": "Allvarsgrad", + "PRIORITIES": "Prioritet", + "TAGS": "Etiketter", + "ASSIGNED_TO": "Tilldelad till", + "CREATED_BY": "Skapad av", + "CUSTOM_FILTERS": "Anpassad filter" + }, + "CONFIRM_DELETE": { + "TITLE": "Ta bort anpassad filter.", + "MESSAGE": "anpassad filter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Första nivån snart klar", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Hjälp för markeringssyntax" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprintar", "VIEW_SPRINTS": "Visa sprintar", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Bevakar", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Ej tilldelad" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Röster", + "NAME": "Namn", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Blockerad", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mina projekt - Taiga", "PAGE_DESCRIPTION": "En lista med alla dina projekt som du kan organisera eller skapa ett nytt. ", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Redigera", - "TITLE_ACTION_DELETE_VALUE": "Ta bort" + "TITLE_ACTION_DELETE_VALUE": "Ta bort", + "TITLE_ACTION_DELETE_TAG": "Ta bort etikett" }, "HELP": "Behöver du hjälp? Besök hjälpsidorna!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduler", "ENABLE": "Aktivera", "DISABLE": "Avvaktivera", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Inkorg", "BACKLOG_DESCRIPTION": "Hantera dina användarhistorier för att organisera visningar av kommande och prioriterade jobb. ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Du kan ändra CSV för datalänken. Den tidigare länken tas bort. Är du säker på det? " }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "rapporter för användarhistorier", "SECTION_TITLE_TASK": "Rapport för uppgifter", "SECTION_TITLE_ISSUE": "Rapporter för ärenden", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Anpassade fält", "SUBTITLE": "Specificera anpassade fält för användarhistorier, uppgifter och ärenden. ", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Användarhistorier för anpassade fält", "US_ADD": "Lägg till ett anpassad fält i användarhistorien", "TASK_DESCRIPTION": "Anpassade fält för uppgifter", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specificera status för dina användarhistorier, uppgifter och ärenden ska ha i olika faser. ", - "US_TITLE": "US statuser", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Status för uppgifter", "ISSUE_TITLE": "Status för ärenden" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiketter", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Lägg till etikett", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "den här invitationen till {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Standardvärde för poängväljaren", - "LABEL_US": "Standardvärde för US-statusväljare", "LABEL_TASK_STATUS": "Standardvärdet för val av uppgiftsstatus", - "LABEL_PRIORITY": "Standardvärde för val av prioritet", - "LABEL_SEVERITY": "Standardvärde för val av allvarlighet", "LABEL_ISSUE_TYPE": "Standardvärde för ärendetyp-väljare", - "LABEL_ISSUE_STATUS": "Standardvärde för väljare för ärendestatus" + "LABEL_ISSUE_STATUS": "Standardvärde för väljare för ärendestatus", + "LABEL_PRIORITY": "Standardvärde för val av prioritet", + "LABEL_SEVERITY": "Standardvärde för val av allvarlighet" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Skriv ett namn för den nya statusen" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Visa alla", "FILTER_TYPE_PROJECTS": "Projekt", "FILTER_TYPE_PROJECT_TITLES": "Visa bara projekt", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Berättelser", "FILTER_TYPE_USER_STORIES_TITLES": "Visa endast användarhistorier", "FILTER_TYPE_TASKS": "Uppgift", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Valfritt) Lägg till en personlig hälsning till invitationen. Berätta något trevligt till din nya projektmedlem ;-)", "PLACEHOLDER_TYPE_EMAIL": "Skriv in en e-postadress", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Användarhistorier {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. avslutad{{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} av {{userStoryTotalTasks}} tasks closed). Poäng: {{userStoryPoints}}. Beskrivning: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Denna användarhistorien är skapat från", "GO_TO_EXTERNAL_REFERENCE": "Gå till början", "BLOCKED": "Användarhistorien är blockerad", - "PREVIOUS": "tidigare användarhistorie", - "NEXT": "nästa användarhistorie", "TITLE_DELETE_ACTION": "Ta bort användarhistorien", "LIGHTBOX_TITLE_BLOKING_US": "Blockera oss", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} uppgifter kompletta", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "sortera sprintar", "KANBAN_ORDER": "kanban-sortering", "TASKBOARD_ORDER": "Sortera uppgiftstavlan", - "US_ORDER": "sortera US" + "US_ORDER": "sortera US", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filter", "REMOVE": "Ta bort filter", "HIDE": "Dölj filter", - "SHOW": "Visa filter", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Etiketter" + "SHOW": "Visa filter" }, "SPRINTS": { "TITLE": "SPRINTAR", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Den här uppgiften är skapad från", "TITLE_LINK_GO_ORIGIN": "Gå till användarhistorie", "BLOCKED": "Uppgiften är blockerad", - "PREVIOUS": "tidigare uppgift", - "NEXT": "ny uppgift", "TITLE_DELETE_ACTION": "Ta bort uppgift", "LIGHTBOX_TITLE_BLOKING_TASK": "Blockerad uppgift", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "ärende", "ACTION_NEW_ISSUE": "+ NYTT ÄRENDE", "ACTION_PROMOTE_TO_US": "Flytta till användarhistorie", - "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", "PROMOTED": "Ärendet har flyttats till US:", "EXTERNAL_REFERENCE": "Den här uppgiften är skapat från", "GO_TO_EXTERNAL_REFERENCE": "Gå till början", "BLOCKED": "Det här ärendet är blockerad", - "TITLE_PREVIOUS_ISSUE": "tidigare ärende", - "TITLE_NEXT_ISSUE": "nästa ärende", "ACTION_DELETE": "Ta bort ärende", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blockerad ärende", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Flytta det här ärendet till en ny användarhistorie", "MESSAGE": "Är du säker på att du vill skapa en ny US från det här ärendet?" }, - "FILTERS": { - "TITLE": "Filter", - "INPUT_SEARCH_PLACEHOLDER": "Titel eller referens", - "TITLE_ACTION_SEARCH": "Sök", - "ACTION_SAVE_CUSTOM_FILTER": "spara som anpassad filter", - "BREADCRUMB": "Filter", - "TITLE_BREADCRUMB": "Filter", - "CATEGORIES": { - "TYPE": "Typ", - "STATUS": "Status", - "SEVERITY": "Allvarsgrad", - "PRIORITIES": "Prioritet", - "TAGS": "Etiketter", - "ASSIGNED_TO": "Tilldelad till", - "CREATED_BY": "Skapad av", - "CUSTOM_FILTERS": "Anpassad filter" - }, - "CONFIRM_DELETE": { - "TITLE": "Ta bort anpassad filter.", - "MESSAGE": "anpassad filter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Typ", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Sök - {{projectName}}", "PAGE_DESCRIPTION": "Sök på vad som helst, användarhistorier, uppgifter, ärenden och wiki-innehåll i projektet {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Användarhistorier", "FILTER_ISSUES": "Ärenden", "FILTER_TASKS": "Uppgift", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} har skapat en ny uppgift {{obj_name}} i {{project_name}} som hör till US {{us_name}}", "WIKI_CREATED": "{{username}} skapade en ny wiki-sida {{obj_name}} i {{project_name}}", "MILESTONE_CREATED": "{{username}} har skapad en ny sprint {{obj_name}} i {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} skapade projektet {{project_name}}", "MILESTONE_UPDATED": "{{username}} har uppdaterad sprinten {{obj_name}}", "US_UPDATED": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" i US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" för uppgiften {{obj_name}} som tillhör US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" för uppgiften {{obj_name}} som tillhör US {{us_name}} till {{new_value}}", "WIKI_UPDATED": "{{username}} har uppdaterad wiki-sidan {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} har kommenterad i {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} har kommenterad i ärendet {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} har kommenterad uppgiften {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} har en ny medlem", "US_ADDED_MILESTONE": "{{username}} har lagt till US {{obj_name}} till {{sprint_name}}", "US_MOVED": "{{username}} har flyttat US {{obj_name}}", diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 296a85f0..4e8ca811 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Her satıra bir kalem...", "NEW_BULK": "Yeni toplu ekleme", "RELATED_TASKS": "İlişkili görevler", + "PREVIOUS": "Previous", + "NEXT": "İleri", "LOGOUT": "Çıkış", "EXTERNAL_USER": "bir dış kullanıcı", "GENERIC_ERROR": "Honki ponkilerimizden biri derki; {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Bu değer geçersiz gözüküyor", "TYPE_EMAIL": "Bu değer geçerli bir e-posta adresi olmalı.", @@ -115,6 +122,7 @@ "USER_STORY": "Kullanıcı hikayesi", "TASK": "Görev", "ISSUE": "Sorun", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Ben O'yum! Etiketle beni...", "DELETE": "Etiket sil", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Bu özel alandaki tüm bilgiler silinecek.\nDevam etmek istediğinize emin misiniz?" }, "FILTERS": { - "TITLE": "filtreler", + "TITLE": "Filtreler", "INPUT_PLACEHOLDER": "Konu yada referans", "TITLE_ACTION_FILTER_BUTTON": "ara", - "BREADCRUMB_TITLE": "kategorilere dön", - "BREADCRUMB_FILTERS": "Filtreler", - "BREADCRUMB_STATUS": "durum" + "INPUT_SEARCH_PLACEHOLDER": "Konu ya da ref", + "TITLE_ACTION_SEARCH": "Ara", + "ACTION_SAVE_CUSTOM_FILTER": "özel filtre olarak kaydet", + "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", + "CATEGORIES": { + "TYPE": "Tip", + "STATUS": "Durum ", + "SEVERITY": "Önem Derecesi", + "PRIORITIES": "Öncelikler", + "TAGS": "Etiketler ", + "ASSIGNED_TO": "Atanmış", + "CREATED_BY": "Oluşturan", + "CUSTOM_FILTERS": "Özel filtreler" + }, + "CONFIRM_DELETE": { + "TITLE": "Özel filtre sil", + "MESSAGE": "'{{customFilterName}}' özel filtresi" + } }, "WYSIWYG": { "H1_BUTTON": "İlk Düzey Başlık", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown yazım kılavuzu" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Koşular", "VIEW_SPRINTS": "Koşuları gör", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "İzleniyor", "DASHBOARD": "Proje Panosu" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Atama Yok" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Oylar", + "NAME": "İsim", + "PROJECT": "Proje", + "SPRINT": "Koşu", + "ASSIGNED_TO": "Assigned", + "STATUS": "Durum ", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Engelli", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Projelerim - Taiga", "PAGE_DESCRIPTION": "Tüm projelerinizi içeren bir liste, yenide düzenle ya da yeni bir tane yarat.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Değeri düzenle", - "TITLE_ACTION_DELETE_VALUE": "Değer sil" + "TITLE_ACTION_DELETE_VALUE": "Değer sil", + "TITLE_ACTION_DELETE_TAG": "Etiket sil" }, "HELP": "Yardıma mı ihtiyacın var? Destek sayfamızı kontrol edin!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modüller", "ENABLE": "Etkinleştir", "DISABLE": "Pasifleştir", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Havuz", "BACKLOG_DESCRIPTION": "Yeni gelen ve önceliklendirilmiş işler için düzenli bir görünüm elde etmek için kullanıcı hikayelerinizi yönetin.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "CSV veri erişim linkini değiştireceksiniz. Önceki link kapatılacak. Emin misiniz?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "kullanıcı hikayeleri raporları", "SECTION_TITLE_TASK": "görevlere ait raporlar", "SECTION_TITLE_ISSUE": "sorun raporları", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Özel Alanlar", "SUBTITLE": "Hikayeleriniz, işleriniz ve sorunlarınız için özel alanları tanımlayın", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Kullanıcı hikayeleri özel alanları", "US_ADD": "Kullanıcı hikayelerine özel bir alan ekleyin", "TASK_DESCRIPTION": "Görevlere ait özel alanlar", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Durum", "SUBTITLE": "Hikayeleriniz, işleriniz ve sorunlarınızın alabileceği durumları tanımlayın", - "US_TITLE": "KH Durumları", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Görev Durumları", "ISSUE_TITLE": "Sorun Durumları" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiketler ", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Etiket ekle", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "davetiye {{email}} " }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Puan seçici için varsayılan değer", - "LABEL_US": "KH durum seçici için varsayılan değer", "LABEL_TASK_STATUS": "Görev durum seçici için varsayılan değer", - "LABEL_PRIORITY": "Önceli seçicisi için varsayılan değer", - "LABEL_SEVERITY": "Önem derecesi seçicisi için varsayılan değer", "LABEL_ISSUE_TYPE": "Sorun tipi seçici için varsayılan değer", - "LABEL_ISSUE_STATUS": "Sorun durumu seçici için varsayılan değer" + "LABEL_ISSUE_STATUS": "Sorun durumu seçici için varsayılan değer", + "LABEL_PRIORITY": "Önceli seçicisi için varsayılan değer", + "LABEL_SEVERITY": "Önem derecesi seçicisi için varsayılan değer" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Yeni durum için bir isim yaz" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Hepsini göster", "FILTER_TYPE_PROJECTS": "Projeler", "FILTER_TYPE_PROJECT_TITLES": "Sadece projeleri görüntüle", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Hikayeler", "FILTER_TYPE_USER_STORIES_TITLES": "Sadece kullanıcı hikayelerini göster", "FILTER_TYPE_TASKS": "Görevler", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opsiyonel) Davetinize kişiselleştirilmiş bir metin ekleyin. Yeni üyelerinize tatlı bir şeyler söyleyin ;-)", "PLACEHOLDER_TYPE_EMAIL": "Bir e-posta girin", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Kullanıcı Hikayesi {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{userStoryStatus }}. Tamamlanan {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Puanlar: {{userStoryPoints}}. Tanım: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Bu KH 'ni oluşturulduğu", "GO_TO_EXTERNAL_REFERENCE": "Kökenine git ", "BLOCKED": "Bu kullanıcı hikayesi engelli", - "PREVIOUS": "önceki kullanıcı hikayesi", - "NEXT": "sonraki kullanıcı hikayesi", "TITLE_DELETE_ACTION": "Kullanıcı Hikayesi Sil", "LIGHTBOX_TITLE_BLOKING_US": "Bizi engelleyen", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tamamlanan görevler", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "koşu sırası", "KANBAN_ORDER": "kanban sırası", "TASKBOARD_ORDER": "Görev panosu sırası", - "US_ORDER": "kh sırası" + "US_ORDER": "kh sırası", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtreler", "REMOVE": "Filtreleri Sil", "HIDE": "Filtreleri Gizle", - "SHOW": "Filtreleri Göster", - "FILTER_CATEGORY_STATUS": "Durum", - "FILTER_CATEGORY_TAGS": "Etiketler " + "SHOW": "Filtreleri Göster" }, "SPRINTS": { "TITLE": "KOŞULAR", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Bu görevin oluşturulduğu", "TITLE_LINK_GO_ORIGIN": "Kullanıcı hikayesine git", "BLOCKED": "Bu iş engelli", - "PREVIOUS": "önceki görev", - "NEXT": "sonraki görev", "TITLE_DELETE_ACTION": "Görev Sil", "LIGHTBOX_TITLE_BLOKING_TASK": "Engelleyen iş", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Sorun", "ACTION_NEW_ISSUE": "+ YENİ SORUN", "ACTION_PROMOTE_TO_US": "Kullanıcı Hikayesine Terfi Ettir", - "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", "PROMOTED": "Bu sorun, kullanıcı hikayesine yükseltildi:", "EXTERNAL_REFERENCE": "Bu talebin oluşturulduğu ", "GO_TO_EXTERNAL_REFERENCE": "Kökenine git", "BLOCKED": "Bu sorun engelli", - "TITLE_PREVIOUS_ISSUE": "önceki sorun", - "TITLE_NEXT_ISSUE": "sonraki sorun", "ACTION_DELETE": "Sorun sil", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Engelleyen sorun", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Bu talebi yeni bir kullanıcı hikayesi olacak şekilde terfi ettirin", "MESSAGE": "Bu sorundan yeni bir hikaye oluşturmak istediğinize emin misiniz?" }, - "FILTERS": { - "TITLE": "Filtreler", - "INPUT_SEARCH_PLACEHOLDER": "Konu ya da ref", - "TITLE_ACTION_SEARCH": "Ara", - "ACTION_SAVE_CUSTOM_FILTER": "özel filtre olarak kaydet", - "BREADCRUMB": "Filtreler", - "TITLE_BREADCRUMB": "Filtreler", - "CATEGORIES": { - "TYPE": "Tip", - "STATUS": "Durum", - "SEVERITY": "Önem Derecesi", - "PRIORITIES": "Öncelikler", - "TAGS": "Etiketler", - "ASSIGNED_TO": "Atanmış", - "CREATED_BY": "Oluşturan", - "CUSTOM_FILTERS": "Özel filtreler" - }, - "CONFIRM_DELETE": { - "TITLE": "Özel filtre sil", - "MESSAGE": "'{{customFilterName}}' özel filtresi" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tip", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Ara - {{projectName}}", "PAGE_DESCRIPTION": "Projedeki hikayeleri, sorunları, işleri, viki sayfalarını ya da herhangi bir şeyi arayın {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Kullanıcı Hikayeleri", "FILTER_ISSUES": "Sorunlar ", "FILTER_TASKS": "Görevler", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": " {{project_name}} projesinde yer alan {{us_name}} adlı KH ya ait yeni bir görev {{obj_name}}, {{username}} tarafından oluşturuldu", "WIKI_CREATED": "{{project_name}} projesindeki yeni wiki sayfası {{obj_name}}, {{username}} tarafından oluşturuldu", "MILESTONE_CREATED": "{{project_name}} projesinde {{obj_name}} koşusu, {{username}} tarafından oluşturuldu", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{project_name}} proje {{username}} tarafından oluşturuldu", "MILESTONE_UPDATED": " {{obj_name}} koşusu {{username}} tarafından güncellendi", "US_UPDATED": "{{username}} kullanıcısı, {{obj_name}} KH 'sinin \"{{field_name}}\" alanını güncelledi. ", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{us_name}} adlı KH'ye ait {{obj_name}} talebinin \"{{field_name}}\" özniteliği {{username}} tarafından güncellendi. ", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}}, {{us_name}} hikayesindeki {{obj_name}} işinin {{field_name}} özelliğini {{new_value}} olacak şekilde değiştirdi", "WIKI_UPDATED": "{{obj_name}} adlı wiki sayfası {{username}} tarafından güncellendi", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": " {{obj_name}} KH'sine {{username}} tarafından yorum yapıldı", "NEW_COMMENT_ISSUE": " {{obj_name}} talebine {{username}} tarafından yorum yapıldı", "NEW_COMMENT_TASK": " {{obj_name}} görevine {{username}} tarafından yorum yapıldı", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} projesi yeni bir üyeye sahip", "US_ADDED_MILESTONE": " {{username}}, {{sprint_name}} koşusuna {{obj_name}} hikayesini ekledi", "US_MOVED": "{{username}}, {{obj_name}} hikayesini taşıdı", diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 1ed57f83..0dd3f5af 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "一行一物 ", "NEW_BULK": "新批次插入", "RELATED_TASKS": "相關任務 ", + "PREVIOUS": "Previous", + "NEXT": "下一個", "LOGOUT": "登出", "EXTERNAL_USER": "外部使用者", "GENERIC_ERROR": "我們的系統指出{{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "該數值似乎為無效", "TYPE_EMAIL": "該電子郵件應為有效地址", @@ -115,6 +122,7 @@ "USER_STORY": "使用者故事", "TASK": "任務", "ISSUE": "問題", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "我在這裏,請標注我", "DELETE": "刪除Tag", @@ -196,9 +204,24 @@ "TITLE": "過濾器", "INPUT_PLACEHOLDER": "主旨或參考", "TITLE_ACTION_FILTER_BUTTON": "搜尋", - "BREADCRUMB_TITLE": "回到類別", - "BREADCRUMB_FILTERS": "過濾器", - "BREADCRUMB_STATUS": "狀態" + "INPUT_SEARCH_PLACEHOLDER": "主旨或參考", + "TITLE_ACTION_SEARCH": "搜尋", + "ACTION_SAVE_CUSTOM_FILTER": "儲存為客製過濾器 ", + "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", + "CATEGORIES": { + "TYPE": "類型", + "STATUS": "狀態", + "SEVERITY": "急迫性", + "PRIORITIES": "優先性", + "TAGS": "標籤", + "ASSIGNED_TO": "指派給 ", + "CREATED_BY": "由創建", + "CUSTOM_FILTERS": "客製過濾器 " + }, + "CONFIRM_DELETE": { + "TITLE": "刪除客製過濾器 ", + "MESSAGE": "預設過濾器 '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "第一層標頭 ", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown 語法協助" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "衝刺任務", "VIEW_SPRINTS": "檢視衝刺任務 ", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "觀看中", "DASHBOARD": "專案控制台" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "未指派" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "投票數", + "NAME": "名稱 ", + "PROJECT": "專案", + "SPRINT": "衝刺任務", + "ASSIGNED_TO": "Assigned", + "STATUS": "狀態", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "已封鎖", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "我的專案 - Taiga", "PAGE_DESCRIPTION": "你的專案列表,你可以記錄或創建新專案。", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "編輯數值", - "TITLE_ACTION_DELETE_VALUE": "删除值" + "TITLE_ACTION_DELETE_VALUE": "删除值", + "TITLE_ACTION_DELETE_TAG": "刪除Tag" }, "HELP": "需要幫助嗎?看看我們的支援頁面吧!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "模組", "ENABLE": "啟用", "DISABLE": "停用", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "待辦任務優先表", "BACKLOG_DESCRIPTION": "管理你的 User Story 讓接下來的及優先的工作能被有條理地檢視 ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "你將要改變CSV資料的連結網址,之前的網址將失效。你確定要這樣做嗎?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "使用者故事報告", "SECTION_TITLE_TASK": "任務報告", "SECTION_TITLE_ISSUE": "問題報告", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "客製化欄位", "SUBTITLE": "指定使用者故事,任務與問題一些客製化欄位", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "使用者客製欄位", "US_ADD": "在使用者故事中加入客制欄位", "TASK_DESCRIPTION": "任務客製化欄位", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "狀態", "SUBTITLE": "指明你的使用者故事狀態,任務以及經歷問題 ", - "US_TITLE": "使用者故事狀態", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "任務狀態", "ISSUE_TITLE": "問題狀態" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "標籤", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "新增標籤", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "角色- {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "邀請 {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "點數選擇器預設值", - "LABEL_US": "使用者故事狀態選擇器預設值", "LABEL_TASK_STATUS": "任務狀態選擇器預設值", - "LABEL_PRIORITY": "優先選擇器預設值", - "LABEL_SEVERITY": "急迫性選擇器預設值", "LABEL_ISSUE_TYPE": "問題類型選擇器預設值", - "LABEL_ISSUE_STATUS": "問題狀態選擇器預設值" + "LABEL_ISSUE_STATUS": "問題狀態選擇器預設值", + "LABEL_PRIORITY": "優先選擇器預設值", + "LABEL_SEVERITY": "急迫性選擇器預設值" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "為此新狀態命名" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "顯示全部", "FILTER_TYPE_PROJECTS": "專案", "FILTER_TYPE_PROJECT_TITLES": "只顯示專案", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "故事", "FILTER_TYPE_USER_STORIES_TITLES": "只顯視使用者故事", "FILTER_TYPE_TASKS": "任務 ", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(非必要) 加上一段私人文字在邀請信,告訴你的新成員一些好事 ;-)", "PLACEHOLDER_TYPE_EMAIL": "輸入一個電郵地址", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - 使用者故事 {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{userStoryStatus }}.已完成 {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). 點數: {{userStoryPoints}}. 描述: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "此使用者故事創造者是", "GO_TO_EXTERNAL_REFERENCE": "回到一開始", "BLOCKED": "這個使用者故事已被封鎖", - "PREVIOUS": "之前的使用者故事", - "NEXT": "下一個使用者故事", "TITLE_DELETE_ACTION": "刪除使用者故事", "LIGHTBOX_TITLE_BLOKING_US": "封鎖中的使用者故事", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} 任務完成", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "衝刺任務次序", "KANBAN_ORDER": "kanban看板次序", "TASKBOARD_ORDER": "任務板次序", - "US_ORDER": "使用者故事次序" + "US_ORDER": "使用者故事次序", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "過濾器", "REMOVE": "移除過濾器", "HIDE": "隱藏過濾器", - "SHOW": "顯示過濾器", - "FILTER_CATEGORY_STATUS": "狀態", - "FILTER_CATEGORY_TAGS": "標籤" + "SHOW": "顯示過濾器" }, "SPRINTS": { "TITLE": "衝刺任務", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "此任務創造者是", "TITLE_LINK_GO_ORIGIN": "到使用者故事", "BLOCKED": "這任務已被封鎖", - "PREVIOUS": "之前的任務 ", - "NEXT": "下一個任務 ", "TITLE_DELETE_ACTION": "刪除任務", "LIGHTBOX_TITLE_BLOKING_TASK": "封鎖中的任務 ", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "問題", "ACTION_NEW_ISSUE": "+ 新問題 ", "ACTION_PROMOTE_TO_US": "提昇到使用者故事", - "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", "PROMOTED": "此問題已提昇成使用者故事 ", "EXTERNAL_REFERENCE": "此問題的提供者是", "GO_TO_EXTERNAL_REFERENCE": "回到一開始", "BLOCKED": "這個議題已被封鎖", - "TITLE_PREVIOUS_ISSUE": "之前的問題 ", - "TITLE_NEXT_ISSUE": "下一個問題 ", "ACTION_DELETE": "删除議題 ", "LIGHTBOX_TITLE_BLOKING_ISSUE": "封鎖中的問題", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "將此問題提到使用者故事", "MESSAGE": "你確定此問題要創建一個新的使用者故事?" }, - "FILTERS": { - "TITLE": "過濾器", - "INPUT_SEARCH_PLACEHOLDER": "主旨或參考", - "TITLE_ACTION_SEARCH": "搜尋", - "ACTION_SAVE_CUSTOM_FILTER": "儲存為客製過濾器 ", - "BREADCRUMB": "過濾器", - "TITLE_BREADCRUMB": "過濾器", - "CATEGORIES": { - "TYPE": "類型", - "STATUS": "狀態", - "SEVERITY": "急迫性", - "PRIORITIES": "優先性", - "TAGS": "標籤", - "ASSIGNED_TO": "指派給 ", - "CREATED_BY": "由創建", - "CUSTOM_FILTERS": "客製過濾器 " - }, - "CONFIRM_DELETE": { - "TITLE": "刪除客製過濾器 ", - "MESSAGE": "預設過濾器 '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "類型", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "搜尋 - {{projectName}}", "PAGE_DESCRIPTION": "專案搜尋(使用者故事, 問題, 任務或維基頁等資訊) {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "使用者故事", "FILTER_ISSUES": "問題 ", "FILTER_TASKS": "任務 ", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} 創建新任務 {{obj_name}} 於 {{project_name}} ,其為{{us_name}}之使用者故事", "WIKI_CREATED": "{{username}} 創建新維基頁 {{obj_name}} 於 {{project_name}}", "MILESTONE_CREATED": "{{username}} 創建新衝刺任務 {{obj_name}} 於 {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} 創建專案 {{project_name}}", "MILESTONE_UPDATED": "{{username}}更新衝刺任務 {{obj_name}} ", "US_UPDATED": "{{username}} 已更新 {{obj_name}}使用者故事之 \"{{field_name}}\"屬性。", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} 更新了 {{obj_name}} 任務之\"{{field_name}}\" 屬性,其為 {{us_name}} 之使用者故事", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} 已更新了 {{obj_name}} 下的 {{us_name}} 使用者故事\"{{field_name}}\"屬性到{{new_value}}", "WIKI_UPDATED": "\n{{username}} 更新了維基頁{{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} 評論了 {{obj_name}}使用者故事", "NEW_COMMENT_ISSUE": "{{username}}評論了此問題 {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} 評論了此任務{{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} 有新成員", "US_ADDED_MILESTONE": "{{username}} 增加使用者故事 {{obj_name}} 給 {{sprint_name}}", "US_MOVED": "{{username}} 搬移了使用者故事 {{obj_name}}", From c5a18c6dd1df067b6920e4da9b20bf0845d46261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 12:25:57 +0200 Subject: [PATCH 100/137] Fix typo in epic permissions section in the admin panel --- app/coffee/modules/admin/roles.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index c282f7c6..11fb5f4c 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -357,7 +357,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> { key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"} { key: "add_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.ADD_EPICS"} { key: "modify_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.MODIFY_EPICS"} - { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_EPICS"} + { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.COMMENT_EPICS"} { key: "delete_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.DELETE_EPICS"} ] categories.push({ From 61d8cb60757049cffc2052d6ca1e580876e78737 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 12:43:02 +0200 Subject: [PATCH 101/137] Fixing initial on color edition in tags admin --- app/partials/includes/modules/admin/project-tags.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 61538b00..e64007c3 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -113,6 +113,7 @@ section tg-color-selector.color-column( is-color-required="false" ng-model="tag" + init-color="tag.color" on-select-color="tag.color = color" ) From 6031e19691ac09440162e05bd6ea84b47e0a2513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 12:58:26 +0200 Subject: [PATCH 102/137] Remove assigned and load indicator --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 5 ++++- .../epics/dashboard/epic-row/epic-row.directive.coffee | 2 +- app/modules/epics/dashboard/epic-row/epic-row.jade | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) 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 65a82333..76c8ca24 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -72,8 +72,11 @@ class EpicRowController @.loadingStatus = false updateAssignedTo: (member) -> - return @epicsService.updateEpicAssignedTo(@.epic, member?.id) + @.assignLoader = true + return @epicsService.updateEpicAssignedTo(@.epic, member?.id or null) .catch () => @confirm.notify('error') + .then () => + @.assignLoader = false 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 b1a6c85d..cf85a32b 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -25,7 +25,7 @@ EpicRowDirective = () -> bindToController: true, scope: { epic: '=', - column: '=', + column: '=' } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 829ecf7a..1952641c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -32,11 +32,14 @@ .sprint(ng-if="vm.column.sprint") - .assigned.e2e-assigned-tio(ng-if="vm.column.assigned") + .assigned.e2e-assigned-to( + ng-if="vm.column.assigned" + tg-loading="vm.assignLoader" + ) tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" - on-remove-assigned="vm.updateAssignedTo(null)" + on-remove-assigned="vm.updateAssignedTo()" on-assign-to="vm.updateAssignedTo(member)" tg-isolate-click ) From 60b7f4a1b9a312ec338eab270ceee18cfc540eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:01:58 +0200 Subject: [PATCH 103/137] Remove drag icon if user has no permission --- app/modules/epics/dashboard/epic-row/epic-row.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1952641c..583c6238 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -4,6 +4,7 @@ ) tg-svg.icon-drag( svg-icon="icon-drag" + ng-if="vm.canEditEpics()" ) .vote( From 3f686d4c52337d3c5f08230e7cb299ae08da10de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:03:54 +0200 Subject: [PATCH 104/137] Avoid removing name from epics list --- app/modules/epics/dashboard/epics-table/epics-table.jade | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 31d29bb0..7ab6a81b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -19,7 +19,6 @@ mixin epicSwitch(name, model) ) .name( translate="EPICS.TABLE.NAME" - ng-if="vm.column.name" ) .project( translate="EPICS.TABLE.PROJECT" @@ -52,12 +51,6 @@ mixin epicSwitch(name, model) for="epicSwitch-votes" ) +epicSwitch('switch-votes', 'vm.column.votes') - .fieldset - label.epics-table-options-vote( - translate="EPICS.TABLE.NAME" - for="switch-name" - ) - +epicSwitch('switch-name', 'vm.column.name') .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.PROJECT" From c6b5228d544c304da6d2f2798c022c96f6bb0592 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:18:08 +0200 Subject: [PATCH 105/137] Fixing color when creating tags on admin area --- .../components/color-selector/color-selector.directive.coffee | 3 ++- app/partials/includes/modules/admin/project-tags.jade | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index fc9df40a..cf5aa8f2 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -44,7 +44,8 @@ ColorSelectorDirective = ($timeout) -> .mouseenter(cancel) .mouseleave(close) - bindOnce scope, 'vm.initColor', (color) -> + scope.$watch 'vm.initColor', (color) -> + # We can't just bind once because sometimes the initial color is reset from the outside ctrl.setColor(color) return { diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index e64007c3..83b6fc78 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -8,7 +8,7 @@ section form.add-tag-container.new-value.hidden tg-color-selector.color-column( is-color-required="false" - ng-model="newValue" + init-color="newValue.color" on-select-color="newValue.color = color" ) From 5e6d29b6098dda8912800cdbbd30a8ecf1dd3e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:32:13 +0200 Subject: [PATCH 106/137] Change cursor depending on epic contents --- app/modules/epics/dashboard/epic-row/epic-row.jade | 3 ++- app/modules/epics/dashboard/epic-row/epic-row.scss | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 583c6238..7497be1c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,5 +1,6 @@ + .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, 'not-empty': vm.epic.getIn(['user_stories_counts', 'opened']) || vm.epic.getIn(['user_stories_counts', 'closed'])}" ng-click="vm.toggleUserStoryList()" ) tg-svg.icon-drag( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 7c1d7814..0f7205a0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -6,7 +6,7 @@ align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; + cursor: move; display: flex; transition: background .2s; &:hover { @@ -15,6 +15,9 @@ opacity: 1; } } + &.not-empty { + cursor: pointer; + } &.is-blocked { background: rgba($red-light, .5); } From 2c6db751aa37d60861aaac6a0015073c5baf9043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 13:40:25 +0200 Subject: [PATCH 107/137] Prevent event propagation when press ENTER in the color selector --- .../color-selector/color-selector.controller.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index a52ed21b..e183adfe 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -29,7 +29,7 @@ class ColorSelectorController checkIsColorRequired: () -> if !@.isColorRequired - @.colorList = _.dropRight(@.colorList); + @.colorList = _.dropRight(@.colorList) setColor: (color) -> @.color = @.initColor @@ -49,9 +49,9 @@ class ColorSelectorController onKeyDown: (event) -> if event.which == 13 # ENTER - event.stopPropagation() if @.color or not @.isColorRequired @.onSelectDropdownColor(@.color) + event.preventDefault() angular.module('taigaComponents').controller("ColorSelectorCtrl", ColorSelectorController) From 0023f528a994afafe211c4e9c552d0409ba3dff3 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:46:25 +0200 Subject: [PATCH 108/137] Fixing epic links in rich content --- app/coffee/modules/common/wisiwyg.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index 40f53264..15c0d689 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -374,7 +374,7 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans search: (term, callback) -> term = taiga.slugify(term) - searchTypes = ['issues', 'tasks', 'userstories'] + searchTypes = ['issues', 'tasks', 'userstories', 'epics'] searchProps = ['ref', 'subject'] filter = (item) => @@ -384,8 +384,7 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans return false cancelablePromise.abort() if cancelablePromise - - cancelablePromise = $rs.search.do($scope.projectId, term) + cancelablePromise = $rs.search.do($scope.projectId || $scope.vm.projectId, term) cancelablePromise.then (res) => # ignore wikipages if they're the only results. can't exclude them in search @@ -441,15 +440,18 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans search: (term, callback) -> term = taiga.slugify(term) - $rs.search.do($scope.projectId, term).then (res) => + $rs.search.do($scope.projectId || $scope.vm.projectId, term).then (res) => if res.count < 1 + console.log 1 callback([]) if res.count < 1 or not res.wikipages or res.wikipages.length <= 0 + console.log 2 callback([]) else callback res.wikipages.filter((page) => + console.log 3 return taiga.slugify(page['slug']).indexOf(term) >= 0 ), true From dc48e3e2c6c5506bc58c19d075dde4ca025d1ac8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:51:07 +0200 Subject: [PATCH 109/137] Removing unncesary console.log --- app/coffee/modules/common/wisiwyg.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index 15c0d689..ee143b59 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -442,16 +442,13 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans $rs.search.do($scope.projectId || $scope.vm.projectId, term).then (res) => if res.count < 1 - console.log 1 callback([]) if res.count < 1 or not res.wikipages or res.wikipages.length <= 0 - console.log 2 callback([]) else callback res.wikipages.filter((page) => - console.log 3 return taiga.slugify(page['slug']).indexOf(term) >= 0 ), true From ebe7f479d276f9a570ce9e5e6f81c9f22bbf7b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 13:51:27 +0200 Subject: [PATCH 110/137] Fix tests --- .../color-selector/color-selector.controller.spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index a8c76c80..e402b8c8 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -64,12 +64,12 @@ describe "ColorSelector", -> colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.onSelectDropdownColor = sinon.stub() - event = {which: 13, stopPropagation: sinon.stub()} + event = {which: 13, preventDefault: sinon.stub()} color = "#fabada" colorSelectorCtrl.color = color colorSelectorCtrl.onKeyDown(event) - expect(event.stopPropagation).have.been.called + expect(event.preventDefault).have.been.called expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color) From d15cc477e8fd7b0505b8a3b97cf129aa5f4379ee Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:04:08 +0200 Subject: [PATCH 111/137] Removing required on new related us form --- .../related-userstories-create.jade | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 9f549da1..84e3898c 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -49,7 +49,6 @@ a.add-button.e2e-add-userstory-button( ng-model="selectedProject" ng-change="selectProject(selectedProject)" data-required="true" - required ng-options="p.id as p.name for p in vm.projects | toMutable" id="project-selector-dropdown" ) @@ -88,20 +87,20 @@ a.add-button.e2e-add-userstory-button( ) label.e2e-bulk-creation-label(for="bulk-new-user-stories") tg-svg(svg-icon="icon-bulk") - + form.new-user-story-form .single-creation(ng-show="creationMode=='single-new-user-story'") input.e2e-new-userstory-input-text( type="text" ng-model="relatedUserstoriesText" - required + data-required="true" ) .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") textarea.e2e-new-userstories-input-textarea( ng-model="relatedUserstoriesText" - required + data-required="true" ) button.button-green.create-user-story.e2e-create-userstory-button.ng-animate-disabled( @@ -129,7 +128,7 @@ a.add-button.e2e-add-userstory-button( select.userstory.e2e-userstories-select( size="5" ng-model="selectedUserstory" - required + data-required="true" ) - var hash = "#"; option.hidden( From e8be87e16fce87189e776d629a8a74918045b092 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:17:02 +0200 Subject: [PATCH 112/137] Fixing project selected on epic related userstories edition --- .../related-userstories-create/related-userstories-create.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 84e3898c..159e594d 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -1,6 +1,6 @@ a.add-button.e2e-add-userstory-button( href="" - ng-click="showLightbox(vm.project.get('id'))" + ng-click="showLightbox(selectedProject)" ) tg-svg(svg-icon="icon-add") From be35cddd6bde66df51f221e45c0c13496df97268 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:22:02 +0200 Subject: [PATCH 113/137] Fixing assigned to activity visualization on detail views --- .../history/history/history-templates/history-assigned.jade | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/modules/history/history/history-templates/history-assigned.jade b/app/modules/history/history/history-templates/history-assigned.jade index ab57ac18..64fb23a6 100644 --- a/app/modules/history/history/history-templates/history-assigned.jade +++ b/app/modules/history/history/history-templates/history-assigned.jade @@ -3,7 +3,11 @@ translate="ACTIVITY.FIELDS.ASSIGNED_TO" ) span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + span.diff(ng-if="!vm.diff[0]" translate="ACTIVITY.VALUES.UNASSIGNED") + tg-svg( svg-icon="icon-arrow-right" ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} + span.diff(ng-if="!vm.diff[1]" translate="ACTIVITY.VALUES.UNASSIGNED") From d186463e5d49b8c834dce698b33af700c066472c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 14:27:28 +0200 Subject: [PATCH 114/137] Fix placeholder for empty epics dashboard --- app/locales/taiga/locale-en.json | 4 ++-- app/modules/epics/dashboard/epics-dashboard.jade | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 86bb1688..4fa858e9 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -414,8 +414,8 @@ "UNASSIGNED": "Unassigned" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 50b88b81..46f751a3 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -27,7 +27,7 @@ p(translate="EPICS.EMPTY.EXPLANATION") a( translate="EPICS.EMPTY.HELP" - href="#TODO: Link to Epics section in taiga-support" + href="https://tree.taiga.io/support/epics/what-is-an-epic/" target="_blank" ng-title="EPICS.EMPTY.HELP | translate" ) From 4c3710cf4f3102dc9a7828b00f2adf632e5beee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 16:05:44 +0200 Subject: [PATCH 115/137] Display placeholder if projects have no US --- app/locales/taiga/locale-en.json | 1 + .../related-userstories-create.jade | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 4fa858e9..1be0721f 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1083,6 +1083,7 @@ "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 159e594d..f5cabb13 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -110,8 +110,13 @@ a.add-button.e2e-add-userstory-button( translate="COMMON.SAVE" ng-show="relatedWithSelector=='new-user-story'" ) + + p( + ng-show="relatedWithSelector=='existing-user-story' && !vm.projectUserstories.size" + translate="EPIC.NO_USERSTORIES" + ) - fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story' && vm.projectUserstories.size") label( translate="EPIC.CHOOSE_USERSTORY" for="userstory-filter" From 4c89ff36697423094798f1109b2b012776f4524c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 16:13:00 +0200 Subject: [PATCH 116/137] Disable color selector if the user doesn\t have permission to edit epic --- .../color-selector/color-selector.controller.coffee | 10 +++++++++- .../color-selector/color-selector.directive.coffee | 3 ++- .../components/color-selector/color-selector.jade | 10 +++++++++- .../components/color-selector/color-selector.scss | 3 +++ app/partials/epic/epic-detail.jade | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index e183adfe..47838f23 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -22,11 +22,19 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController - constructor: () -> + @.$inject = [ + "tgProjectService", + ] + + constructor: (@projectService) -> @.colorList = getDefaulColorList() @.checkIsColorRequired() @.displayColorList = false + userCanChangeColor: () -> + return true if not @.requiredPerm + return @projectService.hasPermission(@.requiredPerm) + checkIsColorRequired: () -> if !@.isColorRequired @.colorList = _.dropRight(@.colorList) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index cf5aa8f2..f020e408 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -56,7 +56,8 @@ ColorSelectorDirective = ($timeout) -> bindToController: { isColorRequired: "=", onSelectColor: "&", - initColor: "=" + initColor: "=", + requiredPerm: "@" }, scope: {}, } diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 7a472f02..d4213b62 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -1,4 +1,12 @@ -.color-selector +//- Read only mode +.color-selector(ng-if="!vm.userCanChangeColor()") + .tag-color.disabled.e2e-open-color-selector( + ng-class="{'empty-color': !vm.color}" + ng-style="{'background': vm.color}" + ) + +//- Read & Edit mode +.color-selector(ng-if="vm.userCanChangeColor()") .tag-color.e2e-open-color-selector( ng-click="vm.toggleColorList()" ng-class="{'empty-color': !vm.color}" diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 6bf35ad9..3f5a9bc0 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -18,6 +18,9 @@ border-radius: 0; margin: 0; transition: background .3s ease-out; + &.disabled { + cursor: auto; + } &.empty-color { @include empty-color(34); } diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index dcaa775e..52bb688c 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -23,6 +23,7 @@ div.wrapper( is-color-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" + required-perm="modify_epic" ) tg-detail-header( item="epic" From 2389bf4785f7a1cb9408cc47d696303711e4d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 16:23:26 +0200 Subject: [PATCH 117/137] Fix tests --- .../color-selector/color-selector.controller.spec.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index e402b8c8..5fe727f6 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -23,9 +23,17 @@ describe "ColorSelector", -> colorSelectorCtrl = null mocks = {} + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide + _mockTgProjectService() + return null beforeEach -> From b7cc9e9c1e4cd5197f38d7052870a87ba3061a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 21:10:27 +0200 Subject: [PATCH 118/137] Fix related userstories view for users without edit permission --- .../related-userstories-controller.coffee | 10 +++++-- .../related-userstories.jade | 2 +- .../related-userstory-row.jade | 5 +++- .../related-userstory-row.scss | 30 ++++++++++--------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 0ab2fd1f..d2624ec6 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,12 +20,18 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgEpicsService"] + @.$inject = [ + "tgProjectService", + "tgEpicsService" + ] - constructor: (@epicsService) -> + constructor: (@projectService, @epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false + userCanSort: () -> + return @projectService.hasPermission("modify_epic") + loadRelatedUserstories: () -> @epicsService.listRelatedUserStories(@.epic) .then (userstories) => diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index 35a848b7..e1f8a79e 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -15,7 +15,7 @@ section.related-userstories ) tg-related-userstory-row.row( tg-repeat="us in vm.userstories track by us.get('id')" - ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked'), sortable: vm.userCanSort()}" userstory="us" epic="vm.epic" project="vm.project" diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index c7790332..a4d74a88 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -1,5 +1,6 @@ tg-svg.icon-drag( svg-icon="icon-drag" + tg-check-permission="modify_epic" ) .userstory-name @@ -33,7 +34,9 @@ tg-svg.icon-drag( ) .status - span.userstory-status(ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}") {{vm.userstory.getIn(['status_extra_info', 'name'])}} + span.userstory-status( + ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}" + ) {{vm.userstory.getIn(['status_extra_info', 'name'])}} .assigned-to-column figure.avatar diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss index ad3cabc3..c9fc2f32 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -2,26 +2,28 @@ tg-related-userstory-row { @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; - cursor: move; display: flex; padding: .5rem 0 .5rem .5rem; - &:hover { - background: rgba($primary-light, .05); - .userstory-settings { - opacity: 1; - transition: all .2s ease-in; + &.sortable { + cursor: move; + &:hover { + background: rgba($primary-light, .05); + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + .icon-drag { + opacity: 1; + } } .icon-drag { - opacity: 1; + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; } } - .icon-drag { - @include svg-size(.75rem); - cursor: move; - fill: $whitish; - opacity: 0; - transition: opacity .1s; - } .status { flex-shrink: 0; position: relative; From 62a20552a4aad406ccb278bbaf4239b54b9ad1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 11:06:09 +0200 Subject: [PATCH 119/137] Color selector fixes for transparent color --- .../components/color-selector/color-selector.jade | 11 ++++++----- .../components/color-selector/color-selector.scss | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index d4213b62..dda5becc 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -28,11 +28,12 @@ .display-custom-color.empty-color( ng-if="!vm.color" ) - .display-custom-color( - ng-if="vm.color" - ng-style="{'background': vm.color}" - ng-click="vm.onSelectDropdownColor(vm.color)" - ) + .display-custom-color-wrapper + .display-custom-color( + ng-if="vm.color" + ng-style="{'background': vm.color}" + ng-click="vm.onSelectDropdownColor(vm.color)" + ) input.custom-color-input( type="text" maxlength="7" diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 3f5a9bc0..98cf62fd 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -52,16 +52,20 @@ } .custom-color-selector { + align-items: center; display: flex; .custom-color-input { margin: 0; width: 100%; } + .display-custom-color-wrapper { + background: $mass-white; + margin-right: .5rem; + } .display-custom-color { @include color-selector-option; flex-shrink: 0; margin: 0; - margin-right: .5rem; &.empty-color { @include empty-color(34); cursor: default; From 2cc8b2e458d3e87cf1fccf867fe188339f62cd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 11:47:25 +0200 Subject: [PATCH 120/137] Show history section and its tabs only when it's needed --- .../history-tabs/history-tabs.directive.coffee | 3 ++- app/modules/history/history-tabs/history-tabs.jade | 4 +++- app/modules/history/history.controller.coffee | 12 +++++++++++- app/modules/history/history.jade | 6 +++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/modules/history/history-tabs/history-tabs.directive.coffee b/app/modules/history/history-tabs/history-tabs.directive.coffee index fdadb250..e7e48e74 100644 --- a/app/modules/history/history-tabs/history-tabs.directive.coffee +++ b/app/modules/history/history-tabs/history-tabs.directive.coffee @@ -20,10 +20,11 @@ module = angular.module('taigaHistory') HistoryTabsDirective = () -> - return { templateUrl:"history/history-tabs/history-tabs.html", scope: { + showCommentTab: "&", + showActivityTab: "&" onActiveComments: "&", onActiveActivities: "&", onOrderComments: "&" diff --git a/app/modules/history/history-tabs/history-tabs.jade b/app/modules/history/history-tabs/history-tabs.jade index deb458de..d9e25e30 100644 --- a/app/modules/history/history-tabs/history-tabs.jade +++ b/app/modules/history/history-tabs/history-tabs.jade @@ -1,5 +1,6 @@ nav.history-tabs a.history-tab.e2e-comments-tab( + ng-if="showCommentTab()" href="" title="{{COMMENTS.COMMENT}}" ng-click="onActiveComments()" @@ -8,6 +9,7 @@ nav.history-tabs translate-values="{comments: commentsNum}" ) a.history-tab.e2e-activity-tab( + ng-if="showActivityTab()" href="" title="Activities" ng-click="onActiveActivities()" @@ -22,7 +24,7 @@ nav.history-tabs ng-class="{'new-first': top, 'old-first': !top}" ng-if="commentsNum > 1 && activeTab" ) - + span( translate="COMMENTS.OLDER_FIRST" ng-if="onReverse" diff --git a/app/modules/history/history.controller.coffee b/app/modules/history/history.controller.coffee index c419fd64..d80a5ec0 100644 --- a/app/modules/history/history.controller.coffee +++ b/app/modules/history/history.controller.coffee @@ -24,9 +24,10 @@ class HistorySectionController "$tgResources", "$tgRepo", "$tgStorage", + "tgProjectService", ] - constructor: (@rs, @repo, @storage) -> + constructor: (@rs, @repo, @storage, @projectService) -> @.editing = null @.deleting = null @.editMode = {} @@ -49,6 +50,15 @@ class HistorySectionController @.activities = _.filter(activities, (item) -> Object.keys(item.values_diff).length > 0) @.activitiesNum = @.activities.length + showHistorySection: () -> + return @.showCommentTab() or @.showActivityTab() + + showCommentTab: () -> + return @.commentsNum > 0 or @projectService.hasPermission("comment_#{@.name}") + + showActivityTab: () -> + return @.activitiesNum > 0 + toggleEditMode: (commentId) -> @.editMode[commentId] = !@.editMode[commentId] diff --git a/app/modules/history/history.jade b/app/modules/history/history.jade index 86ee999a..eaa9af8d 100644 --- a/app/modules/history/history.jade +++ b/app/modules/history/history.jade @@ -1,5 +1,9 @@ -section.history +section.history( + ng-if="vm.showHistorySection()" +) tg-history-tabs( + show-comment-tab="vm.showCommentTab()" + show-activity-tab="vm.showActivityTab()" on-active-comments="vm.onActiveHistoryTab(true)" on-active-activities="vm.onActiveHistoryTab(false)" active-tab="vm.viewComments", From dd72cc880784232c6ffe3672c7f75ff0df8ed029 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 11:49:27 +0200 Subject: [PATCH 121/137] Hidding filters when empty --- app/coffee/modules/controllerMixins.coffee | 7 ++++++- app/coffee/modules/issues/list.coffee | 8 +++++++- app/coffee/modules/taskboard/main.coffee | 7 ++++++- app/modules/components/filter/filter.jade | 8 +++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 08d0ac50..bf0290b7 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -221,6 +221,10 @@ class UsFiltersMixin it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -266,7 +270,8 @@ class UsFiltersMixin title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", content: tags, - hideEmpty: true + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index c1172408..81b577a5 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -188,6 +188,10 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -259,7 +263,9 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi { title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", - content: tags + content: tags, + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 1a13a106..97252b9b 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -160,6 +160,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -205,7 +209,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", content: tags, - hideEmpty: true + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/modules/components/filter/filter.jade b/app/modules/components/filter/filter.jade index 05760586..db7f53b1 100644 --- a/app/modules/components/filter/filter.jade +++ b/app/modules/components/filter/filter.jade @@ -49,7 +49,9 @@ form li( ng-class="{selected: vm.isOpen(filter.dataType)}" ng-repeat="filter in vm.filters track by filter.dataType" + ng-if="!(filter.hideEmpty && filter.totalTaggedElements === 0)" ) + a.filters-cat-single.e2e-category( ng-class="{selected: vm.isOpen(filter.dataType)}" ng-click="vm.toggleFilterCategory(filter.dataType)" @@ -78,7 +80,11 @@ form span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}} span.number.e2e-filter-count(ng-if="it.count > 0") {{it.count}} - li.custom-filters.e2e-custom-filters(ng-class="{selected: vm.isOpen('custom-filter')}") + li.custom-filters.e2e-custom-filters( + ng-class="{selected: vm.isOpen('custom-filter')}" + ng-if="vm.customFilters.length > 0" + ) + a.filters-cat-single( ng-class="{selected: vm.isOpen('custom-filter')}" ng-click="vm.toggleFilterCategory('custom-filter')" From 1d9a405a768e3185b265ed8133537aa0eedeaade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 12:03:01 +0200 Subject: [PATCH 122/137] Fix broken tests --- .../related-userstories.controller.spec.coffee | 7 +++++++ app/modules/history/history.controller.spec.coffee | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 30611140..6c82137e 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -31,10 +31,17 @@ describe "RelatedUserStories", -> provide.value "tgEpicsService", mocks.tgEpicsService + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide _mockTgEpicsService() + _mockTgProjectService() return null beforeEach -> diff --git a/app/modules/history/history.controller.spec.coffee b/app/modules/history/history.controller.spec.coffee index 6194cc5c..2a97b2ca 100644 --- a/app/modules/history/history.controller.spec.coffee +++ b/app/modules/history/history.controller.spec.coffee @@ -48,12 +48,19 @@ describe "HistorySection", -> } provide.value "$tgStorage", mocks.tgStorage + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() _mockTgRepo() _mocktgStorage() + _mockTgProjectService() return null beforeEach -> From a0e95d36cae1ad69d61e00a8a8fd9d271817e74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 12:14:24 +0200 Subject: [PATCH 123/137] Show related user stories section only if it's needed --- .../related-userstories/related-userstories-controller.coffee | 3 +++ .../epics/related-userstories/related-userstories.jade | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index d2624ec6..07319495 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -29,6 +29,9 @@ class RelatedUserStoriesController @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false + showRelatedUserStoriesSection: () -> + return @projectService.hasPermission("modify_epic") or @.userstories?.legth > 0 + userCanSort: () -> return @projectService.hasPermission("modify_epic") diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index e1f8a79e..1cbbe21b 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -1,4 +1,6 @@ -section.related-userstories +section.related-userstories( + ng-if="vm.showRelatedUserStoriesSection()" +) .related-userstories-header span.related-userstories-title(translate="COMMON.RELATED_USERSTORIES") tg-related-userstories-create( From 2950020beab7b9c9d501852daee67da893a9e1e0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 12:33:57 +0200 Subject: [PATCH 124/137] Filtering by epic in backlog and kanban --- app/coffee/modules/controllerMixins.coffee | 21 +++++++++++++++++++-- app/locales/taiga/locale-en.json | 3 ++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index bf0290b7..a6c501cd 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,6 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner + loadFilters.epic = urlfilters.epic loadFilters.q = urlfilters.q return @q.all([ @@ -221,10 +222,8 @@ class UsFiltersMixin it.id = it.name return it - tagsWithAtLeastOneElement = _.filter tags, (tag) -> return tag.count > 0 - assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -239,6 +238,15 @@ class UsFiltersMixin it.name = it.full_name return it + epic = _.map data.epics, (it) -> + if it.id + it.id = it.id.toString() + it.name = "##{it.ref} #{it.subject}" + else + it.id = "null" + it.name = "Not in an epic" + + return it @.selectedFilters = [] @@ -258,6 +266,10 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) + if loadFilters.epic + selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) + @.selectedFilters = @.selectedFilters.concat(selected) + @.filterQ = loadFilters.q @.filters = [ @@ -282,6 +294,11 @@ class UsFiltersMixin title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"), dataType: "owner", content: owner + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), + dataType: "epic", + content: epic } ] diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 1be0721f..31e0225b 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -218,7 +218,8 @@ "TAGS": "Tags", "ASSIGNED_TO": "Assigned to", "CREATED_BY": "Created by", - "CUSTOM_FILTERS": "Custom filters" + "CUSTOM_FILTERS": "Custom filters", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Delete custom filter", From ce35fc3a92f05cd433d81c1c697e90a27c6d358f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 13:02:09 +0200 Subject: [PATCH 125/137] Edit color background --- app/modules/components/belong-to-epics/belong-to-epics.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e068c2c6..01e05872 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -9,7 +9,7 @@ } .belong-to-epic-pill { - background-color: $grayer; + background-color: $mass-white; border-radius: 50%; display: inline-block; height: .7rem; From 5bffce6f89d2b3e0defeb265eae188d4e8e3382b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 13:04:34 +0200 Subject: [PATCH 126/137] Filter by epics --- app/coffee/modules/controllerMixins.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index a6c501cd..5d8cef9f 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,7 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner - loadFilters.epic = urlfilters.epic + loadFilters.epics = urlfilters.epics loadFilters.q = urlfilters.q return @q.all([ @@ -238,7 +238,7 @@ class UsFiltersMixin it.name = it.full_name return it - epic = _.map data.epics, (it) -> + epics = _.map data.epics, (it) -> if it.id it.id = it.id.toString() it.name = "##{it.ref} #{it.subject}" @@ -266,8 +266,8 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) - if loadFilters.epic - selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) + if loadFilters.epics + selected = @.formatSelectedFilters("epics", epics, loadFilters.epics) @.selectedFilters = @.selectedFilters.concat(selected) @.filterQ = loadFilters.q @@ -297,8 +297,8 @@ class UsFiltersMixin }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), - dataType: "epic", - content: epic + dataType: "epics", + content: epics } ] From cf2b2b4be7dd0d5ef7bc6cb4d8112e313a458455 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 13:32:06 +0200 Subject: [PATCH 127/137] Adding darker filter --- app/coffee/modules/common/filters.coffee | 29 +++++++++++++++++++ .../belong-to-epics/belong-to-epics-pill.jade | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index 17696c55..e91de832 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -100,3 +100,32 @@ byRefFilter = ($filterFilter)-> return $filterFilter(userstories, filter) module.filter("byRef", ["filterFilter", byRefFilter]) + + +darkerFilter = -> + return (color, luminosity) -> + # validate hex string + console.log color + color = new String(color).replace(/[^0-9a-f]/gi, '') + console.log color + if color.length < 6 + color = color[0]+ color[0]+ color[1]+ color[1]+ color[2]+ color[2]; + + luminosity = luminosity || 0 + + # convert to decimal and change luminosity + newColor = "#" + c = 0 + i = 0 + black = 0 + white = 255 + # for (i = 0; i < 3; i++) + for i in [0, 1, 2] + c = parseInt(color.substr(i*2,2), 16) + c = Math.round(Math.min(Math.max(black, c + (luminosity * white)), white)).toString(16) + newColor += ("00"+c).substr(c.length) + + return newColor + + +module.filter("darker", darkerFilter) diff --git a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 2ff75ba3..fd861905 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,6 +1,6 @@ - var hash = "#"; span.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( - ng-style="{'background': epic.get('color')}" + ng-style="{'background': epic.get('color'), 'border-color': '{{ epic.get('color') | darker: -0.2 }}'}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" ) From b0395724ed6d5f945047cb05f882e00807785911 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 14:41:33 +0200 Subject: [PATCH 128/137] Removing console.log --- app/coffee/modules/common/filters.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index e91de832..c232d85d 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -105,9 +105,7 @@ module.filter("byRef", ["filterFilter", byRefFilter]) darkerFilter = -> return (color, luminosity) -> # validate hex string - console.log color color = new String(color).replace(/[^0-9a-f]/gi, '') - console.log color if color.length < 6 color = color[0]+ color[0]+ color[1]+ color[1]+ color[2]+ color[2]; From fffc14089898f64dfa101d93759a9c760515919e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 15:18:19 +0200 Subject: [PATCH 129/137] Fixing pills when drag and drop --- .../belong-to-epics/belong-to-epics.directive.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 39eae9fb..91ffe19e 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -22,8 +22,9 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> link = (scope, el, attrs) -> - if scope.epics && !scope.epics.isIterable - scope.epics = Immutable.fromJS(scope.epics) + scope.$watch 'epics', (epics) -> + if epics && !epics.isIterable + scope.epics = Immutable.fromJS(epics) templateUrl = (el, attrs) -> if attrs.format From a3a7b868ba8e66a3b16d00914eeef6f6e268bd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 16:18:20 +0200 Subject: [PATCH 130/137] Add timeout to hide epics table options --- .../epics-table/epics-table.controller.coffee | 12 ++++++++++-- .../epics/dashboard/epics-table/epics-table.jade | 9 +++++++-- .../epics/dashboard/epics-table/epics-table.scss | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) 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 45e41d45..4934b4d9 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -23,10 +23,11 @@ taiga = @.taiga class EpicsTableController @.$inject = [ "$tgConfirm", - "tgEpicsService" + "tgEpicsService", + "$timeout" ] - constructor: (@confirm, @epicsService) -> + constructor: (@confirm, @epicsService, @timeout) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -49,4 +50,11 @@ class EpicsTableController .then null, () => # on error @confirm.notify("error") + hoverEpicTableOption: () -> + if @.timer + @timeout.cancel(@.timer) + + hideEpicTableOption: () -> + return @.timer = @timeout (=> @.displayOptions = false), 400 + angular.module("taigaEpics").controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 7ab6a81b..fe99fcbd 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -40,11 +40,16 @@ mixin epicSwitch(name, model) translate="EPICS.TABLE.PROGRESS" ng-if="vm.column.progress" ) - .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") + .epics-table-options-wrapper( + ng-mouseleave="vm.hideEpicTableOption()" + ) button.epics-table-option-button.e2e-epics-column-button(ng-click="vm.displayOptions = true") span(translate="EPICS.TABLE.VIEW_OPTIONS") tg-svg(svg-icon="icon-arrow-down") - form.epics-table-dropdown.e2e-epics-column-dropdown(ng-show="vm.displayOptions") + form.epics-table-dropdown.e2e-epics-column-dropdown( + ng-show="vm.displayOptions" + ng-mouseenter="vm.keepEpicTableOption()" + ) .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.VOTES" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 2651dde4..8814fbc0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -44,6 +44,12 @@ top: 1.3rem; width: 250px; z-index: 99; + &.ng-hide-remove { + animation: dropdownFade .2s; + } + &.ng-hide-add { + animation: dropdownFade .2s reverse; + } .fieldset { @include font-size(small); border-bottom: 1px solid $whitish; From d3f5cfb02f94cd831bba5f46b2e139d9067c603b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 07:59:44 +0200 Subject: [PATCH 131/137] Add broken link icon --- .../related-userstory-row/related-userstory-row.jade | 2 +- app/svg/sprite.svg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index a4d74a88..c54e09af 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -23,7 +23,7 @@ tg-svg.icon-drag( href="" ng-click="vm.onDeleteRelatedUserstory()" ) - tg-svg(svg-icon="icon-trash") + tg-svg(svg-icon="icon-broken-link") .project( tg-nav="project:project=vm.userstory.getIn(['project_extra_info', 'slug'])" diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index f6f2022f..869e7c00 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -450,5 +450,9 @@ Epics + + Broken Link + + From ffc04dcc41053dbca126d4b68bddfe41d9c17df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 08:05:45 +0200 Subject: [PATCH 132/137] Change DELETE for UNLINK in unlink Story lightbox --- app/locales/taiga/locale-en.json | 6 +++--- .../related-userstory-row.controller.coffee | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 31e0225b..99a17c12 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1073,9 +1073,9 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", "NEW_USERSTORY": "New user story", "EXISTING_USERSTORY": "Existing user story", diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee index ef58ab9b..c8513f9e 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -40,15 +40,15 @@ class RelatedUserstoryRowController return @translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED") onDeleteRelatedUserstory: () -> - title = @translate.instant('EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY') - message = @translate.instant('EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY', { + title = @translate.instant('EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY') + message = @translate.instant('EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY', { subject: @.userstory.get('subject') }) return @confirm.askOnDelete(title, message) .then (askResponse) => onError = () => - message = @translate.instant('EPIC.ERROR_DELETE_RELATED_USERSTORY', {errorMessage: message}) + message = @translate.instant('EPIC.ERROR_UNLINK_RELATED_USERSTORY', {errorMessage: message}) @confirm.notify("error", null, message) askResponse.finish(false) From ea7098cad789cb836ca567220ee0238e1218537f Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 23 Sep 2016 09:49:32 +0200 Subject: [PATCH 133/137] Removing cursor from e2e tests --- conf.e2e.js | 82 ++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/conf.e2e.js b/conf.e2e.js index cd421c09..d7ca0a01 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -53,55 +53,55 @@ var config = { onPrepare: function() { // disable by default because performance problems on IE // track mouse movements - var trackMouse = function() { - angular.module('trackMouse', []).run(function($document) { + // var trackMouse = function() { + // angular.module('trackMouse', []).run(function($document) { - function addDot(ev) { - var color = 'black', - size = 6; + // function addDot(ev) { + // var color = 'black', + // size = 6; - switch (ev.type) { - case 'click': - color = 'red'; - break; - case 'dblclick': - color = 'blue'; - break; - case 'mousemove': - color = 'green'; - break; - } + // switch (ev.type) { + // case 'click': + // color = 'red'; + // break; + // case 'dblclick': + // color = 'blue'; + // break; + // case 'mousemove': + // color = 'green'; + // break; + // } - var dotEl = $('
') - .css({ - position: 'fixed', - height: size + 'px', - width: size + 'px', - 'background-color': color, - top: ev.clientY, - left: ev.clientX, + // var dotEl = $('
') + // .css({ + // position: 'fixed', + // height: size + 'px', + // width: size + 'px', + // 'background-color': color, + // top: ev.clientY, + // left: ev.clientX, - 'z-index': 9999, + // 'z-index': 9999, - // make sure this dot won't interfere with the mouse events of other elements - 'pointer-events': 'none' - }) - .appendTo('body'); + // // make sure this dot won't interfere with the mouse events of other elements + // 'pointer-events': 'none' + // }) + // .appendTo('body'); - setTimeout(function() { - dotEl.remove(); - }, 1000); - } + // setTimeout(function() { + // dotEl.remove(); + // }, 1000); + // } - $document.on({ - click: addDot, - dblclick: addDot, - mousemove: addDot - }); + // $document.on({ + // click: addDot, + // dblclick: addDot, + // mousemove: addDot + // }); - }); - }; - browser.addMockModule('trackMouse', trackMouse); + // }); + // }; + // browser.addMockModule('trackMouse', trackMouse); browser.params.glob.back = argv.back; From 4cdd1653e9846953fd76c17073526f506a9ed3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 11:05:01 +0200 Subject: [PATCH 134/137] Minor style errors --- app/modules/components/filter/filter.scss | 2 +- app/modules/components/vote-button/vote-button.jade | 10 ++++------ app/styles/modules/backlog/sprints.scss | 5 +++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss index 4283e97b..1d78ca71 100644 --- a/app/modules/components/filter/filter.scss +++ b/app/modules/components/filter/filter.scss @@ -1,5 +1,5 @@ tg-filter { - background-color: $whitish; + background-color: $mass-white; display: block; left: 0; min-height: 100%; diff --git a/app/modules/components/vote-button/vote-button.jade b/app/modules/components/vote-button/vote-button.jade index 3ae180ac..1259c10d 100644 --- a/app/modules/components/vote-button/vote-button.jade +++ b/app/modules/components/vote-button/vote-button.jade @@ -8,17 +8,15 @@ a.vote-inner( ng-mouseover="vm.showTextWhenMouseIsOver()" ng-mouseleave="vm.showTextWhenMouseIsLeave()" ) - span.track-icon - tg-svg(svg-icon="icon-upvote") - span.track-button-counter( + tg-svg(svg-icon="icon-upvote") + span( title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}", tg-loading="vm.loading" ) {{ vm.item.total_voters }} //- Anonymous user button span.vote-inner(ng-if="::!vm.user") - span.track-icon - tg-svg(svg-icon="icon-watch") - span.track-button-counter( + tg-svg(svg-icon="icon-upvote") + span( title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}" ) {{ ::vm.item.total_voters }} diff --git a/app/styles/modules/backlog/sprints.scss b/app/styles/modules/backlog/sprints.scss index 82deb18a..040cadba 100644 --- a/app/styles/modules/backlog/sprints.scss +++ b/app/styles/modules/backlog/sprints.scss @@ -78,10 +78,11 @@ } } .number { - @include font-size(small); + @include font-size(xsmall); + margin-right: .2rem; } .description { - @include font-size(x-small); + @include font-size(xsmall); line-height: .6rem; margin-top: 5px; } From 636a44e0f9e13931fe790b701de834630d3b6041 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 23 Sep 2016 19:50:23 +0200 Subject: [PATCH 135/137] Fixing tests --- .../related-userstory-row.controller.coffee | 1 - .../related-userstory-row.controller.spec.coffee | 12 +++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee index c8513f9e..ba583d8a 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -44,7 +44,6 @@ class RelatedUserstoryRowController message = @translate.instant('EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY', { subject: @.userstory.get('subject') }) - return @confirm.askOnDelete(title, message) .then (askResponse) => onError = () => diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee index c300b372..5b496b0e 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee @@ -125,15 +125,14 @@ describe "RelatedUserstoryRow", -> finish: sinon.spy() } - mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") - mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") mocks.tgConfirm.askOnDelete = sinon.stub() mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().resolve(true) RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> - expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).have.been.calledOnce expect(askResponse.finish).have.been.calledOnce done() @@ -153,16 +152,15 @@ describe "RelatedUserstoryRow", -> finish: sinon.spy() } - mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") - mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") - mocks.translate.instant.withArgs("EPIC.ERROR_DELETE_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.ERROR_UNLINK_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") mocks.tgConfirm.askOnDelete = sinon.stub() mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().reject(new Error("error")) RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> - expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).to.not.have.been.called expect(askResponse.finish).have.been.calledWith(false) expect(mocks.tgConfirm.notify).have.been.calledWith("error", null, "error message") From 1bc2eb03448582fa87be11f63918875e9fed073d Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 07:44:32 +0200 Subject: [PATCH 136/137] Fixing epic filtering --- app/coffee/modules/controllerMixins.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 5d8cef9f..a6c501cd 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,7 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner - loadFilters.epics = urlfilters.epics + loadFilters.epic = urlfilters.epic loadFilters.q = urlfilters.q return @q.all([ @@ -238,7 +238,7 @@ class UsFiltersMixin it.name = it.full_name return it - epics = _.map data.epics, (it) -> + epic = _.map data.epics, (it) -> if it.id it.id = it.id.toString() it.name = "##{it.ref} #{it.subject}" @@ -266,8 +266,8 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) - if loadFilters.epics - selected = @.formatSelectedFilters("epics", epics, loadFilters.epics) + if loadFilters.epic + selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) @.selectedFilters = @.selectedFilters.concat(selected) @.filterQ = loadFilters.q @@ -297,8 +297,8 @@ class UsFiltersMixin }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), - dataType: "epics", - content: epics + dataType: "epic", + content: epic } ] From 4da7d44d0e924d9b8c8cba7180a84309fedae7b0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 27 Sep 2016 09:58:33 +0200 Subject: [PATCH 137/137] fix admin e2e tests --- .../admin/admin-custom-attributes.jade | 2 +- .../includes/modules/admin/project-tags.jade | 2 +- e2e/helpers/admin-attributes-helper.js | 20 +++++++++++++------ e2e/helpers/custom-fields-helper.js | 4 ++-- e2e/helpers/project-detail-helper.js | 9 ++++++++- e2e/suites/admin/attributes/tags.e2e.js | 9 +++++---- e2e/suites/admin/project/modules.e2e.js | 2 +- .../admin/project/project-detail.e2e.js | 9 +++++++-- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/app/partials/includes/modules/admin/admin-custom-attributes.jade b/app/partials/includes/modules/admin/admin-custom-attributes.jade index 123d39ce..847a4133 100644 --- a/app/partials/includes/modules/admin/admin-custom-attributes.jade +++ b/app/partials/includes/modules/admin/admin-custom-attributes.jade @@ -16,7 +16,7 @@ section.custom-fields-table.basic-table .table-body .js-sortable - div( + .e2e-item( ng-repeat="attr in customAttributes track by attr.id" tg-bind-scope ) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 83b6fc78..dd339453 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -72,7 +72,7 @@ section ) p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH") - div( + div.e2e-tag-row( ng-repeat="tag in projectTags" tg-bind-scope ) diff --git a/e2e/helpers/admin-attributes-helper.js b/e2e/helpers/admin-attributes-helper.js index fc485b94..b3bedf2d 100644 --- a/e2e/helpers/admin-attributes-helper.js +++ b/e2e/helpers/admin-attributes-helper.js @@ -40,7 +40,15 @@ helper.getTagsSection = function(item) { return { el: section, rows: function() { - return section.$$('.table-main'); + return section.$$('.e2e-tag-row'); + }, + edit: async function(row) { + let editButton = row.$('.edit-value'); + + return browser.actions() + .mouseMove(editButton) + .click() + .perform(); } }; }; @@ -48,7 +56,7 @@ helper.getTagsSection = function(item) { helper.getTagsFilter = function() { return $('.table-header .e2e-tags-filter'); -} +}; helper.getStatusNames = function(section) { return section.$$('.status-name span').getText(); @@ -111,12 +119,12 @@ helper.getGenericForm = function(form) { }; obj.colorBox = function() { - return form.$('.edition .current-color'); - } + return form.$('.edition .e2e-open-color-selector'); + }; obj.colorText = function() { - return form.$('.select-color input'); - } + return form.$('.color-selector-dropdown input'); + }; return obj; }; diff --git a/e2e/helpers/custom-fields-helper.js b/e2e/helpers/custom-fields-helper.js index 19bef257..837ac956 100644 --- a/e2e/helpers/custom-fields-helper.js +++ b/e2e/helpers/custom-fields-helper.js @@ -47,11 +47,11 @@ helper.drag = function(indexType, indexCustomField, indexNewPosition) { let customField = helper.getCustomFiledsByType(indexType).get(indexCustomField).$('.e2e-drag'); let newPosition = helper.getCustomFiledsByType(indexType).get(indexNewPosition); - return utils.common.drag(customField, newPosition, 0, 40); + return utils.common.drag(customField, newPosition, 5, 25); }; helper.getCustomFiledsByType = function(indexType) { - return $$('div[tg-project-custom-attributes]').get(indexType).$$('.js-sortable > div'); + return $$('div[tg-project-custom-attributes]').get(indexType).$$('.e2e-item'); }; helper.delete = async function(indexType, indexCustomField) { diff --git a/e2e/helpers/project-detail-helper.js b/e2e/helpers/project-detail-helper.js index efc676f5..88f2c81a 100644 --- a/e2e/helpers/project-detail-helper.js +++ b/e2e/helpers/project-detail-helper.js @@ -58,11 +58,14 @@ helper.getChangeOwnerLb = function() { return utils.lightbox.close(el); }, search: function(q) { - el.$$('input').get(0).sendKeys(q); + return el.$$('input').get(0).sendKeys(q); }, select: function(index) { el.$$('.user-list-single').get(index).click(); }, + getUserName: function(index) { + return el.$$('.user-list-single').get(index).$('.user-list-name').getText(); + }, addComment: function(text) { el.$('.add-comment a').click(); el.$('textarea').sendKeys(text); @@ -74,3 +77,7 @@ helper.getChangeOwnerLb = function() { return obj; }; + +helper.enableAddTags = function() { + $('.add-tag-button').click(); +}; diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js index a4b6010e..403591fd 100644 --- a/e2e/suites/admin/attributes/tags.e2e.js +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -22,7 +22,9 @@ describe('attributes - tags', function() { let rows = section.rows(); let row = rows.get(0); - let form = adminAttributesHelper.getGenericForm(row.$('form')); + section.edit(row); + + let form = adminAttributesHelper.getGenericForm(row.$$('form').first()); var colorBox = form.colorBox(); await colorBox.click(); @@ -35,7 +37,7 @@ describe('attributes - tags', function() { section = adminAttributesHelper.getTagsSection(0); rows = section.rows(); row = rows.get(0); - let backgroundColor = await row.$$('.edition .current-color').get(0).getCssValue('background-color'); + let backgroundColor = await row.$$('.e2e-open-color-selector').get(0).getCssValue('background-color'); expect(backgroundColor).to.be.equal('rgba(0, 0, 0, 1)'); utils.common.takeScreenshot('attributes', 'tag edited is black'); }); @@ -44,12 +46,11 @@ describe('attributes - tags', function() { let tagsFilter = adminAttributesHelper.getTagsFilter(); await tagsFilter.clear(); await tagsFilter.sendKeys('ad'); + await browser.sleep(5000); let section = adminAttributesHelper.getTagsSection(0); let rows = section.rows(); let count = await rows.count(); expect(count).to.be.equal(2); }); - - }); diff --git a/e2e/suites/admin/project/modules.e2e.js b/e2e/suites/admin/project/modules.e2e.js index 7ad0e361..5c85e3a8 100644 --- a/e2e/suites/admin/project/modules.e2e.js +++ b/e2e/suites/admin/project/modules.e2e.js @@ -78,7 +78,7 @@ describe('modules', function() { }); it('enable videoconference', async function() { - let functionality = $$('.module').get(4); + let functionality = $$('.module').get(5); let input = functionality.$('.check input'); diff --git a/e2e/suites/admin/project/project-detail.e2e.js b/e2e/suites/admin/project/project-detail.e2e.js index e3c15366..033015a4 100644 --- a/e2e/suites/admin/project/project-detail.e2e.js +++ b/e2e/suites/admin/project/project-detail.e2e.js @@ -18,7 +18,9 @@ describe('project detail', function() { }); it('edit tag, description and project settings', async function() { - let tag = $('.tag-input'); + adminHelper.enableAddTags(); + + let tag = $('.e2e-add-tag-input'); tag.sendKeys('aaa'); browser.actions().sendKeys(protractor.Key.ENTER).perform(); @@ -105,7 +107,10 @@ describe('project detail', function() { await lb.waitOpen(); - lb.search('Alicia Flores'); + let username = lb.getUserName(0); + + await lb.search(username); + lb.select(0); lb.addComment('text');