* team customized labels added

* team editor interface minor redesign
This commit is contained in:
Dávid Danyi 2018-09-06 15:38:50 +02:00
parent 0f535881a4
commit ea05a47086
8 changed files with 144 additions and 55 deletions

57
src/app/admin/team-editor/team-editor.component.html Normal file → Executable file
View File

@ -11,22 +11,33 @@
<input id="filter_id" type="number" name="filter_id" [(ngModel)]="team.filterId">
</div>
</div>
<h5 class="ui header">1st column</h5>
<div class="six wide field">
<label for="team_name"> </label>
<div class="ui checkbox">
<input type="checkbox" id="team_is_active" name="team_is_active"
[(ngModel)]="team.isActive">
<label for="team_is_active">Active</label>
</div>
</div>
<h4 class="ui dividing header">Column configuration</h4>
<div class="four fields">
<div class="six wide field">
<label>Jira status</label>
<input type="text" name="column1_js"
placeholder="Jira column name" [(ngModel)]="team.backlogColumn.jiraStatusName">
</div>
<div class="four wide field">
<label>Display name</label>
<input type="text" name="column1_l"
placeholder="Kanban board header" [(ngModel)]="team.backlogColumn.label">
</div>
<div class="two wide field">
<label>WIP limit</label>
<input type="text" name="column1_wip"
placeholder="WIP limit" [(ngModel)]="team.backlogColumn.wipLimit">
</div>
</div>
<h5 class="ui header">2nd column</h5>
<div class="four fields">
<div class="six wide field">
<input type="text" name="column2_js"
@ -41,7 +52,6 @@
placeholder="WIP limit" [(ngModel)]="team.inprogressColumn.wipLimit">
</div>
</div>
<h5 class="ui header">3rd column</h5>
<div class="four fields">
<div class="six wide field">
<input type="text" name="column3_js"
@ -56,7 +66,6 @@
placeholder="WIP limit" [(ngModel)]="team.verificationColumn.wipLimit">
</div>
</div>
<h5 class="ui header">4th column</h5>
<div class="four fields">
<div class="six wide field">
<input type="text" name="column4_js"
@ -72,13 +81,40 @@
</div>
</div>
<div class="six wide field">
<label for="team_name"> </label>
<div class="ui checkbox">
<input type="checkbox" id="team_is_active" name="team_is_active"
[(ngModel)]="team.isActive">
<label for="team_is_active">Active</label>
<h4 class="ui dividing header">Labels</h4>
<div class="three inline fields">
<div class="two wide field">
<button type="button" class="ui fluid button"
[class.positive]="canAddLabel"
[class.disabled]="!canAddLabel"
(keydown.enter)="handleEnter($event)"
(click)="addLabel()">Add
</button>
</div>
<div class="five wide field">
<input type="text" #labelInput
name="label_name"
placeholder="Label text"
(keydown.enter)="handleLabelEnter($event)"
[(ngModel)]="label.name">
</div>
<ng-template let-option #optionTemplate>
<span class="ui tiny {{option}} label">{{option}}</span>
</ng-template>
<sui-select class="ui right floated selection"
id="label_color"
name="label_color"
[(ngModel)]="label.color"
[optionTemplate]="optionTemplate"
[isSearchable]="false"
#labelSelect>
<sui-select-option *ngFor="let labelColor of labelColors" [value]="labelColor"></sui-select-option>
</sui-select>
</div>
<div>
<span *ngFor="let label of team.labels"
class="ui medium {{label.color}} label">{{label.name}}<i class="large delete icon"
(click)="removeLabel(label)"></i></span>
</div>
<h4 class="ui dividing header">Team members</h4>
@ -106,6 +142,7 @@
[(ngModel)]="member.name">
</div>
</div>
<h4 class="ui dividing header"></h4>
<table class="ui celled definition table" *ngIf="team.members.length">
<thead>

59
src/app/admin/team-editor/team-editor.component.ts Normal file → Executable file
View File

@ -1,11 +1,11 @@
import { Component, ElementRef, HostBinding, OnInit, ViewChild } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TeamService } from '../../shared/service/team.service';
import { Team } from '../../shared/team';
import { Member } from '../../shared/member';
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
import { Label } from '../../shared/label';
@Component({
selector: 'app-team-editor',
@ -13,8 +13,10 @@ import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
styleUrls: ['./team-editor.component.css']
})
export class TeamEditorComponent implements OnInit {
@ViewChild('labelInput') labelInputElement: ElementRef;
@ViewChild('signumInput') signumInputElement: ElementRef;
public team: Team;
public label: Label = new Label();
public member: Member = new Member();
constructor(private teamService: TeamService,
@ -28,6 +30,57 @@ export class TeamEditorComponent implements OnInit {
this.route.data.subscribe((data: { team: Team }) => this.team = data.team ? data.team : new Team());
}
get labelColors(): Array<string> {
return [
'red',
'orange',
'yellow',
'olive',
'green',
'teal',
'blue',
'violet',
'purple',
'pink',
'brown',
'grey',
'black',
'white'
];
}
get canAddLabel(): boolean {
try {
return [this.label.name, this.label.color].every(field => field.length !== 0)
&& this.team.labels.every(label => label.name !== this.label.name);
} catch (e) {
return false;
}
}
public addLabel() {
this.team.labels = this.team.labels
.concat(Object.assign({}, this.label))
.sort((a: Label, b: Label) => a.name < b.name ? -1 : 1);
this.label = new Label();
}
public removeLabel(label: Label) {
this.team.labels = this.team.labels.filter(teamLabel => teamLabel !== label);
}
public handleLabelEnter(ev: KeyboardEvent) {
ev.preventDefault();
if (this.canAddLabel) {
this.addLabel();
this.focusLabelField();
}
}
public focusLabelField() {
this.labelInputElement.nativeElement.focus();
}
get canAddMember(): boolean {
try {
return [this.member.name, this.member.signum].every(field => field.length !== 0)
@ -66,7 +119,7 @@ export class TeamEditorComponent implements OnInit {
return [
this.team.name.trim(),
this.team.members
].every(field => field.length > 0);
].every(field => field.length > 0) && this.team.filterId > 0;
}
public saveTeam() {

View File

@ -12,10 +12,8 @@
<div class="task-description">
<span *ngIf="kanbanEntry.epicName"
class="ui mini olive right floated label">{{kanbanEntry.epicName}}</span>
<ng-template [ngIf]="hasLabels(kanbanEntry)">
<span *ngFor="let label of kanbanEntry.labels"
class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span>
</ng-template>
<span *ngFor="let label of filteredLabels(kanbanEntry)"
class="ui mini {{labelClass(label)}} right floated label">{{label|blockedDays:kanbanEntry.daysBlocked}}</span>
<span *ngIf="wasBlocked(kanbanEntry)" class="ui mini {{labelClass('blocked')}} right floated label">{{kanbanEntry.daysBlocked}}D</span>
<div *ngIf="!hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated image">
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}" [title]="kanbanEntry.assignee?.name">

View File

@ -2,23 +2,11 @@ import { Component, Input } from '@angular/core';
import { environment } from '../../../environments/environment';
import { JiraAssignee, KanbanEntry } from '../shared';
import { SettingsService } from '../../shared/service/settings.service';
const DEFAULT_AVATAR = '/assets/riddler.png';
const JIRA_BOARD_BASE_HREF = 'https://cc-jira.rnd.ki.sw.ericsson.se/browse/';
const labelColors = {
TSP: 'teal',
MTAS: 'orange',
INTERNAL: 'yellow',
TEAM: 'yellow',
BLOCKED: 'red',
SPIKE: 'purple',
EXPEDITE: 'pink',
'MTAS-GUARDIAN': 'pink',
'MTAS-GUARDIANACTIVE': 'yellow',
};
@Component({
selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
templateUrl: './kanban-entry-item.component.html',
@ -30,8 +18,7 @@ export class KanbanEntryItemComponent {
@Input() wipLimit = 0;
@Input() wipCount = 0;
constructor() {
}
constructor(private settingService: SettingsService) {}
/**
* Returns the full url of the assignee avatar,
@ -41,17 +28,9 @@ export class KanbanEntryItemComponent {
* @returns {string}
*/
public avatarUrl(avatarPath: string): string {
return environment.apiUrl + (avatarPath ? avatarPath : DEFAULT_AVATAR);
}
/**
* Returns true if issue has any labels attached
*
* @param {KanbanEntry} entry
* @returns {boolean}
*/
public hasLabels(entry: KanbanEntry): boolean {
return entry.labels.length > 0;
return environment.apiUrl + (avatarPath
? avatarPath
: DEFAULT_AVATAR);
}
/**
@ -61,11 +40,16 @@ export class KanbanEntryItemComponent {
* @returns {string}
*/
public labelClass(label: string): string {
try {
return labelColors[label.toUpperCase()];
} catch (e) {
return 'white';
if (this.settingService.team.labels) {
const color = this.settingService.team.labels.find(
teamLabel => teamLabel.name.toLocaleLowerCase() === label.toLocaleLowerCase()
).color;
if (color !== null) {
return color;
}
}
return 'white';
}
/**
@ -80,6 +64,15 @@ export class KanbanEntryItemComponent {
};
}
public filteredLabels(kanbanEntry: KanbanEntry): Array<string> {
if (this.settingService.team.labels) {
return kanbanEntry.labels.filter(entryLabel => this.settingService.team.labels.some(
teamLabel => teamLabel.name.toLocaleLowerCase() === entryLabel.toLocaleLowerCase()
));
}
return [];
}
/**
* Generate jira issue href
*
@ -122,4 +115,5 @@ export class KanbanEntryItemComponent {
public getAssignees(kanbanEntry: KanbanEntry): Array<JiraAssignee> {
return [].concat([kanbanEntry.assignee], kanbanEntry.additionalAssignees);
}
}

View File

@ -12,6 +12,7 @@
:host.preview {
position: absolute;
z-index: 1;
padding: 30px;
}
iframe {
width: 100%;

View File

@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { Slide } from '../shared/slide';
import { SlideService } from '../shared/service/slide.service';
import { Router } from '@angular/router';
import { SettingsService } from '../shared/service/settings.service';
import {Injectable} from '@angular/core';
import {Slide, SlideVisibility} from '../shared/slide';
import {SlideService} from '../shared/service/slide.service';
import {Router} from '@angular/router';
import {SettingsService} from '../shared/service/settings.service';
@Injectable()
export class SlideShowService {
@ -39,7 +39,7 @@ export class SlideShowService {
const team = this.settingsService.team;
this.slideService.list().subscribe(
slides => this.slides = slides.filter(
slide => slide.teams === null || slide.teams.some(s => s.id === team.id) && slide.isVisible
slide => slide.isVisible && (slide.visibility === SlideVisibility.Public || slide.teams.some(s => s.id === team.id))
)
);
}

4
src/app/shared/label.ts Executable file
View File

@ -0,0 +1,4 @@
export class Label {
name: string;
color: string;
}

6
src/app/shared/team.ts Normal file → Executable file
View File

@ -1,16 +1,18 @@
import { Member } from './member';
import { KanbanColumn } from './kanban-column';
import { Label } from './label';
export class Team {
id: number = null;
name: String = '';
members: Array<Member> = [];
filterId: number;
filterId = 0;
backlogColumn: KanbanColumn = new KanbanColumn();
inprogressColumn: KanbanColumn = new KanbanColumn();
verificationColumn: KanbanColumn = new KanbanColumn();
doneColumn: KanbanColumn = new KanbanColumn();
isActive = false;
labels: Array<Label> = [];
isActive = true;
createdAt: String = null;
updatedAt: String = null;
}