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. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ccc7247c..9a552542 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) -> @@ -124,7 +124,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) - $routeProvider.when("/blocked-project/:pslug/", { templateUrl: "projects/project/blocked-project.html", @@ -153,6 +152,25 @@ 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" + } + ) + + # 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", @@ -519,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") @@ -574,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) -> @@ -601,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") @@ -672,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', () -> @@ -715,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` @@ -725,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) @@ -742,7 +762,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na if next.mobileViewport appMetaService.addMobileViewport() - else + else appMetaService.removeMobileViewport() if next.disableHeader @@ -750,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", @@ -784,6 +807,7 @@ modules = [ "taigaPlugins", "taigaIntegrations", "taigaComponents", + # new modules "taigaProfile", "taigaHome", @@ -792,6 +816,8 @@ modules = [ "taigaDiscover", "taigaHistory", "taigaWikiHistory", + "taigaEpics", + "taigaUtils" # template cache "templates", @@ -804,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) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 06fba778..65eb759c 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]] @@ -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/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/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index f1815b99..11fb5f4c 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.EPICS.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/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 = [] diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index c4448294..5cfa7dd4 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -73,8 +73,10 @@ 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-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/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a358dde4..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 ############################################################################# @@ -765,6 +678,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/common/filters.coffee b/app/coffee/modules/common/filters.coffee index 7590b45c..c232d85d 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -74,3 +74,56 @@ 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]) + + +darkerFilter = -> + return (color, luminosity) -> + # validate hex string + color = new String(color).replace(/[^0-9a-f]/gi, '') + 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/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index 40f53264..ee143b59 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,7 +440,7 @@ 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 callback([]) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 08d0ac50..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,6 +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() @@ -235,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 = [] @@ -254,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 = [ @@ -266,7 +282,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"), @@ -277,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/coffee/modules/epics.coffee b/app/coffee/modules/epics.coffee new file mode 100644 index 00000000..743e70d4 --- /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/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/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/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) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 59e8743a..20b9fa9a 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" @@ -92,6 +93,15 @@ urls = { # Milestones/Sprints "milestones": "/milestones" + # 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" "bulk-create-us": "/userstories/bulk_create" @@ -130,26 +140,30 @@ 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" "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" @@ -158,6 +172,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" @@ -219,6 +234,7 @@ module.run([ "$tgRolesResourcesProvider", "$tgUserSettingsResourcesProvider", "$tgSprintsResourcesProvider", + "$tgEpicsResourcesProvider", "$tgUserstoriesResourcesProvider", "$tgTasksResourcesProvider", "$tgIssuesResourcesProvider", 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/coffee/modules/resources/epics.coffee b/app/coffee/modules/resources/epics.coffee new file mode 100644 index 00000000..480395ce --- /dev/null +++ b/app/coffee/modules/resources/epics.coffee @@ -0,0 +1,77 @@ +### +# 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, $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) + 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 {} + + 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","$tgHttp", "$tgUrls", "$tgStorage", resourceProvider]) 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/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 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/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/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/coffee/utils.coffee b/app/coffee/utils.coffee index 1bffd7c7..a2d02c99 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', '#222222' +] + +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/images/epics-empty.png b/app/images/epics-empty.png new file mode 100644 index 00000000..28363733 Binary files /dev/null and b/app/images/epics-empty.png differ 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-en.json b/app/locales/taiga/locale-en.json index f67ddd47..99a17c12 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}}.", @@ -45,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" @@ -119,6 +122,7 @@ "USER_STORY": "User story", "TASK": "Task", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Enter tag", "DELETE": "Delete tag", @@ -214,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", @@ -253,6 +258,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", @@ -391,6 +404,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": "Unassigned" + }, + "EMPTY": { + "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": { + "VOTES": "Votes", + "NAME": "Name", + "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": "Blocked", + "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.", @@ -461,6 +509,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", @@ -523,6 +573,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", @@ -535,6 +586,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", @@ -572,7 +625,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" }, @@ -684,13 +738,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" @@ -793,6 +848,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", @@ -1012,6 +1069,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_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", + "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?", + "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" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", @@ -1026,8 +1103,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", @@ -1130,7 +1205,8 @@ "SPRINT_ORDER": "sprint order", "KANBAN_ORDER": "kanban order", "TASKBOARD_ORDER": "taskboard order", - "US_ORDER": "us order" + "US_ORDER": "us order", + "COLOR": "color" } }, "BACKLOG": { @@ -1261,8 +1337,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": { @@ -1307,8 +1381,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": { @@ -1365,6 +1437,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", @@ -1507,6 +1580,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}}", @@ -1519,9 +1594,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-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}}", 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..709ba6cc --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.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: assigned-to-selector.directive.coffee +### + +AssignedItemDirective = () -> + + link = (scope, el, attrs) -> + + return { + 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..b0c06515 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -0,0 +1,3 @@ +.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 new file mode 100644 index 00000000..132d34aa --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.scss @@ -0,0 +1,22 @@ +.assignable-member-single { + align-items: center; + display: flex; + padding: .25rem 0; + .assigned-members-option & { + background: $white; + border-bottom: 1px solid $whitish; + cursor: pointer; + } + &:hover { + background: rgba($primary-light, .05); + } + .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 new file mode 100644 index 00000000..4e70615b --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -0,0 +1,41 @@ +### +# 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: () -> + if @.assigned + @._getAssignedMember() + @._filterAssignedMember() + + _getAssignedMember: () -> + @.assignedMember = _.filter(@.project.members, (member) => + return member.id == @.assigned.get('id') + ) + + _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 new file mode 100644 index 00000000..b840e856 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.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 +### + +AssignedToSelectorDirective = () -> + + return { + controller: "AssignedToSelectorCtrl", + controllerAs: "vm", + bindToController: true, + templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", + scope: { + assigned: "=", + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" + } + } + +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..435a7979 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -0,0 +1,27 @@ +tg-lightbox-close + +.assigned-to-container + 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.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.e2e-unassign( + svg-icon="icon-close" + svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED" + ng-click="vm.onRemoveAssigned()" + ) + 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-selector/assigned-to-selector.scss b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss new file mode 100644 index 00000000..ac54aa3a --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss @@ -0,0 +1,27 @@ +.assigned-to-container { + width: 600px; +} + +.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 new file mode 100644 index 00000000..dc69b30e --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -0,0 +1,51 @@ +### +# 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.controller.coffee +### + +class AssignedToController + @.$inject = [ + "tgLightboxFactory", + "lightboxService", + ] + + 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", + "on-remove-assigned": "onRemoveAssigned()" + "on-assign-to": "assignTo(member)" + }, { + "assigned": @.assignedTo, + "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 new file mode 100644 index 00000000..a6ec47aa --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.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.directive.coffee +### + +AssignedToDirective = () -> + + return { + controller: "AssignedToCtrl", + controllerAs: "vm", + bindToController: true, + templateUrl: "components/assigned-to/assigned-to.html", + scope: { + assignedTo: "=", + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" + } + } + +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..a03af5a3 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -0,0 +1,24 @@ +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.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.e2e-assigned-to-image( + 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.e2e-assigned-to-image( + ng-if="!vm.assignedTo && !vm.has_permissions" + src="/#{v}/images/unnamed.png" + alt="{{'EPICS.DASHBOARD.UNASSIGNED' | translate}}" +) 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/components/belong-to-epics/belong-to-epics-pill.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade new file mode 100644 index 00000000..fd861905 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -0,0 +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'), 'border-color': '{{ epic.get('color') | darker: -0.2 }}'}" + 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..7d23bdbe --- /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')") + 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 new file mode 100644 index 00000000..91ffe19e --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -0,0 +1,43 @@ +### +# 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 = () -> + + link = (scope, el, attrs) -> + scope.$watch 'epics', (epics) -> + if epics && !epics.isIterable + scope.epics = Immutable.fromJS(epics) + + 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: '=' + }, + templateUrl: templateUrl + } + + +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 new file mode 100644 index 00000000..01e05872 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -0,0 +1,36 @@ +.belong-to-epic-pill-wrapper { + display: inline-block; + position: relative; + &:hover { + .belong-to-epic-pill-data { + display: block; + } + } +} + +.belong-to-epic-pill { + background-color: $mass-white; + border-radius: 50%; + display: inline-block; + height: .7rem; + margin: 0 .1rem; + position: relative; + width: .7rem; +} + +.belong-to-epic-text-wrapper { + margin-right: 1rem; +} + +.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; +} diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade index ad5434d8..94df8825 100644 --- a/app/modules/components/card/card-templates/card-title.jade +++ b/app/modules/components/card/card-templates/card-title.jade @@ -7,3 +7,8 @@ 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/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee new file mode 100644 index 00000000..47838f23 --- /dev/null +++ b/app/modules/components/color-selector/color-selector.controller.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: color-selector.controller.coffee +### + +taiga = @.taiga +getDefaulColorList = taiga.getDefaulColorList + + +class ColorSelectorController + @.$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) + + setColor: (color) -> + @.color = @.initColor + + resetColor: () -> + if @.isColorRequired and not @.color + @.color = @.initColor + + toggleColorList: () -> + @.displayColorList = !@.displayColorList + @.resetColor() + + onSelectDropdownColor: (color) -> + @.color = color + @.onSelectColor({color: color}) + @.toggleColorList() + + onKeyDown: (event) -> + if event.which == 13 # ENTER + if @.color or not @.isColorRequired + @.onSelectDropdownColor(@.color) + event.preventDefault() + + +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 56% 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..5fe727f6 100644 --- a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -23,32 +23,41 @@ 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 -> - module "taigaCommon" + module "taigaComponents" _mocks() inject ($controller) -> controller = $controller + it "require Color on Selector", () -> colorSelectorCtrl = controller "ColorSelectorCtrl" - colorSelectorCtrl.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - ] - colorSelectorCtrl.displaycolorList = false + colorSelectorCtrl.colorList = ["#000000", "#123123"] + colorSelectorCtrl.isColorRequired = false + colorSelectorCtrl.checkIsColorRequired() + expect(colorSelectorCtrl.colorList).to.be.eql(["#000000"]) 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 +67,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, preventDefault: sinon.stub()} + color = "#fabada" + + colorSelectorCtrl.color = color + + colorSelectorCtrl.onKeyDown(event) + expect(event.preventDefault).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color) 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 61% 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..f020e408 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,23 @@ # File: color-selector.directive.coffee ### -module = angular.module('taigaCommon') +bindOnce = @.taiga.bindOnce 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 (() -> + ctrl.displayColorList = false + ctrl.resetColor() ), 400 el.find('.color-selector') @@ -42,20 +44,27 @@ ColorSelectorDirective = ($timeout) -> .mouseenter(cancel) .mouseleave(close) + 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 { link: link, - scope:{ - onSelectColor: "&", - color: "=" - }, - templateUrl:"components/tags/color-selector/color-selector.html", + templateUrl:"components/color-selector/color-selector.html", controller: "ColorSelectorCtrl", controllerAs: "vm", - bindToController: true + bindToController: { + isColorRequired: "=", + onSelectColor: "&", + initColor: "=", + requiredPerm: "@" + }, + scope: {}, } + ColorSelectorDirective.$inject = [ "$timeout" ] -module.directive("tgColorSelector", ColorSelectorDirective) +angular.module('taigaComponents').directive("tgColorSelector", ColorSelectorDirective) diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade new file mode 100644 index 00000000..dda5becc --- /dev/null +++ b/app/modules/components/color-selector/color-selector.jade @@ -0,0 +1,43 @@ +//- 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}" + ng-style="{'background': vm.color}" + ) + .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" + ng-style="{'background': color}" + ng-title="color" + ng-click="vm.onSelectDropdownColor(color)" + ) + li.empty-color( + ng-if="!vm.isColorRequired" + ng-click="vm.onSelectDropdownColor(null)" + ) + .custom-color-selector + .display-custom-color.empty-color( + ng-if="!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" + 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 89% rename from app/modules/components/tags/color-selector/color-selector.scss rename to app/modules/components/color-selector/color-selector.scss index 2f06ccb0..98cf62fd 100644 --- a/app/modules/components/tags/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -15,10 +15,12 @@ .tag-color { @include color-selector-option; border: 1px solid $gray-light; - border-left: 0; border-radius: 0; margin: 0; transition: background .3s ease-out; + &.disabled { + cursor: auto; + } &.empty-color { @include empty-color(34); } @@ -50,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; diff --git a/app/modules/components/detail/header/detail-header.controller.coffee b/app/modules/components/detail/header/detail-header.controller.coffee new file mode 100644 index 00000000..2d2b5437 --- /dev/null +++ b/app/modules/components/detail/header/detail-header.controller.coffee @@ -0,0 +1,90 @@ +### +# 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-header.controller.coffee +### + +module = angular.module("taigaUserStories") + +class StoryHeaderController + @.$inject = [ + "$rootScope", + "$tgConfirm", + "$tgQueueModelTransformation", + "$tgNavUrls", + "$window" + ] + + constructor: (@rootScope, @confirm, @modelTransform, @navUrls, @window) -> + @.editMode = false + @.loadingSubject = false + @.originalSubject = @.item.subject + + _checkNav: () -> + if @.item.neighbors.previous?.ref? + ctx = { + project: @.project.slug + ref: @.item.neighbors.previous.ref + } + @.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-" + @.item._name + "-detail", ctx) + + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, @.requiredPerm) + } + + editSubject: (value) -> + selection = @window.getSelection() + if selection.type != "Range" + if value + @.editMode = true + if !value + @.editMode = false + + onKeyDown: (event) -> + if event.which == 13 + @.saveSubject() + + if event.which == 27 + @.item.subject = @.originalSubject + @.editSubject(false) + + saveSubject: () -> + onEditSubjectSuccess = () => + @.loadingSubject = false + @rootScope.$broadcast("object:updated") + @confirm.notify('success') + @.originalSubject = @.item.subject + + 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/components/detail/header/detail-header.controller.spec.coffee b/app/modules/components/detail/header/detail-header.controller.spec.coffee new file mode 100644 index 00000000..31ee9c96 --- /dev/null +++ b/app/modules/components/detail/header/detail-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/components/detail/header/detail-header.directive.coffee b/app/modules/components/detail/header/detail-header.directive.coffee new file mode 100644 index 00000000..10267d0f --- /dev/null +++ b/app/modules/components/detail/header/detail-header.directive.coffee @@ -0,0 +1,43 @@ +### +# 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() + ctrl._checkNav() + + return { + link: link, + controller: "StoryHeaderCtrl", + bindToController: true, + scope: { + item: "=", + project: "=", + requiredPerm: "@" + }, + controllerAs: "vm", + templateUrl:"components/detail/header/detail-header.html" + } + + +module.directive("tgDetailHeader", DetailHeaderDirective) diff --git a/app/modules/components/detail/header/detail-header.jade b/app/modules/components/detail/header/detail-header.jade new file mode 100644 index 00000000..317065c0 --- /dev/null +++ b/app/modules/components/detail/header/detail-header.jade @@ -0,0 +1,113 @@ +.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.e2e-title-subject( + ng-click="vm.editSubject(true)" + ng-if="vm.permissions.canEdit" + ) {{vm.item.subject}} + span.detail-subject.e2e-title-subject( + ng-if="!vm.permissions.canEdit" + ) {{vm.item.subject}} + 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.e2e-title-input( + type="text" + ng-model="vm.item.subject" + maxlength="500" + autofocus + required + ng-keydown="vm.onKeyDown($event)" + ) + button.edit-title-button.e2e-title-button( + ng-click="vm.saveSubject()" + tg-loading="vm.loadingSubject" + ) + 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( + ng-if="::vm.item.epics" + epics="::vm.item.epics" + format="text" + ) + +//- 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( + 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.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" + ) + +//- 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="" + 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.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}} + +//- Navigation +.issue-nav + a( + ng-if="vm.previousUrl" + ng-href="{{vm.previousUrl}}" + title="{{'COMMON.PREVIOUS' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-left") + a( + ng-if="vm.nextUrl" + ng-href="{{vm.nextUrl}}" + title="{{'COMMON.NEXT' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/components/detail/header/detail-header.scss b/app/modules/components/detail/header/detail-header.scss new file mode 100644 index 00000000..122e1cd3 --- /dev/null +++ b/app/modules/components/detail/header/detail-header.scss @@ -0,0 +1,120 @@ +.detail-header-container { + background: $mass-white; + flex: 1; + padding: 1rem; + position: relative; + &: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-generated-us, + .item-origin-issue, + .task-belongs-to, + .belong-to-epics-wrapper, + .block-desc-container { + @include font-size(small); + margin-top: .5rem; + } + .item-generated-us, + .task-belongs-to, + .item-origin-issue { + a { + cursor: pointer; + padding: 0 .2rem; + } + .item-ref { + padding: 0 .2rem; + } + } +} + +.detail-title-wrapper { + @include font-size(larger); + @include font-type(text); + align-content: center; + display: flex; + max-width: 95%; + position: relative; + transition: all .2s linear; + &.blocked { + background: $red; + transition: all .2s linear; + } + .detail-title-text { + line-height: normal; + 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; + } +} 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')" 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/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.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/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index e1bdd2d9..62fc2b9c 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -15,16 +15,26 @@ 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(ng-if="vm.menu.get('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')" ng-mouseover="backlogHover = true" diff --git a/app/modules/components/tags/color-selector/color-selector.controller.coffee b/app/modules/components/tags/color-selector/color-selector.controller.coffee deleted file mode 100644 index 2d817a15..00000000 --- a/app/modules/components/tags/color-selector/color-selector.controller.coffee +++ /dev/null @@ -1,57 +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: color-selector.controller.coffee -### - -module = angular.module('taigaCommon') - -class ColorSelectorController - - constructor: () -> - @.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - '#8ae234', - '#73d216', - '#4e9a06', - '#d3d7cf', - '#fcaf3e', - '#f57900', - '#ce5c00', - '#729fcf', - '#3465a4', - '#204a87', - '#888a85', - '#ad7fa8', - '#75507b', - '#5c3566', - '#ef2929', - '#cc0000', - '#a40000' - ] - @.displaycolorList = false - - toggleColorList: () -> - @.displaycolorList = !@.displaycolorList - - onSelectDropdownColor: (color) -> - @.onSelectColor({color: color}) - @.toggleColorList() - - -module.controller("ColorSelectorCtrl", ColorSelectorController) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/tags/color-selector/color-selector.jade deleted file mode 100644 index c66e83aa..00000000 --- a/app/modules/components/tags/color-selector/color-selector.jade +++ /dev/null @@ -1,30 +0,0 @@ -.color-selector - .tag-color.e2e-open-color-selector( - ng-click="vm.toggleColorList()" - ng-class="{'empty-color': !vm.color}" - ng-style="{'background': vm.color}" - ) - .color-selector-dropdown(ng-show="vm.displaycolorList") - ul.color-selector-dropdown-list.e2e-color-dropdown - li.color-selector-option( - ng-repeat="color in vm.colorList" - ng-style="{'background': color}" - ng-title="color" - ng-click="vm.onSelectDropdownColor(color)" - ) - li.empty-color(ng-click="vm.onSelectDropdownColor(null)") - .custom-color-selector - .display-custom-color.empty-color( - ng-if="!customColor.color || customColor.color.length < 7" - ) - .display-custom-color( - ng-if="customColor.color.length === 7" - ng-style="{'background': customColor.color}" - ng-click="vm.onSelectDropdownColor(customColor.color)" - ) - input.custom-color-input( - type="text" - maxlength="7" - placeholder="#000000" - ng-model="customColor.color" - ) diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade index 7e1b11ca..62b8cf1c 100644 --- a/app/modules/components/tags/components/add-tag-input.jade +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -21,8 +21,8 @@ tg-color-selector( ng-if="!vm.disableColorSelection" - color="vm.newTag.color", on-select-color="vm.selectColor(color)" + is-color-required="false" ) tg-svg.save( 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/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/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/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) 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..8ab9ef15 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -0,0 +1,82 @@ +### +# 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 +### + +taiga = @.taiga +trim = taiga.trim +getRandomDefaultColor = taiga.getRandomDefaultColor + + +class CreateEpicController + @.$inject = [ + "$tgConfirm" + "tgProjectService", + "tgEpicsService" + ] + + 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() + status: @.project.default_epic_status + tags: [] + } + @.attachments = Immutable.List() + + @.loading = false + + createEpic: () -> + return if not @.validateForm() + + @.loading = true + + @epicsService.createEpic(@.newEpic, @.attachments) + .then (response) => # On success + @.onCreateEpic() + @.loading = false + .catch (response) => # On error + @.loading = false + @.setFormErrors(response.data) + if response.data._error_message + @confirm.notify("error", response.data._error_message) + + # Color selector + selectColor: (color) -> + @.newEpic.color = color + + # Tags + 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] + + # Attachments + addAttachment: (attachment) -> + @.attachments.push(attachment) + +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 new file mode 100644 index 00000000..cd588888 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -0,0 +1,108 @@ +### +# 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 = {} + + _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 = { + createEpic: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgConfirm() + _mockTgProjectService() + _mockTgEpicsService() + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "create Epic with invalid form", () -> + mocks.tgProjectService.project.toJS.withArgs().returns( + {id: 1, default_epic_status: 1} + ) + + data = { + 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(false) + + createEpicCtrl.createEpic() + + expect(data.validateForm).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 = { + 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.tgEpicsService.createEpic + .withArgs( + createEpicCtrl.newEpic, + createEpicCtrl.attachments) + .promise() + .resolve( + {data: {id: 1, project: 1}} + ) + + createEpicCtrl.createEpic().then () -> + expect(data.validateForm).have.been.called + expect(createEpicCtrl.onCreateEpic).have.been.called + done() 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..fda1525d --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -0,0 +1,41 @@ +### +# 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 +### + +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: { + onCreateEpic: '&' + }, + scope: {} + } + +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 new file mode 100644 index 00000000..e93a63fe --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.jade @@ -0,0 +1,101 @@ +tg-lightbox-close + +.create-epic-container + h2.title(translate="EPICS.CREATE.TITLE") + form( + ng-submit="vm.createEpic()" + ) + .subject-container + .color-selector + fieldset + tg-color-selector( + is-color-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" + name="status" + ng-model="vm.newEpic.status" + ng-options="s.id as s.name for s in vm.project.epic_statuses | orderBy:'order'" + ) + 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="{{'EPICS.CREATE.PLACEHOLDER_DESCRIPTION' | translate}}" + ng-model="vm.newEpic.description" + ) + fieldset + tg-attachments-simple( + attachments="vm.attachments" + on-add="vm.addAttachment(attachment)" + ) + .settings + fieldset.team-requirement + input( + type="checkbox" + name="team_requirement" + ng-model="vm.newEpic.team_requirement" + id="team-requirement" + ) + label.requirement.trans-button.e2e-create-epic-team-requirement( + for="team-requirement" + translate="EPICS.CREATE.TEAM_REQUIREMENT" + ) + fieldset.client-requirement + input( + type="checkbox" + name="client_requirement" + ng-model="vm.newEpic.client_requirement" + id="client-requirement" + ) + label.requirement.trans-button.e2e-create-epic-client-requirement( + for="client-requirement" + translate="EPICS.CREATE.CLIENT_REQUIREMENT" + ) + fieldset + input( + type="checkbox" + name="blocked" + ng-model="vm.newEpic.is_blocked" + id="blocked" + ng-click="displayBlockedReason = !displayBlockedReason" + ) + label.requirement.trans-button.blocked.e2e-create-epic-blocked( + for="blocked" + translate="EPICS.CREATE.BLOCKED" + ) + fieldset(ng-if="displayBlockedReason") + input.e2e-create-epic-blocked-note( + type="text" + name="blocked_note" + maxlength="140" + ng-model="vm.newEpic.blocked_note" + placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" + ) + fieldset + 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" + ) 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..e778e047 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.scss @@ -0,0 +1,75 @@ +.lightbox-create-epic { + align-items: center; + display: flex; + justify-content: center; + opacity: 1; + .create-epic-container { + max-width: 700px; + width: 90%; + } + .subject-container { + align-items: center; + display: flex; + .subject { + padding-left: 1rem; + width: 100%; + } + } + .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 new file mode 100644 index 00000000..76c8ca24 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -0,0 +1,82 @@ +### +# 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 +### + +class EpicRowController + @.$inject = [ + "$tgConfirm", + "tgProjectService", + "tgEpicsService" + ] + + 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() + + _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 + @.percentage = "0%" + else + @.percentage = "#{closed * 100 / total}%" + + canEditEpics: () -> + return @projectService.hasPermission("modify_epic") + + toggleUserStoryList: () -> + if !@.displayUserStories + @epicsService.listRelatedUserStories(@.epic) + .then (userStories) => + @.epicStories = userStories + @.displayUserStories = true + .catch => + @confirm.notify('error') + else + @.displayUserStories = false + + updateStatus: (statusId) -> + @.displayStatusList = false + @.loadingStatus = true + return @epicsService.updateEpicStatus(@.epic, statusId) + .catch () => + @confirm.notify('error') + .finally () => + @.loadingStatus = false + + updateAssignedTo: (member) -> + @.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.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee new file mode 100644 index 00000000..f3df7c64 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -0,0 +1,198 @@ +### +# 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 = {} + + _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 + _mockTgConfirm() + _mockTgProjectService() + _mockTgEpicsService() + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "calculate progress bar in open US", () -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 10, + closed: 10 + } + }) + } + + expect(ctrl.percentage).to.be.equal("50%") + + 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%") + + 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) -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + version: 1 + }) + } + + statusId = 1 + + promise = mocks.tgEpicsService.updateEpicStatus + .withArgs(ctrl.epic, statusId) + .promise() + .reject(new Error('error')) + + 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) -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } + + ctrl.displayUserStories = false + + data = Immutable.List() + + 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) -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } + + ctrl.displayUserStories = false + + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .reject(new Error('error')) + + ctrl.toggleUserStoryList().then () -> + expect(ctrl.displayUserStories).to.be.false + expect(mocks.tgConfirm.notify).have.been.calledWith('error') + done() + + it "display User Stories error", -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } + + ctrl.displayUserStories = true + + ctrl.toggleUserStoryList() + + expect(ctrl.displayUserStories).to.be.false 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..cf85a32b --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -0,0 +1,32 @@ +### +# 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 +### + +EpicRowDirective = () -> + return { + templateUrl:"epics/dashboard/epic-row/epic-row.html", + controller: "EpicRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + epic: '=', + column: '=' + } + } + +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 new file mode 100644 index 00000000..7497be1c --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -0,0 +1,86 @@ + +.epic-row.e2e-epic-row( + 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( + svg-icon="icon-drag" + ng-if="vm.canEditEpics()" + ) + + .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") + - var hash = "#"; + a( + 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( + 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") + + .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()" + on-assign-to="vm.updateAssignedTo(member)" + tg-isolate-click + ) + + .status( + ng-if="vm.column.status && !vm.canEditEpics()" + ) + span {{vm.epic.getIn(['status_extra_info', 'name'])}} + .status( + ng-if="vm.column.status && vm.canEditEpics()" + ng-mouseleave="vm.displayStatusList = false" + tg-isolate-click + ) + button( + ng-click="vm.displayStatusList = true" + ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" + tg-loading="vm.loadingStatus" + ) + 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.e2e-edit-epic-status( + ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" + 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-story(tg-repeat="story in vm.epicStories track by story.get('id')") + tg-story-row.e2e-story( + story="story" + 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 new file mode 100644 index 00000000..0f7205a0 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -0,0 +1,135 @@ +@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; + display: flex; + transition: background .2s; + &:hover { + background: rgba($primary-light, .05); + .icon-drag { + opacity: 1; + } + } + &.not-empty { + cursor: pointer; + } + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name a { + color: lighten($gray-light, 15%); + 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; + fill: $whitish; + 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-arrow-down { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .1rem; + } + .progress-bar, + .progress-status { + height: 1.5rem; + left: 0; + position: absolute; + top: .25rem; + } + .progress-bar { + background: $mass-white; + max-width: 40vw; + padding-right: 1rem; + width: 100%; + } + .progress-status { + background: $primary-light; + width: 10vw; + } + .vote { + color: $gray; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } + } + .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); + 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; + text-align: left; + top: 2.5rem; + width: 200px; + z-index: 99; + &:last-child { + border: 0; + } + li { + padding: .5rem; + &:hover { + color: $primary-light; + transition: color .3s linear; + } + } + } +} 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..040a1d47 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -0,0 +1,86 @@ +### +# 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 +### + +taiga = @.taiga + + +class EpicsDashboardController + @.$inject = [ + "$routeParams", + "tgErrorHandlingService", + "tgLightboxFactory", + "lightboxService", + "$tgConfirm", + "tgProjectService", + "tgEpicsService", + "tgAppMetaService", + "$translate" + ] + + constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService, + @confirm, @projectService, @epicsService, @appMetaService, @translate) -> + + @.sectionName = "EPICS.SECTION_NAME" + + taiga.defineImmutableProperty @, 'project', () => return @projectService.project + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics + + @appMetaService.setfn @._setMeta.bind(this) + + _setMeta: () -> + return null if !@.project + + ctx = { + projectName: @.project.get("name") + projectDescription: @.project.get("description") + } + + return { + title: @translate.instant("EPICS.PAGE_TITLE", ctx) + description: @translate.instant("EPICS.PAGE_DESCRIPTION", ctx) + } + + loadInitialData: () -> + @epicsService.clear() + return @projectService.setProjectBySlug(@params.pslug) + .then () => + if not @projectService.isEpicsDashboardEnabled() + return @errorHandlingService.notFound() + if not @projectService.hasPermission("view_epics") + return @errorHandlingService.permissionDenied() + + return @epicsService.fetchEpics() + + canCreateEpics: () -> + 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": onCreateEpic.bind(this) + }) + +angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController) 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..66f1f11a --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -0,0 +1,184 @@ +### +# 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", -> + provide = null + controller = null + mocks = {} + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgProjectService = () -> + mocks.tgProjectService = { + setProjectBySlug: sinon.stub() + hasPermission: sinon.stub() + isEpicsDashboardEnabled: sinon.stub() + project: Immutable.Map({ + "name": "testing name" + "description": "testing description" + }) + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + clear: sinon.stub() + fetchEpics: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + + _mockRouteParams = () -> + mocks.routeParams = { + pslug: sinon.stub() + } + + provide.value "$routeParams", mocks.routeParams + + _mockTgErrorHandlingService = () -> + mocks.tgErrorHandlingService = { + permissionDenied: sinon.stub() + notFound: 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 + + _mockTgAppMetaService = () -> + mocks.tgAppMetaService = { + setfn: sinon.stub() + } + + provide.value "tgAppMetaService", mocks.tgAppMetaService + + _mockTranslate = () -> + mocks.translate = sinon.stub() + + provide.value "$translate", mocks.translate + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgConfirm() + _mockTgProjectService() + _mockTgEpicsService() + _mockRouteParams() + _mockTgErrorHandlingService() + _mockTgLightboxFactory() + _mockLightboxService() + _mockTgAppMetaService() + _mockTranslate() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "metada is set", () -> + ctrl = controller("EpicsDashboardCtrl") + expect(mocks.tgAppMetaService.setfn).have.been.called + + it "load data because epics panel is enabled and user has permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + 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(mocks.tgErrorHandlingService.notFound).not.have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called + done() + + it "not load data because epics panel is not enabled and user has not permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + 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 new file mode 100644 index 00000000..46f751a3 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -0,0 +1,39 @@ +.wrapper(ng-init="vm.loadInitialData()") + tg-project-menu + section.main.epics(role="main") + header.header-with-actions + h1( + tg-main-title + project-name="vm.project.get('name')" + i18n-section-name="{{vm.sectionName}}" + ) + .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.epics.size" + ) + + section.empty-epics.empty-large(ng-if="!vm.epics.size") + img( + src="/#{v}/images/empty/empty_des.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/epics/what-is-an-epic/" + 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-dashboard.scss b/app/modules/epics/dashboard/epics-dashboard.scss new file mode 100644 index 00000000..159d1671 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.scss @@ -0,0 +1,8 @@ +.empty-epics { + text-align: center; + a { + color: $primary; + display: block; + margin-bottom: 2rem; + } +} 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..53063281 --- /dev/null +++ b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee @@ -0,0 +1,64 @@ +### +# 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, projectService) -> + link = (scope, el, attrs) -> + return if not projectService.hasPermission("modify_epic") + + 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", + "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 new file mode 100644 index 00000000..4934b4d9 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -0,0 +1,60 @@ +### +# 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 +### + +taiga = @.taiga + + +class EpicsTableController + @.$inject = [ + "$tgConfirm", + "tgEpicsService", + "$timeout" + ] + + constructor: (@confirm, @epicsService, @timeout) -> + @.displayOptions = false + @.displayVotes = true + @.column = { + votes: true, + name: true, + project: true, + sprint: true, + assigned: true, + status: true, + progress: true + } + + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics + + toggleEpicTableOptions: () -> + @.displayOptions = !@.displayOptions + + reorderEpic: (epic, newIndex) -> + @epicsService.reorderEpic(epic, newIndex) + .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.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee new file mode 100644 index 00000000..cdd83c6c --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -0,0 +1,57 @@ +### +# 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 = {} + + _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 -> + 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 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..f072a3e4 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -0,0 +1,29 @@ +### +# 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 +### + +EpicsTableDirective = () -> + return { + templateUrl:"epics/dashboard/epics-table/epics-table.html", + controller: "EpicsTableCtrl", + controllerAs: "vm", + scope: {} + } + + +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 new file mode 100644 index 00000000..fe99fcbd --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -0,0 +1,97 @@ +mixin epicSwitch(name, model) + div.check + input.activate-input( + id= name + name= name + type="checkbox" + ng-checked= model + ng-model= model + ) + div + span.check-text.check-yes(translate="COMMON.YES") + span.check-text.check-no(translate="COMMON.NO") + +.epics-table.e2e-epic-table + .epics-table-header.e2e-epics-table-header + .vote( + translate="EPICS.TABLE.VOTES" + ng-if="vm.column.votes" + ) + .name( + translate="EPICS.TABLE.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.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" + ng-mouseenter="vm.keepEpicTableOption()" + ) + .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.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(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" + 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 new file mode 100644 index 00000000..8814fbc0 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -0,0 +1,64 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + +.epics-table { + margin-top: 2rem; +} + +.epics-table-header { + @include epics-table; + @include font-type(bold); + border-bottom: 1px solid $gray-light; + display: flex; + padding: .5rem; + position: relative; + .project, + .assigned { + padding: 1rem .5rem; + } +} + +.epics-table-options-wrapper { + bottom: 1rem; + position: absolute; + right: .5rem; +} + +.epics-table-option-button { + @include font-type(light); + @include font-size(small); + background: none; + .icon { + @include svg-size(.7rem); + } +} + +.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.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; + color: $gray-light; + display: flex; + justify-content: space-between; + padding: .5rem 0; + &:last-child { + border: 0; + } + } +} 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..ce959248 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -0,0 +1,39 @@ +### +# 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: () -> + if @.story.get('is_closed') == true + @.percentage = "100%" + else + totalTasks = @.story.get('tasks').size + totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size + 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%") + 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..13195c0a --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.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') + +StoryRowDirective = () -> + return { + templateUrl:"epics/dashboard/story-row/story-row.html", + controller: "StoryRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + story: '=', + column: '=' + } + } + +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..ae5bee27 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -0,0 +1,47 @@ +.story-row( + ng-class="{'is-blocked': vm.story.get('is_blocked'), 'is-closed': vm.story.get('is_closed')}" +) + .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.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( + ng-if="vm.story.get('epics')" + format="pill" + epics="vm.story.get('epics')" + ) + .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( + 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')") + 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 + .progress-status( + ng-if="::vm.percentage" + ng-style="{'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..9ecc112c --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -0,0 +1,68 @@ +@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; + display: flex; + margin-left: 4rem; + transition: background .2s; + &:hover { + background: rgba($primary-light, .05); + } + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name { + color: $gray-light; + text-decoration: line-through; + } + } + .name { + flex-basis: 17.5vw; + a { + cursor: pointer; + } + } + .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; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } + } + .project { + cursor: pointer; + } + .project, + .assigned { + img { + width: 40px; + } + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } +} diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee new file mode 100644 index 00000000..4095bbf1 --- /dev/null +++ b/app/modules/epics/epics.service.coffee @@ -0,0 +1,121 @@ +### +# 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' + ] + + constructor: (@projectService, @attachmentsService, @resources, @xhrError) -> + @._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 = @projectService.project.get('id') + + return @resources.epics.post(epicData) + .then (epic) => + promises = _.map attachments.toJS(), (attachment) => + @attachmentsService.upload(attachment.file, epic.get('id'), epic.get('project'), 'epic') + + 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() + newOrder = if !previous then 0 else previous.get('epics_order') + 1 + + previousWithTheSameOrder = beforeDestination.filter (it) => + it.get('epics_order') == previous.get('epics_order') + setOrders = _.fromPairs previousWithTheSameOrder.map((it) => + [it.get('id'), it.get('epics_order')] + ).toJS() + + data = { + epics_order: newOrder, + version: epic.get('version') + } + return @resources.epics.reorder(epic.get('id'), data, setOrders) + .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 = _.fromPairs previousWithTheSameOrder.map((it) => + [it.get('id'), it.get('epic_order')] + ).toJS() + + 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, + 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/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 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..07319495 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -0,0 +1,48 @@ +### +# 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 = [ + "tgProjectService", + "tgEpicsService" + ] + + constructor: (@projectService, @epicsService) -> + @.sectionName = "Epics" + @.showCreateRelatedUserstoriesLightbox = false + + showRelatedUserStoriesSection: () -> + return @projectService.hasPermission("modify_epic") or @.userstories?.legth > 0 + + userCanSort: () -> + return @projectService.hasPermission("modify_epic") + + loadRelatedUserstories: () -> + @epicsService.listRelatedUserStories(@.epic) + .then (userstories) => + @.userstories = userstories + + 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-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..f5cabb13 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -0,0 +1,152 @@ +a.add-button.e2e-add-userstory-button( + href="" + ng-click="showLightbox(selectedProject)" +) + tg-svg(svg-icon="icon-add") + +.lightbox.lightbox-create-related-user-stories + tg-lightbox-close + + .lightbox-create-related-user-stories-wrapper + h2.title(translate="EPIC.CREATE_RELATED_USERSTORIES") + + .related-with-selector + .related-with-selector-single + 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}} + + .related-with-selector-single + 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}} + + fieldset.project-selector + label( + ng-if="relatedWithSelector=='new-user-story'" + translate="EPIC.CHOOSE_PROJECT_FOR_CREATION" + for="project-selector-dropdown" + ) + label( + ng-if="relatedWithSelector=='existing-user-story'" + translate="EPIC.CHOOSE_PROJECT_FROM" + for="project-selector-dropdown" + ) + select( + ng-model="selectedProject" + ng-change="selectProject(selectedProject)" + data-required="true" + ng-options="p.id as p.name for p in vm.projects | toMutable" + id="project-selector-dropdown" + ) + + fieldset(ng-show="relatedWithSelector=='new-user-story'") + .new-user-story-title + label( + ng-show="creationMode=='single-new-user-story'" + translate="EPIC.SUBJECT" + ) + + label( + ng-show="creationMode=='bulk-new-user-stories'" + translate="EPIC.SUBJECT_BULK_MODE" + ) + .new-user-story-options + .new-user-story-option-single + 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") + + .new-user-story-option-single + 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" + ) + + 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'" + ) + + p( + ng-show="relatedWithSelector=='existing-user-story' && !vm.projectUserstories.size" + translate="EPIC.NO_USERSTORIES" + ) + + fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story' && vm.projectUserstories.size") + 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" + ng-change="onUpdateSearchUserstory()" + ) + + form.existing-user-story-form + select.userstory.e2e-userstories-select( + size="5" + ng-model="selectedUserstory" + 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}} + + button.button-green.e2e-select-related-userstory-button( + href="" + ng-click="vm.saveRelatedUserStory(selectedUserstory, closeLightbox)" + tg-loading="vm.loading" + 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..1e397dbf --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss @@ -0,0 +1,83 @@ +.lightbox-create-related-user-stories { + .lightbox-create-related-user-stories-wrapper { + max-width: 600px; + width: 90%; + } + .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; + } + } + .related-with-selector-single { + flex: 1; + &:first-child { + margin-right: .5rem; + } + } + } + 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; + } + .new-user-story-options { + display: flex; + margin-left: auto; + input { + display: none; + &:checked+label { + background: $primary-light; + color: $white; + fill: $white; + transition: background .2s ease-in; + } + +label { + background: $mass-white; + color: $grayer; + cursor: pointer; + display: block; + padding: .5rem; + transition: background .2s ease-in; + } + +label:hover { + background: $primary-light; + color: $white; + fill: $white; + } + } + } + button { + width: 100%; + } +} 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 new file mode 100644 index 00000000..6c82137e --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -0,0 +1,107 @@ +### +# 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 = {} + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + listRelatedUserStories: sinon.stub() + reorderRelatedUserstory: sinon.stub() + } + + 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 -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "load related userstories", (done) -> + ctrl = controller "RelatedUserStoriesCtrl" + userstories = Immutable.fromJS([ + { + id: 1 + } + ]) + + ctrl.epic = Immutable.fromJS({ + id: 66 + }) + + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .resolve(userstories) + + 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.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..1cbbe21b --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -0,0 +1,28 @@ +section.related-userstories( + ng-if="vm.showRelatedUserStoriesSection()" +) + .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( + 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'), sortable: vm.userCanSort()}" + userstory="us" + epic="vm.epic" + project="vm.project" + load-related-userstories="vm.loadRelatedUserstories()" + tg-bind-scope + ) + + 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..67ba81a0 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.scss @@ -0,0 +1,39 @@ +.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%; +} 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..ba583d8a --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -0,0 +1,62 @@ +### +# 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_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_UNLINK_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..5b496b0e --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee @@ -0,0 +1,167 @@ +### +# 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_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(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_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(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..c54e09af --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -0,0 +1,51 @@ +tg-svg.icon-drag( + svg-icon="icon-drag" + tg-check-permission="modify_epic" +) + +.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-broken-link") + +.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/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..c9fc2f32 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -0,0 +1,118 @@ +tg-related-userstory-row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: .5rem 0 .5rem .5rem; + &.sortable { + cursor: move; + &: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; + height: 35px; + width: 35px; + } + } + .project { + cursor: pointer; + flex-basis: 100px; + img { + width: 40px; + } + } + .userstory-name { + display: flex; + flex: 1; + margin-right: 1rem; + a { + cursor: pointer; + } + span { + display: inline-block; + margin-left: .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/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.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 -> 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", 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-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") 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..89b69d87 --- /dev/null +++ b/app/modules/history/history/history-templates/history-color.jade @@ -0,0 +1,17 @@ +.diff-color-wrapper + span.key( + translate="ACTIVITY.FIELDS.COLOR" + ) + 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]" + 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; + } + } } 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..a547feb8 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() @@ -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/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'}, 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/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) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee new file mode 100644 index 00000000..293830b9 --- /dev/null +++ b/app/modules/resources/epics-resource.service.coffee @@ -0,0 +1,101 @@ +### +# 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.listInAllProjects = (params) -> + url = urlsService.resolve("epics") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + 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) + .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}" + + 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) + + params = { + user_story: userstoryId + epic: epicId + } + + 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) + + 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} + +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) -> diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index ce2b7cd4..d410036e 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,6 +33,35 @@ 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") + + params = { + 'epic': epicIid, + 'order_by': 'epic_order', + 'include_tasks': true + } + + return http.get(url, params) + .then (result) -> + return Immutable.fromJS(result.data) + return () -> return {"userstories": service} 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) 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..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 @@ -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,31 @@ 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 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' && + 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-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; 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: [ 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", []) 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 }} 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) 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", 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'", diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade new file mode 100644 index 00000000..52bb688c --- /dev/null +++ b/app/partials/epic/epic-detail.jade @@ -0,0 +1,128 @@ +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.epic-header-container(ng-class="{blocked: epic.is_blocked}") + tg-color-selector.color-selector( + is-color-required="true" + init-color="epic.color" + on-select-color="ctrl.onSelectColor(color)" + required-perm="modify_epic" + ) + tg-detail-header( + item="epic" + project="project" + required-perm="modify_epic" + 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/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index 0ed659b9..4a3a6143 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,12 @@ div.row.us-item-row( ) span(tg-bo-ref="us.ref") span(ng-bind="us.subject") - div.us-settings + tg-belong-to-epics( + format="pill" + 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/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") 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/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") diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 2692089e..dd339453 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" - ng-model="newValue" + tg-color-selector.color-column( + is-color-required="false" + init-color="newValue.color" + 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( @@ -77,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 ) @@ -115,17 +110,12 @@ 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" + init-color="tag.color" + 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/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..f794f480 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-epics-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) diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index d66c5ca7..f85bc3d6 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -19,6 +19,11 @@ 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( + format="pill" + 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/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 3ad5325b..1b7bad37 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -54,6 +54,11 @@ 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( + format="pill" + ng-if="us.epics" + epics="us.epics" + ) p.points-value span(ng-bind="us.total_points") span(translate="TASKBOARD.TABLE.FIELD_POINTS") diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 1b4185f0..e85c9a3d 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -17,52 +17,14 @@ 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" + format="text" + ) .subheader tg-tag-line.tags-block( ng-if="issue && project" diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 6ec12a6b..2a331a5b 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -26,54 +26,14 @@ 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" + type="text" + ) .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 8baf2bec..6c04fb9f 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -26,47 +26,14 @@ 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.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") + tg-detail-header.detail-header-container( + item="us" + project="project" + required-perm="modify_us" + ng-class="{blocked: us.is_blocked}" + ng-if="project && us" + type="text" + ) .subheader tg-tag-line.tags-block( ng-if="us && project" diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss new file mode 100644 index 00000000..75781d58 --- /dev/null +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -0,0 +1,55 @@ +@mixin epics-table { + .project, + .assigned { + padding: .5rem; + } + .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; + margin-right: 1rem; + position: relative; + } + .sprint { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; + } +} 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; + } } } diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 7021eca6..30af3d0a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -7,178 +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; - } - } - .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 { 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..040cadba 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; } @@ -80,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; } @@ -182,7 +181,6 @@ padding: 0 4px; } .us-name { - @include ellipsis(230px); display: block; &.closed { color: lighten($gray-light, 5%); 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/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%; + } +} diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index 8fa6cc2a..869e7c00 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,13 @@ Fill + + Epics + + + + Broken Link + + 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/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/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/detail-helper.js b/e2e/helpers/detail-helper.js index 1148d21c..a315edbb 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(); } }; @@ -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-dashboard-helper.js b/e2e/helpers/epics-dashboard-helper.js new file mode 100644 index 00000000..787acd1c --- /dev/null +++ b/e2e/helpers/epics-dashboard-helper.js @@ -0,0 +1,200 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.epic = function() { + let el = $$('.e2e-epic'); + + 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() { + 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(); + 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(); + await browser.waitForAngular(); + return el.get(0).$('.e2e-assigned-to-image').getAttribute("alt"); + }, + 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: 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: async function() { + $('.e2e-epics-column-button').click(); + utils.common.takeScreenshot("epics", "epics-edit-columns"); + $$('.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/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/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/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/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/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/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'); diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js new file mode 100644 index 00000000..9eb5d606 --- /dev/null +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -0,0 +1,76 @@ +var utils = require('../../utils'); +var epicsDashboardHelper = require('../../helpers').epicsDashboard; + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe('Epics Dashboard', function(){ + let epicsUrl = ''; + + before(async function(){ + await utils.nav + .init() + .project('Project Example 0') + .epics() + .go(); + + epicsUrl = await browser.getCurrentUrl(); + }); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "dashboard"); + }); + + it('display child stories', async function() { + 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 = epicsDashboardHelper.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 = epicsDashboardHelper.epic(); + await epic.resetAssignedTo(); + let unAssigned = await epic.removeAssignedTo(); + expect(unAssigned).to.be.equal('Unassigned'); + }); + + it('change status from dashboard', async function() { + let epic = epicsDashboardHelper.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 = epicsDashboardHelper.epic(); + let currentColumns = await epic.getColumns(); + await epic.removeColumns(); + let newColumns = await epic.getColumns(); + expect(currentColumns).to.be.above(newColumns); + }); + +}) 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 3712f716..b3c32daa 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -46,6 +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()); @@ -101,6 +116,14 @@ 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; + }, + 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',