Admin area for tags

stable
Alejandro Alonso 2016-05-10 13:17:10 +02:00 committed by Juanfran
parent 527713985b
commit 6386dd57b2
12 changed files with 316 additions and 39 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
};

View File

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