* basic canban board now functional with wip limit display and auto refresh every 5minutes
This commit is contained in:
parent
f7bb463bd4
commit
4f64981cd6
58
deploy.php
Normal file
58
deploy.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Deployer;
|
||||||
|
require 'recipe/common.php';
|
||||||
|
|
||||||
|
set('ssh_type', 'native');
|
||||||
|
set('ssh_multiplexing', true);
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
|
||||||
|
// set('repository', 'git@domain.com:username/repository.git');
|
||||||
|
set('shared_files', []);
|
||||||
|
set('shared_dirs', []);
|
||||||
|
set('writable_dirs', []);
|
||||||
|
set('keep_releases', 3);
|
||||||
|
set('default_stage', 'production');
|
||||||
|
|
||||||
|
// Servers
|
||||||
|
server('vasgyuro', 'vasgyuro.tsp')
|
||||||
|
->stage('production')
|
||||||
|
->user('edvidan')
|
||||||
|
->forwardAgent()
|
||||||
|
->set('ng_basehref', '/taurus-tv/')
|
||||||
|
->set('ng_target', 'production')
|
||||||
|
->set('ng_environment', 'prod')
|
||||||
|
->set('env_vars', 'NODE_ENV=production')
|
||||||
|
->set('deploy_path', '/home/edvidan/applications/taurus-tv');
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
desc('Prepare release');
|
||||||
|
task('deploy:ng-prepare', function() {
|
||||||
|
runLocally("ng build --base-href={{ng_basehref}} --target={{ng_target}} --environment={{ng_environment}}");
|
||||||
|
runLocally("tar -cJf dist.tar.xz dist");
|
||||||
|
});
|
||||||
|
|
||||||
|
desc('Upload release');
|
||||||
|
task('deploy:ng-upload', function() {
|
||||||
|
upload("dist.tar.xz", "{{release_path}}/dist.tar.xz");
|
||||||
|
run("tar -C {{release_path}} -xJf {{release_path}}/dist.tar.xz");
|
||||||
|
run("rm -f {{release_path}}/dist.tar.xz");
|
||||||
|
runLocally("rm -rf dist.tar.xz dist");
|
||||||
|
upload("htaccess", "{{release_path}}/dist/.htaccess");
|
||||||
|
});
|
||||||
|
|
||||||
|
desc('Deploy your project');
|
||||||
|
task('deploy', [
|
||||||
|
'deploy:prepare',
|
||||||
|
'deploy:lock',
|
||||||
|
'deploy:release',
|
||||||
|
'deploy:ng-prepare',
|
||||||
|
'deploy:ng-upload',
|
||||||
|
'deploy:shared',
|
||||||
|
'deploy:clear_paths',
|
||||||
|
'deploy:symlink',
|
||||||
|
'deploy:unlock',
|
||||||
|
'cleanup',
|
||||||
|
]);
|
||||||
|
after('deploy', 'success');
|
||||||
24
htaccess
Normal file
24
htaccess
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
# The following rule tells Apache that if the requested filename
|
||||||
|
# exists, simply serve it.
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
|
RewriteRule ^.*$ - [NC,L]
|
||||||
|
|
||||||
|
# The following rewrites all other queries to index.php. The
|
||||||
|
# condition ensures that if you are using Apache aliases to do
|
||||||
|
# mass virtual hosting, the base path will be prepended to
|
||||||
|
# allow proper resolution of the index.php file; it will work
|
||||||
|
# in non-aliased environments as well, providing a safe, one-size
|
||||||
|
# fits all solution.
|
||||||
|
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||||
|
RewriteRule ^(.*) - [E=BASE:%1]
|
||||||
|
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
|
||||||
|
|
||||||
|
<Limit GET POST PUT DELETE HEAD OPTIONS>
|
||||||
|
Require all granted
|
||||||
|
</Limit>
|
||||||
|
<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
||||||
|
Require all denied
|
||||||
|
</LimitExcept>
|
||||||
22
semantic.json
Normal file
22
semantic.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"base": "semantic/",
|
||||||
|
"paths": {
|
||||||
|
"source": {
|
||||||
|
"config": "src/theme.config",
|
||||||
|
"definitions": "src/definitions/",
|
||||||
|
"site": "src/site/",
|
||||||
|
"themes": "src/themes/"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"packaged": "dist/",
|
||||||
|
"uncompressed": "dist/components/",
|
||||||
|
"compressed": "dist/components/",
|
||||||
|
"themes": "dist/themes/"
|
||||||
|
},
|
||||||
|
"clean": "dist/"
|
||||||
|
},
|
||||||
|
"permission": false,
|
||||||
|
"autoInstall": true,
|
||||||
|
"rtl": false,
|
||||||
|
"version": "2.2.11"
|
||||||
|
}
|
||||||
@ -1,10 +1,31 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
|
import {Subscription} from "rxjs/Subscription";
|
||||||
|
import {TimerObservable} from "rxjs/observable/TimerObservable";
|
||||||
|
|
||||||
|
import {KanbanService} from "./kanban/shared/kanban.service";
|
||||||
|
|
||||||
|
const RENEW_TIMER_INITIAL = 300000;
|
||||||
|
const RENEW_TIMER_PERIOD = 300000;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit, OnDestroy{
|
||||||
title = 'app';
|
title = 'app';
|
||||||
|
authRenewTimer: Subscription;
|
||||||
|
|
||||||
|
constructor( private kanbanService: KanbanService ) {}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
let timer = TimerObservable.create(RENEW_TIMER_INITIAL, RENEW_TIMER_PERIOD);
|
||||||
|
this.authRenewTimer = timer.subscribe(() => {
|
||||||
|
this.kanbanService.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.authRenewTimer.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
<div class="ui main fullwide-container">
|
<div class="ui main fullwide-container">
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div class="six wide column">
|
<div app-kanban-entry-item class="four wide column"
|
||||||
<app-kanban-entry-item [kanbanEntries]="kanbanBoard.inbox"></app-kanban-entry-item>
|
[kanbanEntries]="kanbanBoard.inbox"></div>
|
||||||
</div>
|
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
||||||
<div class="six wide column">
|
[kanbanEntries]="kanbanBoard.inProgress"></div>
|
||||||
<app-kanban-entry-item [kanbanEntries]="kanbanBoard.inProgress"></app-kanban-entry-item>
|
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
||||||
</div>
|
[kanbanEntries]="kanbanBoard.verification"></div>
|
||||||
<div class="four wide column">
|
<div app-kanban-entry-item class="four wide column"
|
||||||
<app-kanban-entry-item [kanbanEntries]="kanbanBoard.verification"></app-kanban-entry-item>
|
[kanbanEntries]="kanbanBoard.done"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,6 +5,10 @@ import {ActivatedRoute} from '@angular/router';
|
|||||||
import {
|
import {
|
||||||
KanbanBoard
|
KanbanBoard
|
||||||
} from "../shared";
|
} from "../shared";
|
||||||
|
import {KanbanService} from "../shared/kanban.service";
|
||||||
|
|
||||||
|
const WIP_LIMIT_INPROGRESS = 12;
|
||||||
|
const WIP_LIMIT_VERIFICATION = 8;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kanban-board',
|
selector: 'app-kanban-board',
|
||||||
@ -13,10 +17,9 @@ import {
|
|||||||
})
|
})
|
||||||
export class KanbanBoardComponent implements OnInit {
|
export class KanbanBoardComponent implements OnInit {
|
||||||
|
|
||||||
public kanbanBoard: KanbanBoard = new KanbanBoard;
|
|
||||||
|
|
||||||
constructor(private titleService: Title,
|
constructor(private titleService: Title,
|
||||||
private route: ActivatedRoute) {
|
private route: ActivatedRoute,
|
||||||
|
private kanbanService: KanbanService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -24,4 +27,23 @@ export class KanbanBoardComponent implements OnInit {
|
|||||||
this.route.data.subscribe((data: { kanbanBoard: KanbanBoard }) => this.kanbanBoard = data.kanbanBoard);
|
this.route.data.subscribe((data: { kanbanBoard: KanbanBoard }) => this.kanbanBoard = data.kanbanBoard);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get kanbanBoard(): KanbanBoard {
|
||||||
|
return this.kanbanService.kanbanBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
set kanbanBoard(kanbanBoard: KanbanBoard) {
|
||||||
|
this.kanbanService.kanbanBoard = kanbanBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
get inprogressWipClass() {
|
||||||
|
return {
|
||||||
|
'over-wip': this.kanbanBoard.inProgress.length > WIP_LIMIT_INPROGRESS,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get verificationWipClass() {
|
||||||
|
return {
|
||||||
|
'over-wip': this.kanbanBoard.verification.length > WIP_LIMIT_VERIFICATION,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
.task-description {
|
||||||
|
font-size: 14pt;
|
||||||
|
line-height: 1.25em;
|
||||||
|
/*text-align: justify;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.jira-avatar.image {
|
||||||
|
width: 45px;
|
||||||
|
height: auto;
|
||||||
|
/*font-size: 1em;*/
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.jira-avatar.image > img {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.divided.items > .item:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.divided.items > .item {
|
||||||
|
border-top: 1px solid rgba(250, 250, 250, 0.25);
|
||||||
|
}
|
||||||
@ -1,15 +1,11 @@
|
|||||||
<div class="ui divided items">
|
<div class="ui divided items">
|
||||||
<div class="item" *ngFor="let kanbanEntry of kanbanEntries">
|
<div class="item" *ngFor="let kanbanEntry of kanbanEntries">
|
||||||
<div class="ui tiny image">
|
|
||||||
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}">
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<a class="header">{{kanbanEntry.key}}</a>
|
<div class="task-description">
|
||||||
<div class="description">
|
<div class="ui jira-avatar floated image">
|
||||||
<p>{{kanbanEntry.summary}}</p>
|
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="extra">
|
<span [innerHTML]="kanbanEntry.summary|priorityColor"></span>
|
||||||
Additional Details
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import {Component, Input, OnInit} from '@angular/core';
|
|||||||
import {environment} from "../../../environments/environment";
|
import {environment} from "../../../environments/environment";
|
||||||
import {KanbanEntry} from "../shared/kanban-entry.model";
|
import {KanbanEntry} from "../shared/kanban-entry.model";
|
||||||
|
|
||||||
|
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kanban-entry-item',
|
selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
|
||||||
templateUrl: './kanban-entry-item.component.html',
|
templateUrl: './kanban-entry-item.component.html',
|
||||||
styleUrls: ['./kanban-entry-item.component.css']
|
styleUrls: ['./kanban-entry-item.component.css']
|
||||||
})
|
})
|
||||||
@ -16,10 +18,6 @@ export class KanbanEntryItemComponent implements OnInit {
|
|||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
public avatarUrl(avatarPath: string): string {
|
public avatarUrl(avatarPath: string): string {
|
||||||
try {
|
return environment.apiUri + ( avatarPath ? avatarPath : DEFAULT_AVATAR );
|
||||||
return environment.apiUri + avatarPath;
|
|
||||||
} catch (e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
|||||||
import { KanbanService } from './shared/kanban.service';
|
import { KanbanService } from './shared/kanban.service';
|
||||||
import { KanbanEntryItemComponent } from './kanban-entry-item/kanban-entry-item.component';
|
import { KanbanEntryItemComponent } from './kanban-entry-item/kanban-entry-item.component';
|
||||||
import { KanbanEntryCardComponent } from './kanban-entry-card/kanban-entry-card.component';
|
import { KanbanEntryCardComponent } from './kanban-entry-card/kanban-entry-card.component';
|
||||||
|
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -17,6 +18,7 @@ import { KanbanEntryCardComponent } from './kanban-entry-card/kanban-entry-card.
|
|||||||
KanbanBoardComponent,
|
KanbanBoardComponent,
|
||||||
KanbanEntryItemComponent,
|
KanbanEntryItemComponent,
|
||||||
KanbanEntryCardComponent,
|
KanbanEntryCardComponent,
|
||||||
|
PriorityColorPipe,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
KanbanService,
|
KanbanService,
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import {
|
|||||||
export class KanbanService {
|
export class KanbanService {
|
||||||
private url = environment.apiUri + '/api/kanban';
|
private url = environment.apiUri + '/api/kanban';
|
||||||
|
|
||||||
|
private cachedKanbanBoard: KanbanBoard = new KanbanBoard();
|
||||||
|
|
||||||
constructor(private httpService: Http) {}
|
constructor(private httpService: Http) {}
|
||||||
|
|
||||||
public getList(): Observable<KanbanBoard> {
|
public getList(): Observable<KanbanBoard> {
|
||||||
@ -22,4 +24,17 @@ export class KanbanService {
|
|||||||
public resolve(route: ActivatedRouteSnapshot): Promise<KanbanBoard> {
|
public resolve(route: ActivatedRouteSnapshot): Promise<KanbanBoard> {
|
||||||
return this.getList().toPromise().then(result => result ? result : false);
|
return this.getList().toPromise().then(result => result ? result : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reload() {
|
||||||
|
this.getList().subscribe(result => this.cachedKanbanBoard = result);
|
||||||
|
}
|
||||||
|
|
||||||
|
get kanbanBoard(): KanbanBoard {
|
||||||
|
return this.cachedKanbanBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
set kanbanBoard(kanbanBoard: KanbanBoard) {
|
||||||
|
this.cachedKanbanBoard = kanbanBoard;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/kanban/shared/priority-color.pipe.spec.ts
Normal file
8
src/app/kanban/shared/priority-color.pipe.spec.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { PriorityColorPipe } from './priority-color.pipe';
|
||||||
|
|
||||||
|
describe('PriorityColorPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new PriorityColorPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
32
src/app/kanban/shared/priority-color.pipe.ts
Normal file
32
src/app/kanban/shared/priority-color.pipe.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'priorityColor'
|
||||||
|
})
|
||||||
|
export class PriorityColorPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: any, args?: any): any {
|
||||||
|
let mhrMatch = /(\[.*mhr\])/ig;
|
||||||
|
value = value.replace(mhrMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
|
return `<span class="match-mhr">${mhrMatched}</span> `;
|
||||||
|
});
|
||||||
|
let sMatch = /(\[s\])/ig;
|
||||||
|
value = value.replace(sMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
|
return `<span class="match-s">${mhrMatched}</span> `;
|
||||||
|
});
|
||||||
|
let mMatch = /(\[m\])/ig;
|
||||||
|
value = value.replace(mMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
|
return `<span class="match-m">${mhrMatched}</span> `;
|
||||||
|
});
|
||||||
|
let lMatch = /(\[l\])/ig;
|
||||||
|
value = value.replace(lMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
|
return `<span class="match-l">${mhrMatched}</span> `;
|
||||||
|
});
|
||||||
|
let xlMatch = /(\[xl\])/ig;
|
||||||
|
value = value.replace(xlMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
|
return `<span class="match-xl">${mhrMatched}</span> `;
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,9 +1,42 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
body {
|
body {
|
||||||
background-color: #FFFFFF;
|
background-color: #303030 !important;
|
||||||
|
color: #cccccc !important;
|
||||||
|
margin-top: 1em !important;
|
||||||
|
margin-bottom: 1em !important;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.fullwide-container {
|
.ui.fullwide-container {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.match-mhr {
|
||||||
|
text-justify: none;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-s {
|
||||||
|
text-justify: none;
|
||||||
|
color: #00b5ad;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-m {
|
||||||
|
text-justify: none;
|
||||||
|
color: #0ea432;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-l {
|
||||||
|
text-justify: none;
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-xl {
|
||||||
|
text-justify: none;
|
||||||
|
color: coral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.over-wip {
|
||||||
|
background-color: rgba(194,59,34, 0.3);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user