Compare commits
22 Commits
emakazi_de
...
tspinfo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e346f84eeb | ||
|
|
5f2639061b | ||
|
|
41c057c5e5 | ||
|
|
04aa27bd2e | ||
|
|
82184678b9 | ||
|
|
cc8a3ddd03 | ||
|
|
f4ce7ec951 | ||
|
|
9f1d5d4d9a | ||
|
|
0d628456c4 | ||
|
|
6494c5c75c | ||
|
|
d37a6f68c6 | ||
|
|
97d946a324 | ||
|
|
59b463c3b2 | ||
|
|
985383797e | ||
|
|
7e82c57b0e | ||
|
|
c61d1c8f13 | ||
|
|
990dbfa565 | ||
|
|
4704a6a0d1 | ||
|
|
6fc65e54d5 | ||
|
|
e434523925 | ||
|
|
c07ef0efcb | ||
|
|
0ed37b025e |
@@ -9,7 +9,7 @@
|
|||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"assets": [
|
"assets": [
|
||||||
"assets",
|
"assets",
|
||||||
"favicon.ico"
|
"favicon.png"
|
||||||
],
|
],
|
||||||
"index": "index.html",
|
"index": "index.html",
|
||||||
"main": "main.ts",
|
"main": "main.ts",
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -1,28 +1,36 @@
|
|||||||
# TaurusTv
|
# TaurusTv
|
||||||
|
|
||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.6.
|
## Tools used for development
|
||||||
|
|
||||||
## Development server
|
- [deployer](https://deployer.org/download)
|
||||||
|
- [nodeJS](https://nodejs.org/en/download/)
|
||||||
|
- [php7.1](http://php.net) - [installation on ELX](https://www.colinodell.com/blog/2016-12/installing-php-7-1)
|
||||||
|
- [phpStorm](https://www.jetbrains.com/phpstorm/download/)
|
||||||
|
|
||||||
|
Install the latest versions, add them to your $PATH.
|
||||||
|
|
||||||
|
## Prepare for development
|
||||||
|
|
||||||
|
### Install required node packages
|
||||||
|
```bash
|
||||||
|
sudo npm -g i typescript gulp @angular/cli
|
||||||
|
# Inside your project root
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Run `dep deploy` in the project root, to deploy the application to `vasgyuro.tsp`. The application will automatically reload on the TV when a new version is deployed.
|
||||||
|
|
||||||
|
## Angular cli
|
||||||
|
### Development server
|
||||||
|
|
||||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
## Code scaffolding
|
### Code scaffolding
|
||||||
|
|
||||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
|
||||||
|
|
||||||
## Build
|
### Further help
|
||||||
|
|
||||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
|
||||||
|
|
||||||
## Running unit tests
|
|
||||||
|
|
||||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
||||||
|
|
||||||
## Running end-to-end tests
|
|
||||||
|
|
||||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
|
||||||
Before running the tests make sure you are serving the app via `ng serve`.
|
|
||||||
|
|
||||||
## Further help
|
|
||||||
|
|
||||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||||
|
|||||||
8
htaccess
8
htaccess
@@ -22,3 +22,11 @@ RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
|
|||||||
<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
||||||
Require all denied
|
Require all denied
|
||||||
</LimitExcept>
|
</LimitExcept>
|
||||||
|
|
||||||
|
<Files revision.json>
|
||||||
|
FileETag None
|
||||||
|
Header unset ETag
|
||||||
|
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
|
||||||
|
Header set Pragma "no-cache"
|
||||||
|
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
|
||||||
|
</Files>
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -3246,6 +3246,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gify-parse": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/gify-parse/-/gify-parse-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-NHRHheiiBLUhtJNtMFBwpqU0fos=",
|
||||||
|
"requires": {
|
||||||
|
"commander": "2.11.0",
|
||||||
|
"jdataview": "2.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||||
@@ -5560,6 +5569,11 @@
|
|||||||
"integrity": "sha1-2llSddGuYx3nNqwKfH2Fyfc+9lI=",
|
"integrity": "sha1-2llSddGuYx3nNqwKfH2Fyfc+9lI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"jdataview": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jdataview/-/jdataview-2.5.0.tgz",
|
||||||
|
"integrity": "sha1-MIGz/qZR+TF+xr1P6y3cmKpB1ZU="
|
||||||
|
},
|
||||||
"jquery": {
|
"jquery": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@angular/platform-browser-dynamic": "^4.0.0",
|
"@angular/platform-browser-dynamic": "^4.0.0",
|
||||||
"@angular/router": "^4.0.0",
|
"@angular/router": "^4.0.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
|
"gify-parse": "^1.0.6",
|
||||||
"rxjs": "^5.4.1",
|
"rxjs": "^5.4.1",
|
||||||
"semantic-ui": "^2.2.11",
|
"semantic-ui": "^2.2.11",
|
||||||
"zone.js": "^0.8.14"
|
"zone.js": "^0.8.14"
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {Routes, RouterModule} from '@angular/router';
|
import {Routes, RouterModule} from '@angular/router';
|
||||||
|
|
||||||
import { KanbanService } from "./kanban/shared"
|
|
||||||
import { KanbanBoardComponent } from "./kanban/kanban-board/kanban-board.component"
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: '/kanban',
|
redirectTo: '/kanban-fixed',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},{
|
|
||||||
path: 'kanban',
|
|
||||||
children: [],
|
|
||||||
component: KanbanBoardComponent,
|
|
||||||
resolve: {
|
|
||||||
kanbanBoard: KanbanService,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
|
|||||||
import {Subscription} from "rxjs/Subscription";
|
import {Subscription} from "rxjs/Subscription";
|
||||||
import {TimerObservable} from "rxjs/observable/TimerObservable";
|
import {TimerObservable} from "rxjs/observable/TimerObservable";
|
||||||
|
|
||||||
import {KanbanService} from "./kanban/shared/kanban.service";
|
import {KanbanService} from "./kanban/shared";
|
||||||
import {Router} from "@angular/router";
|
|
||||||
import {SelfUpdaterService} from "./kanban/shared/self-updater.service";
|
import {SelfUpdaterService} from "./kanban/shared/self-updater.service";
|
||||||
|
|
||||||
const TIMER_DEPLOY_REFRESH = 30000;
|
const TIMER_DEPLOY_REFRESH = 30000;
|
||||||
const TIMER_JIRA_REFRESH = 60000;
|
const TIMER_JIRA_REFRESH = 60000;
|
||||||
const TIMER_PAGE_SWITCH = 300000;
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -18,36 +15,32 @@ const TIMER_PAGE_SWITCH = 300000;
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
selfUpdateCheckerTimer: Subscription;
|
private selfUpdateCheckerTimer: Subscription;
|
||||||
reloadJiraIssueTimer: Subscription;
|
private reloadKanbanTimer: Subscription;
|
||||||
pageSwitchTimer: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(private selfUpdaterService: SelfUpdaterService,
|
||||||
private selfUpdaterService: SelfUpdaterService,
|
private kanbanService: KanbanService,
|
||||||
private kanbanService: KanbanService,
|
|
||||||
private router: Router
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize application timers:
|
||||||
|
* - selfUpdateCheckerTimer is used to see if there is a newer revision deployed on the server
|
||||||
|
* - reloadKanbanTimer is used to refresh the status of the jira board
|
||||||
|
* - pageSwitchTimer handles switching back and forth between page views
|
||||||
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
let timer0 = TimerObservable.create(TIMER_DEPLOY_REFRESH, TIMER_JIRA_REFRESH);
|
let timer0 = TimerObservable.create(TIMER_DEPLOY_REFRESH, TIMER_JIRA_REFRESH);
|
||||||
this.selfUpdateCheckerTimer = timer0.subscribe(() => {
|
this.selfUpdateCheckerTimer = timer0.subscribe(() => {
|
||||||
this.selfUpdaterService.checkAndReloadIfNecessary();
|
this.selfUpdaterService.checkAndReloadIfNecessary();
|
||||||
});
|
});
|
||||||
let timer1 = TimerObservable.create(TIMER_JIRA_REFRESH, TIMER_JIRA_REFRESH);
|
let timer1 = TimerObservable.create(TIMER_JIRA_REFRESH, TIMER_JIRA_REFRESH);
|
||||||
this.reloadJiraIssueTimer = timer1.subscribe(() => {
|
this.reloadKanbanTimer = timer1.subscribe(() => {
|
||||||
this.kanbanService.reload();
|
this.kanbanService.reload();
|
||||||
});
|
});
|
||||||
let timer2 = TimerObservable.create(TIMER_PAGE_SWITCH, TIMER_PAGE_SWITCH);
|
|
||||||
this.pageSwitchTimer = timer2.subscribe(() => {
|
|
||||||
// navigate to next page
|
|
||||||
// this.router.navigate();
|
|
||||||
console.log("pageSwitch");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.selfUpdateCheckerTimer.unsubscribe();
|
this.selfUpdateCheckerTimer.unsubscribe();
|
||||||
this.reloadJiraIssueTimer.unsubscribe();
|
this.reloadKanbanTimer.unsubscribe();
|
||||||
this.pageSwitchTimer.unsubscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import { KanbanModule } from './kanban/kanban.module';
|
|||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
KanbanModule
|
KanbanModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<div class="ui main fullwide-container">
|
<div class="ui main fullwide-container">
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div app-kanban-entry-item class="four wide column"
|
<div app-kanban-entry-item class="four wide column"
|
||||||
rowHeading="INBOX" [kanbanEntries]="kanbanBoard.inbox"></div>
|
rowHeading="INBOX"
|
||||||
|
[kanbanEntries]="kanbanBoard.inbox"></div>
|
||||||
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
||||||
rowHeading="INPROGRESS" [kanbanEntries]="kanbanBoard.inProgress"></div>
|
rowHeading="INPROGRESS"
|
||||||
|
[wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount"
|
||||||
|
[kanbanEntries]="kanbanBoard.inProgress"></div>
|
||||||
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
||||||
rowHeading="VERIFICATION" [kanbanEntries]="kanbanBoard.verification"></div>
|
rowHeading="VERIFICATION"
|
||||||
|
[wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount"
|
||||||
|
[kanbanEntries]="kanbanBoard.verification"></div>
|
||||||
<div app-kanban-entry-item class="four wide column"
|
<div app-kanban-entry-item class="four wide column"
|
||||||
rowHeading="DÖNER" [kanbanEntries]="kanbanBoard.done"></div>
|
rowHeading="DÖNER"
|
||||||
|
[kanbanEntries]="kanbanBoard.done"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, HostBinding, HostListener, OnInit} from '@angular/core';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
KanbanBoard
|
KanbanBoard,
|
||||||
|
KanbanService,
|
||||||
|
KanbanEntry,
|
||||||
} from "../shared";
|
} from "../shared";
|
||||||
import {KanbanService} from "../shared/kanban.service";
|
|
||||||
|
|
||||||
const WIP_LIMIT_INPROGRESS = 12;
|
const WIP_LIMIT_INPROGRESS = 12;
|
||||||
const WIP_LIMIT_VERIFICATION = 8;
|
const WIP_LIMIT_VERIFICATION = 8;
|
||||||
|
|
||||||
|
const STYLE_HIDDEN = 'hidden';
|
||||||
|
const STYLE_VISIBLE = 'scroll';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kanban-board',
|
selector: 'app-kanban-board',
|
||||||
templateUrl: './kanban-board.component.html',
|
templateUrl: './kanban-board.component.html',
|
||||||
@@ -17,14 +21,23 @@ const WIP_LIMIT_VERIFICATION = 8;
|
|||||||
})
|
})
|
||||||
export class KanbanBoardComponent implements OnInit {
|
export class KanbanBoardComponent implements OnInit {
|
||||||
|
|
||||||
|
@HostBinding('style.overflow') hostOverflow = STYLE_HIDDEN;
|
||||||
|
|
||||||
constructor(private titleService: Title,
|
constructor(private titleService: Title,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private kanbanService: KanbanService) {
|
private kanbanService: KanbanService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set page title, and handle preloaded kanbanBoard data
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.titleService.setTitle('TaurusXFT : Kanban board');
|
this.titleService.setTitle('TaurusXFT : Kanban board');
|
||||||
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 {
|
get kanbanBoard(): KanbanBoard {
|
||||||
@@ -35,15 +48,61 @@ export class KanbanBoardComponent implements OnInit {
|
|||||||
this.kanbanService.kanbanBoard = kanbanBoard;
|
this.kanbanService.kanbanBoard = kanbanBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get inprogressWipLimit(): number {
|
||||||
|
return WIP_LIMIT_INPROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
get inprogressWipCount(): number {
|
||||||
|
return this.kanbanBoard.inProgress.filter(
|
||||||
|
(entry: KanbanEntry) => entry.labels.every(
|
||||||
|
label => label.toUpperCase() != 'BLOCKED'
|
||||||
|
)
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 'over-wip' class on inprogress row if its over the wip limit
|
||||||
|
* This excludes issues marked as BLOCKED with labels
|
||||||
|
*
|
||||||
|
* @returns {{over-wip: boolean}}
|
||||||
|
*/
|
||||||
get inprogressWipClass() {
|
get inprogressWipClass() {
|
||||||
return {
|
return {
|
||||||
'over-wip': this.kanbanBoard.inProgress.length > WIP_LIMIT_INPROGRESS,
|
'over-wip': this.inprogressWipCount > WIP_LIMIT_INPROGRESS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get verificationWipLimit(): number {
|
||||||
|
return WIP_LIMIT_VERIFICATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
get verificationWipCount(): number {
|
||||||
|
return this.kanbanBoard.verification.filter(
|
||||||
|
(entry: KanbanEntry) => entry.labels.every(
|
||||||
|
label => label.toUpperCase() != 'BLOCKED'
|
||||||
|
)
|
||||||
|
).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 'over-wip' class on verification row if its over the wip limit
|
||||||
|
* This excludes issues marked as BLOCKED with labels
|
||||||
|
*
|
||||||
|
* @returns {{over-wip: boolean}}
|
||||||
|
*/
|
||||||
get verificationWipClass() {
|
get verificationWipClass() {
|
||||||
return {
|
return {
|
||||||
'over-wip': this.kanbanBoard.verification.length > WIP_LIMIT_VERIFICATION,
|
'over-wip': this.verificationWipCount > WIP_LIMIT_VERIFICATION,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('mouseover')
|
||||||
|
private onMouseOver() {
|
||||||
|
this.hostOverflow = STYLE_VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('mouseout')
|
||||||
|
private onMouseOut() {
|
||||||
|
this.hostOverflow = STYLE_HIDDEN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
a {
|
||||||
|
color: #eeeeee !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #4183C4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.task-description {
|
.task-description {
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
@@ -30,22 +38,62 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui.divided.items > .item.blocker.bottom-separator {
|
.ui.divided.items > .item.blocker.bottom-separator {
|
||||||
border-bottom: 1px solid rgba(219, 40, 40, 0.5);
|
border-bottom: 3px double rgba(219, 40, 40, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.divided.items > .item.critical.bottom-separator {
|
.ui.divided.items > .item.critical.bottom-separator {
|
||||||
border-bottom: 1px solid rgba(242, 113, 28, 0.5);
|
border-bottom: 3px double rgba(242, 113, 28, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.divided.items > .item.major.bottom-separator {
|
.ui.divided.items > .item.major.bottom-separator {
|
||||||
border-bottom: 1px solid rgba(181, 204, 24, 0.5);
|
border-bottom: 3px double rgba(181, 204, 24, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.divided.items > .item.minor.bottom-separator {
|
.ui.divided.items > .item.minor.bottom-separator {
|
||||||
border-bottom: 1px solid rgba(0, 181, 173, 0.5);
|
border-bottom: 3px double rgba(0, 181, 173, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Nothing below trivial, no separator needed*/
|
/*Nothing below trivial, no separator needed*/
|
||||||
/*.ui.divided.items > .item.trivial.bottom-separator {*/
|
/*.ui.divided.items > .item.trivial.bottom-separator {*/
|
||||||
/*border-bottom: 1px solid rgba(181, 204, 24, 0.5);*/
|
/*border-bottom: 1px solid rgba(181, 204, 24, 0.5);*/
|
||||||
/*}*/
|
/*}*/
|
||||||
|
|
||||||
|
.ui.images {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.images .image {
|
||||||
|
margin: 0;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2 images */
|
||||||
|
.ui.two.images .image {
|
||||||
|
width: 22px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3 images */
|
||||||
|
.ui.three.images .image {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
.ui.three.images .image:first-child {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.ui.three.images .image:not(:first-child) {
|
||||||
|
width: 22px;
|
||||||
|
margin-top: -14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4 images */
|
||||||
|
.ui.four.images .image {
|
||||||
|
height: 22px;
|
||||||
|
width: 22px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
<h1>{{rowHeading}}</h1>
|
<h1>
|
||||||
|
{{rowHeading}}
|
||||||
|
<ng-template [ngIf]="wipLimit">
|
||||||
|
- {{wipCount}}/{{wipLimit}}
|
||||||
|
</ng-template>
|
||||||
|
</h1>
|
||||||
<div class="ui divided items">
|
<div class="ui divided items">
|
||||||
<div class="item {{kanbanEntry.issuePriority|lowercase}}" [ngClass]="entryClass(kanbanEntry)" *ngFor="let kanbanEntry of kanbanEntries">
|
<div *ngFor="let kanbanEntry of kanbanEntries"
|
||||||
|
class="item {{kanbanEntry.issuePriority|lowercase}}"
|
||||||
|
[ngClass]="entryClass(kanbanEntry)">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="task-description">
|
<div class="task-description">
|
||||||
|
<span *ngIf="kanbanEntry.epicName"
|
||||||
|
class="ui mini olive right floated label">{{kanbanEntry.epicName}}</span>
|
||||||
<ng-template [ngIf]="hasLabels(kanbanEntry)">
|
<ng-template [ngIf]="hasLabels(kanbanEntry)">
|
||||||
<a *ngFor="let label of kanbanEntry.labels" class="ui mini {{labelClass(label)}} right floated label">{{label}}</a>
|
<span *ngFor="let label of kanbanEntry.labels"
|
||||||
|
class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="ui jira-avatar floated image">
|
<span *ngIf="wasBlocked(kanbanEntry)" class="ui mini {{labelClass('blocked')}} right floated label">{{kanbanEntry.daysBlocked}}D</span>
|
||||||
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}">
|
<div *ngIf="!hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated image">
|
||||||
|
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}" [title]="kanbanEntry.assignee?.name">
|
||||||
</div>
|
</div>
|
||||||
<span [innerHTML]="kanbanEntry.summary|shortenText|priorityColor:kanbanEntry.issuePriorityIcon:kanbanEntry.worklog" [title]="kanbanEntry.summary"></span>
|
<div *ngIf="hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated {{assigneeCount(kanbanEntry)}} images">
|
||||||
|
<img *ngFor="let assignee of getAssignees(kanbanEntry)" class="image"
|
||||||
|
src="{{avatarUrl(assignee?.avatar)}}"
|
||||||
|
[title]="assignee?.name">
|
||||||
|
</div>
|
||||||
|
<a [href]="jiraHref(kanbanEntry)" target="_blank"
|
||||||
|
[innerHTML]="kanbanEntry.summary|shortenText|priorityColor:kanbanEntry.issuePriorityIcon:kanbanEntry.worklog|prefixJiraId:kanbanEntry.key"
|
||||||
|
[title]="kanbanEntry.summary"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import {Component, Input, OnInit} from '@angular/core';
|
import {Component, Input} from '@angular/core';
|
||||||
|
|
||||||
import {environment} from "../../../environments/environment";
|
import { environment } from "../../../environments/environment";
|
||||||
import {KanbanEntry} from "../shared/kanban-entry.model";
|
import { KanbanEntry } from "../shared";
|
||||||
|
import { JiraAssignee } from "../shared";
|
||||||
|
|
||||||
const DEFAULT_AVATAR = '/assets/riddler.png';
|
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||||
|
const JIRA_BOARD_BASE_HREF = 'https://cc-jira.rnd.ki.sw.ericsson.se/browse/';
|
||||||
|
|
||||||
const labelColors = {
|
const labelColors = {
|
||||||
TSP: 'teal',
|
TSP: 'teal',
|
||||||
MTAS: 'orange',
|
MTAS: 'orange',
|
||||||
Internal: 'yellow',
|
INTERNAL: 'yellow',
|
||||||
Team: 'yellow',
|
TEAM: 'yellow',
|
||||||
BLOCKED: 'red',
|
BLOCKED: 'red',
|
||||||
|
SPIKE: 'purple',
|
||||||
|
EXPEDITE: 'pink',
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -18,33 +22,98 @@ const labelColors = {
|
|||||||
templateUrl: './kanban-entry-item.component.html',
|
templateUrl: './kanban-entry-item.component.html',
|
||||||
styleUrls: ['./kanban-entry-item.component.css']
|
styleUrls: ['./kanban-entry-item.component.css']
|
||||||
})
|
})
|
||||||
export class KanbanEntryItemComponent implements OnInit {
|
export class KanbanEntryItemComponent {
|
||||||
@Input() kanbanEntries: Array<KanbanEntry>;
|
@Input() kanbanEntries: Array<KanbanEntry>;
|
||||||
@Input() rowHeading: string = "";
|
@Input() rowHeading: string = "";
|
||||||
|
@Input() wipLimit: number = 0;
|
||||||
|
@Input() wipCount: number = 0;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
ngOnInit() {}
|
/**
|
||||||
|
* Returns the full url of the assignee avatar,
|
||||||
|
* or the default riddler avatar if there is no assignee
|
||||||
|
*
|
||||||
|
* @param {string} avatarPath
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
public avatarUrl(avatarPath: string): string {
|
public avatarUrl(avatarPath: string): string {
|
||||||
return environment.apiUri + ( avatarPath ? avatarPath : DEFAULT_AVATAR );
|
return environment.apiUri + ( avatarPath ? avatarPath : DEFAULT_AVATAR );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if issue has any labels attached
|
||||||
|
*
|
||||||
|
* @param {KanbanEntry} entry
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
public hasLabels(entry: KanbanEntry): boolean {
|
public hasLabels(entry: KanbanEntry): boolean {
|
||||||
return entry.labels.length > 0;
|
return entry.labels.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set label colors
|
||||||
|
*
|
||||||
|
* @param {string} label
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
public labelClass(label: string): string {
|
public labelClass(label: string): string {
|
||||||
try {
|
try {
|
||||||
return labelColors[label];
|
return labelColors[label.toUpperCase()];
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return 'white';
|
return 'white';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add 'bottom-separator' class to every item that is the last of its priority type
|
||||||
|
*
|
||||||
|
* @param {KanbanEntry} entry
|
||||||
|
* @returns {{bottom-separator: boolean}}
|
||||||
|
*/
|
||||||
public entryClass(entry: KanbanEntry) {
|
public entryClass(entry: KanbanEntry) {
|
||||||
return {
|
return {
|
||||||
'bottom-separator': entry.isLastOfPriority,
|
'bottom-separator': entry.isLastOfPriority,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate jira issue href
|
||||||
|
*
|
||||||
|
* @param {KanbanEntry} kanbanEntry
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public jiraHref(kanbanEntry: KanbanEntry): string {
|
||||||
|
return JIRA_BOARD_BASE_HREF + kanbanEntry.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if issue has blocked days logged at any point of time,
|
||||||
|
* but is not in BLOCKED state any more.
|
||||||
|
*
|
||||||
|
* @param {KanbanEntry} kanbanEntry
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
public wasBlocked(kanbanEntry: KanbanEntry): boolean {
|
||||||
|
return kanbanEntry.daysBlocked > 0
|
||||||
|
&& kanbanEntry.labels.every(label => label.toUpperCase() != 'BLOCKED');
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasMultiAssignee(kanbanEntry: KanbanEntry): boolean {
|
||||||
|
return kanbanEntry.additionalAssignees.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public assigneeCount(kanbanEntry: KanbanEntry): string {
|
||||||
|
let count = 1 + kanbanEntry.additionalAssignees.length;
|
||||||
|
switch (count) {
|
||||||
|
case 2: return "two";
|
||||||
|
case 3: return "three";
|
||||||
|
case 4:
|
||||||
|
default: return "four";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAssignees(kanbanEntry: KanbanEntry): Array<JiraAssignee> {
|
||||||
|
return [].concat([kanbanEntry.assignee], kanbanEntry.additionalAssignees);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/app/kanban/kanban-routing.module.ts
Normal file
35
src/app/kanban/kanban-routing.module.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
import {Routes, RouterModule} from '@angular/router';
|
||||||
|
import {KanbanBoardComponent} from "./kanban-board/kanban-board.component";
|
||||||
|
import {KanbanService} from "./shared";
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'kanban',
|
||||||
|
children: [],
|
||||||
|
component: KanbanBoardComponent,
|
||||||
|
resolve: {
|
||||||
|
kanbanBoard: KanbanService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
disableAutoSwitch: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'kanban-fixed',
|
||||||
|
children: [],
|
||||||
|
component: KanbanBoardComponent,
|
||||||
|
resolve: {
|
||||||
|
kanbanBoard: KanbanService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
disableAutoSwitch: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class KanbanRoutingModule {
|
||||||
|
}
|
||||||
@@ -4,22 +4,28 @@ import { HttpModule } from "@angular/http";
|
|||||||
|
|
||||||
import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
||||||
|
|
||||||
import { KanbanService } from './shared/kanban.service';
|
import { KanbanService } from './shared';
|
||||||
import { KanbanEntryItemComponent } from './kanban-entry-item/kanban-entry-item.component';
|
import { KanbanEntryItemComponent } from './kanban-entry-item/kanban-entry-item.component';
|
||||||
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
||||||
import { ShortenTextPipe } from './shared/shorten-text.pipe';
|
import { ShortenTextPipe } from './shared/shorten-text.pipe';
|
||||||
import { SelfUpdaterService } from './shared/self-updater.service';
|
import { SelfUpdaterService } from './shared/self-updater.service';
|
||||||
|
import { BlockedDaysPipe } from './shared/blocked-days.pipe';
|
||||||
|
import { KanbanRoutingModule } from "./kanban-routing.module";
|
||||||
|
import { PrefixJiraIdPipe } from './shared/prefix-jira-id.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
|
KanbanRoutingModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
KanbanBoardComponent,
|
KanbanBoardComponent,
|
||||||
KanbanEntryItemComponent,
|
KanbanEntryItemComponent,
|
||||||
PriorityColorPipe,
|
PriorityColorPipe,
|
||||||
ShortenTextPipe,
|
ShortenTextPipe,
|
||||||
|
BlockedDaysPipe,
|
||||||
|
PrefixJiraIdPipe,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
KanbanService,
|
KanbanService,
|
||||||
|
|||||||
8
src/app/kanban/shared/blocked-days.pipe.spec.ts
Normal file
8
src/app/kanban/shared/blocked-days.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { BlockedDaysPipe } from './blocked-days.pipe';
|
||||||
|
|
||||||
|
describe('BlockedDaysPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new BlockedDaysPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
21
src/app/kanban/shared/blocked-days.pipe.ts
Normal file
21
src/app/kanban/shared/blocked-days.pipe.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'blockedDays'
|
||||||
|
})
|
||||||
|
export class BlockedDaysPipe implements PipeTransform {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the BLOCKED label, including days since the task is blocked
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @param {number} days
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
transform(value: string, days: number): any {
|
||||||
|
return ( value.toUpperCase() == 'BLOCKED' && days > 0 )
|
||||||
|
? `${value} - ${days}D`
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,21 +7,24 @@ export class KanbanEntry {
|
|||||||
public key: string;
|
public key: string;
|
||||||
public summary: string;
|
public summary: string;
|
||||||
public issueType: JiraIssueType;
|
public issueType: JiraIssueType;
|
||||||
|
public epicName: string;
|
||||||
public status: JiraStatus;
|
public status: JiraStatus;
|
||||||
public assignee: JiraAssignee;
|
public assignee: JiraAssignee;
|
||||||
|
public additionalAssignees: Array<JiraAssignee> = [];
|
||||||
public issuePriority: string;
|
public issuePriority: string;
|
||||||
public issuePriorityIcon: string;
|
public issuePriorityIcon: string;
|
||||||
public labels: Array<string>;
|
public labels: Array<string>;
|
||||||
public prio: number;
|
// public prio: number;
|
||||||
public functionalAreas: Array<string>;
|
// public functionalAreas: Array<string>;
|
||||||
public externalId: string;
|
// public externalId: string;
|
||||||
public externalLink: string;
|
// public externalLink: string;
|
||||||
public project: string;
|
// public project: string;
|
||||||
public mhwebStatus: string;
|
// public mhwebStatus: string;
|
||||||
public mhwebHot: boolean;
|
// public mhwebHot: boolean;
|
||||||
public mhwebExternal: boolean;
|
// public mhwebExternal: boolean;
|
||||||
public team: string;
|
// public team: string;
|
||||||
public answerCode: string;
|
// public answerCode: string;
|
||||||
public isLastOfPriority: boolean;
|
public isLastOfPriority: boolean;
|
||||||
public worklog: number;
|
public worklog: number;
|
||||||
|
public daysBlocked: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Http, Headers } from "@angular/http";
|
import { Http } from "@angular/http";
|
||||||
import 'rxjs/Rx';
|
import 'rxjs/Rx';
|
||||||
import { Router, Resolve, ActivatedRouteSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
@@ -17,14 +17,28 @@ export class KanbanService {
|
|||||||
|
|
||||||
constructor(private httpService: Http) {}
|
constructor(private httpService: Http) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable instance to the kanban board api
|
||||||
|
*
|
||||||
|
* @returns {Observable<KanbanBoard>}
|
||||||
|
*/
|
||||||
public getList(): Observable<KanbanBoard> {
|
public getList(): Observable<KanbanBoard> {
|
||||||
return this.httpService.get(this.url).map(res => this.preprocessPriorities(<KanbanBoard>res.json()));
|
return this.httpService.get(this.url).map(res => <KanbanBoard>res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route preload resolver
|
||||||
|
*
|
||||||
|
* @param {ActivatedRouteSnapshot} route
|
||||||
|
* @returns {Promise<KanbanBoard>}
|
||||||
|
*/
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the board
|
||||||
|
*/
|
||||||
public reload() {
|
public reload() {
|
||||||
this.getList().subscribe(result => this.cachedKanbanBoard = result);
|
this.getList().subscribe(result => this.cachedKanbanBoard = result);
|
||||||
}
|
}
|
||||||
@@ -36,26 +50,4 @@ export class KanbanService {
|
|||||||
set kanbanBoard(kanbanBoard: KanbanBoard) {
|
set kanbanBoard(kanbanBoard: KanbanBoard) {
|
||||||
this.cachedKanbanBoard = kanbanBoard;
|
this.cachedKanbanBoard = kanbanBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
private preprocessPriorities(kanbanBoard: KanbanBoard): KanbanBoard {
|
|
||||||
['inbox','inProgress','verification','done'].map(progress => {
|
|
||||||
kanbanBoard[progress].map(entry => entry.isLastOfPriority = false);
|
|
||||||
['Trivial',
|
|
||||||
'Minor',
|
|
||||||
'Major',
|
|
||||||
'Critical',
|
|
||||||
'Blocker'].map(prio => {
|
|
||||||
let prioLastIndex = -1;
|
|
||||||
kanbanBoard[progress].map( (entry, idx) => {
|
|
||||||
if(entry.issuePriority == prio) {
|
|
||||||
prioLastIndex = idx;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
kanbanBoard[progress][prioLastIndex].isLastOfPriority = true;
|
|
||||||
} catch(e) {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return kanbanBoard;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/kanban/shared/prefix-jira-id.pipe.spec.ts
Normal file
8
src/app/kanban/shared/prefix-jira-id.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { PrefixJiraIdPipe } from './prefix-jira-id.pipe';
|
||||||
|
|
||||||
|
describe('PrefixJiraIdPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new PrefixJiraIdPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
12
src/app/kanban/shared/prefix-jira-id.pipe.ts
Normal file
12
src/app/kanban/shared/prefix-jira-id.pipe.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'prefixJiraId'
|
||||||
|
})
|
||||||
|
export class PrefixJiraIdPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: string, jiraId: string): string {
|
||||||
|
return `[${jiraId}] ${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,15 @@ import {Pipe, PipeTransform} from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class PriorityColorPipe implements PipeTransform {
|
export class PriorityColorPipe implements PipeTransform {
|
||||||
|
|
||||||
transform(value: any, prioIcon: string = "", worklog: number = 0): any {
|
/**
|
||||||
|
* Format the []-tags in the issue summary
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @param {string} prioIcon
|
||||||
|
* @param {number} worklog
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
transform(value: string, prioIcon: string = "", worklog: number = 0): string {
|
||||||
let mhrMatch = /(\[(.*)mhr\])/ig;
|
let mhrMatch = /(\[(.*)mhr\])/ig;
|
||||||
value = value.replace(mhrMatch, (fullMatch: string, mhrMatched: string, hoursMatch: number) => {
|
value = value.replace(mhrMatch, (fullMatch: string, mhrMatched: string, hoursMatch: number) => {
|
||||||
return `<span class="match-mhr">[${worklog}/${hoursMatch} mhr] </span> `;
|
return `<span class="match-mhr">[${worklog}/${hoursMatch} mhr] </span> `;
|
||||||
@@ -26,7 +34,7 @@ export class PriorityColorPipe implements PipeTransform {
|
|||||||
value = value.replace(xlMatch, (fullMatch: string, mhrMatched: string) => {
|
value = value.replace(xlMatch, (fullMatch: string, mhrMatched: string) => {
|
||||||
return `<span class="match-xl">${mhrMatched}</span> `;
|
return `<span class="match-xl">${mhrMatched}</span> `;
|
||||||
});
|
});
|
||||||
return (prioIcon ? `<img class="prio-icon" src="${prioIcon}"> ` : "") + value;
|
return (prioIcon ? `<img class="prio-icon" width="16" height="16" src="${prioIcon}"> ` : "") + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ export class SelfUpdaterService {
|
|||||||
private appRevision: number = 0;
|
private appRevision: number = 0;
|
||||||
private initFailed: boolean = false;
|
private initFailed: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load current revision data from the server on initialization
|
||||||
|
*
|
||||||
|
* @param {Http} httpService
|
||||||
|
* @param {Location} locationService
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private httpService: Http,
|
private httpService: Http,
|
||||||
private locationService: Location,
|
private locationService: Location,
|
||||||
@@ -26,10 +32,17 @@ export class SelfUpdaterService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return observable instance to the installed version on the server
|
||||||
|
* @returns {Observable<number>}
|
||||||
|
*/
|
||||||
private getDeployedRevision(): Observable<number> {
|
private getDeployedRevision(): Observable<number> {
|
||||||
return this.httpService.get(this.locationService.prepareExternalUrl("/revision.json")).map(result => result.json());
|
return this.httpService.get(this.locationService.prepareExternalUrl("/revision.json")).map(result => result.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the application if the server revision is newer than the current running revision
|
||||||
|
*/
|
||||||
public checkAndReloadIfNecessary() {
|
public checkAndReloadIfNecessary() {
|
||||||
if (!this.initFailed) {
|
if (!this.initFailed) {
|
||||||
this.getDeployedRevision().subscribe(
|
this.getDeployedRevision().subscribe(
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'shortenText'
|
name: 'shortenText'
|
||||||
})
|
})
|
||||||
export class ShortenTextPipe implements PipeTransform {
|
export class ShortenTextPipe implements PipeTransform {
|
||||||
|
|
||||||
transform(value: string, length: number = 120): any {
|
/**
|
||||||
return value.length > length
|
* Shorten long text, postfixing it with '...'
|
||||||
? (value.substring(0,length) + '...')
|
*
|
||||||
: value;
|
* @param {string} value
|
||||||
}
|
* @param {number} length
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
transform(value: string, length: number = 120): any {
|
||||||
|
return value.length > length
|
||||||
|
? (value.substring(0, length) + '...')
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/assets/font/impact.ttf
Normal file
BIN
src/assets/font/impact.ttf
Normal file
Binary file not shown.
BIN
src/assets/font/utm-ericsson-capital.ttf
Normal file
BIN
src/assets/font/utm-ericsson-capital.ttf
Normal file
Binary file not shown.
@@ -5,5 +5,5 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiUri: "http://localhost:8080",
|
apiUri: "http://localhost:8888",
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
src/favicon.ico
BIN
src/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
BIN
src/favicon.png
Normal file
BIN
src/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
@@ -6,7 +6,7 @@
|
|||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.png">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root>
|
<app-root>
|
||||||
|
|||||||
@@ -1,48 +1,73 @@
|
|||||||
/* 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 */
|
||||||
|
@import "//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700";
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'EricssonCapital';
|
||||||
|
src: url("./assets/font/utm-ericsson-capital.ttf");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Impact';
|
||||||
|
src: url("./assets/font/impact.ttf");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
body {
|
body {
|
||||||
background-color: #303030 !important;
|
height: 100% !important;
|
||||||
color: #eeeeee !important;
|
overflow: hidden;
|
||||||
margin-top: 1em !important;
|
}
|
||||||
margin-bottom: 1em !important;
|
|
||||||
overflow: hidden;
|
body {
|
||||||
font-weight: bold !important;
|
background-color: #303030 !important;
|
||||||
|
color: #eeeeee !important;
|
||||||
|
margin-top: 1em !important;
|
||||||
|
margin-bottom: 1em !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
app-kanban-board {
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.fullwide-container {
|
.ui.fullwide-container {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-mhr {
|
.match-mhr {
|
||||||
text-justify: none;
|
text-justify: none;
|
||||||
color: mediumpurple;
|
color: mediumpurple;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-s {
|
.match-s {
|
||||||
text-justify: none;
|
text-justify: none;
|
||||||
color: #00b5ad;
|
color: #00b5ad;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-m {
|
.match-m {
|
||||||
text-justify: none;
|
text-justify: none;
|
||||||
color: #0ea432;
|
color: #0ea432;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-l {
|
.match-l {
|
||||||
text-justify: none;
|
text-justify: none;
|
||||||
color: #ffbf00;
|
color: #ffbf00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.match-xl {
|
.match-xl {
|
||||||
text-justify: none;
|
text-justify: none;
|
||||||
color: coral;
|
color: coral;
|
||||||
}
|
}
|
||||||
|
|
||||||
.over-wip {
|
.over-wip {
|
||||||
background-color: rgba(194,59,34, 0.3);
|
background-color: rgba(194, 59, 34, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prio-icon {
|
.prio-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user