From 6386dd57b235076224117d85de7345b9f2823ac3 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 10 May 2016 13:17:10 +0200 Subject: [PATCH] Admin area for tags --- CHANGELOG.md | 1 + app/coffee/app.coffee | 7 +- .../modules/admin/project-values.coffee | 81 ++++++++++++++++--- app/coffee/modules/base.coffee | 1 + app/locales/taiga/locale-en.json | 9 ++- .../admin/admin-project-values-tags.jade | 67 +++++++++++++++ .../modules/admin-submenu-project-values.jade | 4 + app/styles/layout/admin-project-tags.scss | 20 +++++ app/styles/layout/admin-project-values.scss | 9 +++ app/styles/modules/common/colors-table.scss | 76 ++++++++++------- e2e/helpers/admin-attributes-helper.js | 24 ++++++ e2e/suites/admin/attributes/tags.e2e.js | 56 +++++++++++++ 12 files changed, 316 insertions(+), 39 deletions(-) create mode 100644 app/partials/admin/admin-project-values-tags.jade create mode 100644 app/styles/layout/admin-project-tags.scss create mode 100644 e2e/suites/admin/attributes/tags.e2e.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c7263396..c8b29a01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Errors (not found, server error, permissions and blocked project) don't change the current url. - Attachments image slider +- New admin area to edit the tag colors used in your project ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 5a0b7370..10413ddc 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -289,7 +289,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven section: "admin" } ) - + $routeProvider.when("/project/:pslug/admin/project-values/tags", + { + templateUrl: "admin/admin-project-values-tags.html", + section: "admin" + } + ) $routeProvider.when("/project/:pslug/admin/memberships", { templateUrl: "admin/admin-memberships.html", diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index d76149d0..ba9533d1 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -266,14 +266,6 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra editionRow.removeClass('hidden') editionRow.find('input:visible').first().focus().select() - $el.on "keyup", ".edition input", (event) -> - if event.keyCode == 13 - target = angular.element(event.currentTarget) - saveValue(target) - else if event.keyCode == 27 - target = angular.element(event.currentTarget) - cancel(target) - $el.on "keyup", ".new-value input", (event) -> if event.keyCode == 13 target = $el.find(".new-value") @@ -349,7 +341,7 @@ ColorSelectionDirective = () -> event.preventDefault() event.stopPropagation() target = angular.element(event.currentTarget) - $el.find(".select-color").hide() + $(".select-color").hide() target.siblings(".select-color").show() # Hide when click outside body = angular.element("body") @@ -372,6 +364,15 @@ ColorSelectionDirective = () -> $model.$modelValue.color = $scope.color $el.find(".select-color").hide() + $el.on "keyup", "input", (event) -> + if event.keyCode == 13 + $scope.$apply -> + $model.$modelValue.color = $scope.color + $el.find(".select-color").hide() + + else if event.keyCode == 27 + $el.find(".select-color").hide() + $scope.$on "$destroy", -> $el.off() @@ -689,3 +690,65 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame, $translate) module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", "$translate", ProjectCustomAttributesDirective]) + + +############################################################################# +## Tags Controller +############################################################################# + +class ProjectTagsController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "tgAppMetaService", + "$translate" + ] + + constructor: (@scope, @rootscope, @repo, @appMetaService, @translate) -> + @.loading = true + @rootscope.$on "project:loaded", => + sectionName = @translate.instant(@scope.sectionName) + title = @translate.instant("ADMIN.CUSTOM_ATTRIBUTES.PAGE_TITLE", { + "sectionName": sectionName, + "projectName": @scope.project.name + }) + description = @scope.project.description + @appMetaService.setAll(title, description) + + @.loading = false + @.tagNames = Object.keys(@scope.project.tags_colors).sort() + @scope.projectTags = _.map(@.tagNames, (tagName) => {name: tagName, color: @scope.project.tags_colors[tagName]}) + + updateTag: (tag) -> + tags_colors = angular.copy(@scope.project.tags_colors) + tags_colors[tag.name] = tag.color + @scope.project.tags_colors = tags_colors + return @repo.save(@scope.project) + +module.controller("ProjectTagsController", ProjectTagsController) + + +############################################################################# +## Tags Directive +############################################################################# + +ProjectTagDirective = () -> + link = ($scope, $el, $attrs) -> + $el.color = $scope.tag.color + $ctrl = $el.controller() + + $scope.$on "$destroy", -> + $el.off() + + $scope.$watch "tag.color", (newColor) => + if $el.color != newColor + promise = $ctrl.updateTag($scope.tag) + promise.then null, (data) -> + form.setErrors(data) + + $el.color = newColor + + return {link: link} + +module.directive("tgProjectTag", [ProjectTagDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 2806d182..e593f3c6 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -99,6 +99,7 @@ urls = { "project-admin-project-values-severities": "/project/:project/admin/project-values/severities" "project-admin-project-values-types": "/project/:project/admin/project-values/types" "project-admin-project-values-custom-fields": "/project/:project/admin/project-values/custom-fields" + "project-admin-project-values-tags": "/project/:project/admin/project-values/tags" "project-admin-memberships": "/project/:project/admin/memberships" "project-admin-roles": "/project/:project/admin/roles" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index bf80f4f2..8904a654 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -557,6 +557,12 @@ "ISSUE_TITLE": "Issues types", "ACTION_ADD": "Add new {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "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" + }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", "WARNING_NO_ROLE": "Be careful, no role in your project will be able to estimate the point value for user stories", @@ -682,7 +688,8 @@ "PRIORITIES": "Priorities", "SEVERITIES": "Severities", "TYPES": "Types", - "CUSTOM_FIELDS": "Custom fields" + "CUSTOM_FIELDS": "Custom fields", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Project Profile" diff --git a/app/partials/admin/admin-project-values-tags.jade b/app/partials/admin/admin-project-values-tags.jade new file mode 100644 index 00000000..fc52290b --- /dev/null +++ b/app/partials/admin/admin-project-values-tags.jade @@ -0,0 +1,67 @@ +doctype html + +div.wrapper( + ng-controller="ProjectValuesSectionController", + ng-init="sectionName='ADMIN.PROJECT_VALUES_TAGS.TITLE'" +) + + tg-project-menu + + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") + include ../includes/modules/admin-menu + + sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags") + include ../includes/modules/admin-submenu-project-values + + section.main.admin-common.admin-attributes.colors-table + include ../includes/components/mainTitle + p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE") + + .admin-attributes-section( + ng-controller="ProjectTagsController as ctrl" + ) + .admin-attributes-section-wrapper-empty( + ng-if="!projectTags.length" + tg-loading="ctrl.loading" + ) + p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") + .admin-attributes-section-wrapper( + ng-if="projectTags.length" + ) + .table-header.table-tags-editor + .row + .color-column(translate="COMMON.FIELDS.COLOR") + .color-name(translate="COMMON.FIELDS.NAME") + .color-filter + input.e2e-tags-filter( + type="text" + name="name" + ng-model="tagsFilter.name" + ng-model-options="{debounce: 200}" + ) + tg-svg( + svg-icon="icon-search" + ) + + p.admin-attributes-section-wrapper-empty( + tg-loading="ctrl.loading" + translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH" + ng-if="!(projectTags | filter:tagsFilter).length" + ) + .table-main( + ng-repeat="tag in projectTags | filter:tagsFilter" + tg-bind-scope + ) + form( + tg-project-tag + ng-model="tag" + ) + .row.edition.no-draggable + .color-column( + tg-color-selection + ng-model="tag" + ) + .current-color(ng-style="{background: tag.color}") + include ../includes/components/select-color + + .color-name {{ tag.name }} diff --git a/app/partials/includes/modules/admin-submenu-project-values.jade b/app/partials/includes/modules/admin-submenu-project-values.jade index 00533831..bd0c71c3 100644 --- a/app/partials/includes/modules/admin-submenu-project-values.jade +++ b/app/partials/includes/modules/admin-submenu-project-values.jade @@ -24,3 +24,7 @@ section.admin-submenu li#adminmenu-values-custom-fields a(href="", tg-nav="project-admin-project-values-custom-fields:project=project.slug") span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.CUSTOM_FIELDS") + + li#adminmenu-values-tags + a(href="", tg-nav="project-admin-project-values-tags:project=project.slug") + span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.TAGS") diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss new file mode 100644 index 00000000..f341214f --- /dev/null +++ b/app/styles/layout/admin-project-tags.scss @@ -0,0 +1,20 @@ +.table-tags-editor { + input[type="text"] { + background-color: transparent; + border: 0; + border-bottom: 1px solid transparent; + box-shadow: none; + transition: border-bottom .2s linear; + &:focus { + border-bottom: 1px solid $gray; + outline: none; + } + } + .color-filter { + align-items: center; + display: flex; + flex-grow: 1; + padding: 0 10px; + position: relative; + } +} diff --git a/app/styles/layout/admin-project-values.scss b/app/styles/layout/admin-project-values.scss index 7474d016..c0863739 100644 --- a/app/styles/layout/admin-project-values.scss +++ b/app/styles/layout/admin-project-values.scss @@ -11,6 +11,15 @@ width: 100%; } } + .admin-attributes-section-wrapper-empty { + color: $gray-light; + padding: 10vh 0 0; + text-align: center; + } + .loading-spinner { + max-height: 3rem; + max-width: 3rem; + } } } diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index 872bbbeb..bc5d0d36 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -8,32 +8,8 @@ } .row { padding-left: 50px; - } - } - .table-main { - .row:hover { - background: lighten($primary, 60%); - cursor: move; - transition: background .2s ease-in; - .icon { - opacity: 1; - transition: opacity .2s ease-in; - } - .options-column { - opacity: 1; - transition: opacity .3s linear; - } - } - .options-column { - a { - display: inline-block; - } - } - } - form { - &:last-child { - .row { - border: 0; + &:hover { + background: transparent; } } } @@ -58,6 +34,25 @@ &.hidden { display: none; } + &:hover { + background: lighten($primary, 60%); + cursor: move; + transition: background .2s ease-in; + .icon { + opacity: 1; + transition: opacity .2s ease-in; + } + .options-column { + opacity: 1; + transition: opacity .3s linear; + } + } + &.no-draggable { + padding-left: 50px; + &:hover { + cursor: auto; + } + } .color-column { flex-basis: 60px; flex-grow: 1; @@ -70,10 +65,13 @@ .status-wip-limit { flex-basis: 100px; flex-grow: 1; + flex-shrink: 0; } - .status-name { + .status-name, + .color-name { flex-basis: 150px; - flex-grow: 6; + flex-grow: 1; + flex-shrink: 0; padding: 0 10px; position: relative; span { @@ -81,6 +79,9 @@ display: block; } } + .color-name { + flex-basis: 100px; + } .status-slug { flex-basis: 150px; flex-grow: 6; @@ -105,7 +106,21 @@ padding: 0 0 0 10px; text-align: center; } + } + .options-column { + a { + display: inline-block; + } + } + form { + &:last-child { + .row { + border: 0; + } + } + } + .row-edit { .options-column { opacity: 1; @@ -132,6 +147,11 @@ fill: $primary; opacity: 1; } + &.icon-search { + cursor: none; + fill: $primary; + opacity: 1; + } &.icon-drag { cursor: move; } diff --git a/e2e/helpers/admin-attributes-helper.js b/e2e/helpers/admin-attributes-helper.js index dd742b5f..7590053f 100644 --- a/e2e/helpers/admin-attributes-helper.js +++ b/e2e/helpers/admin-attributes-helper.js @@ -34,6 +34,22 @@ helper.getSection = function(item) { }; }; +helper.getTagsSection = function(item) { + let section = $$('.admin-attributes-section').get(item); + + return { + el: section, + rows: function() { + return section.$$('.table-main > div'); + } + }; +}; + + +helper.getTagsFilter = function() { + return $('.table-header .e2e-tags-filter'); +} + helper.getStatusNames = function(section) { return section.$$('.status-name span').getText(); }; @@ -94,6 +110,14 @@ helper.getGenericForm = function(form) { return form.$('.status-name input'); }; + obj.colorBox = function() { + return form.$('.edition .current-color'); + } + + obj.colorText = function() { + return form.$('.select-color input'); + } + return obj; }; diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js new file mode 100644 index 00000000..8f3641c3 --- /dev/null +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -0,0 +1,56 @@ +var utils = require('../../../utils'); + +var adminAttributesHelper = require('../../../helpers').adminAttributes; + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe.only('attributes - tags', function() { + before(async function(){ + browser.get(browser.params.glob.host + 'project/project-0/admin/project-values/tags'); + + await adminAttributesHelper.waitLoad(); + + utils.common.takeScreenshot('attributes', 'tags'); + }); + + it('edit', async function() { + let section = adminAttributesHelper.getTagsSection(0); + let rows = section.rows(); + let row = rows.get(0); + + let form = adminAttributesHelper.getGenericForm(row.$('form')); + + var colorBox = form.colorBox(); + await colorBox.click(); + await form.colorText().clear(); + await form.colorText().sendKeys('#000000'); + await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + + await browser.waitForAngular(); + + section = adminAttributesHelper.getTagsSection(0); + rows = section.rows(); + row = rows.get(0); + let backgroundColor = await row.$$('.edition .current-color').get(0).getCssValue('background-color'); + expect(backgroundColor).to.be.equal('rgba(0, 0, 0, 1)'); + utils.common.takeScreenshot('attributes', 'tag edited is black'); + }); + + it('filter', async function() { + let tagsFilter = adminAttributesHelper.getTagsFilter(); + await tagsFilter.clear(); + await tagsFilter.sendKeys('ad'); + await browser.waitForAngular(); + + let section = adminAttributesHelper.getTagsSection(0); + let rows = section.rows(); + let count = await rows.count(); + expect(count).to.be.equal(2); + }); + + +});