TSK-1712: Introduce Tasks-by-priority charts in Monitor component

This commit is contained in:
Sofie Hofmann 2021-09-14 16:35:51 +02:00
parent 816befbbdc
commit a33cf9f14c
27 changed files with 500 additions and 32 deletions

View File

@ -91,6 +91,6 @@ INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000074', 'ETI:0000000
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000075', 'ETI:000000000000000000000000000000000075', RELATIVE_DATE(-4) , RELATIVE_DATE(-2) , RELATIVE_DATE(0) , RELATIVE_DATE(0) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'TaskXY' , 'creator_user_id' , 'TaskXY' , null , 3 , -1 , 'COMPLETED' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000008' , 'USER-2-1' , 'DOMAIN_A', 'PI_0000000000075' , 'DOC_0000000000000000075' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000076', 'ETI:000000000000000000000000000000000076', RELATIVE_DATE(0) , null , null , RELATIVE_DATE(0) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'Schadenfall' , 'creator_user_id' , 'Schadenfall' , null , 1 , -1 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000009' , 'USER-2-2' , 'DOMAIN_A', 'PI_0000000000076' , 'DOC_0000000000000000076' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , false , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000077', 'ETI:000000000000000000000000000000000077', RELATIVE_DATE(0) , null , null , RELATIVE_DATE(0) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'Widerruf' , 'creator_user_id' , 'Wideruf' , null , 2 , -1 , 'READY' , 'EXTERN' , 'T2000' , 'CLI:100000000000000000000000000000000016', 'WBI:100000000000000000000000000000000009' , 'USER-2-2' , 'DOMAIN_A', 'PI_0000000000077' , 'DOC_0000000000000000077' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , false , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000078', 'ETI:000000000000000000000000000000000078', RELATIVE_DATE(-2) , RELATIVE_DATE(-1) , null , RELATIVE_DATE(-1) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'TaskXY' , 'creator_user_id' , 'TaskXY' , null , 2 , -1 , 'CLAIMED' , 'EXTERN' , 'T6310' , 'CLI:000000000000000000000000000000000011', 'WBI:100000000000000000000000000000000009' , 'USER-2-2' , 'DOMAIN_A', 'PI_0000000000078' , 'DOC_0000000000000000078' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000079', 'ETI:000000000000000000000000000000000079', RELATIVE_DATE(-1) , null , null , RELATIVE_DATE(-1) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'Schadenfall' , 'creator_user_id' , 'Schadenfall' , null , 3 , -1 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000009' , 'USER-2-2' , 'DOMAIN_A', 'PI_0000000000079' , 'DOC_0000000000000000079' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000080', 'ETI:000000000000000000000000000000000080', RELATIVE_DATE(-4) , RELATIVE_DATE(-2) , RELATIVE_DATE(0) , RELATIVE_DATE(0) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'TaskXY' , 'creator_user_id' , 'TaskXY' , null , 3 , -1 , 'COMPLETED' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000009' , 'USER-2-2' , 'DOMAIN_A', 'PI_0000000000080' , 'DOC_0000000000000000080' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000078', 'ETI:000000000000000000000000000000000078', RELATIVE_DATE(-2) , RELATIVE_DATE(-1) , null , RELATIVE_DATE(-1) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'TaskXY' , 'creator_user_id' , 'TaskXY' , null , 2 , -1 , 'CLAIMED' , 'EXTERN' , 'T6310' , 'CLI:000000000000000000000000000000000011', 'WBI:100000000000000000000000000000000016' , 'TPK_VIP_2' , 'DOMAIN_A', 'PI_0000000000078' , 'DOC_0000000000000000078' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000079', 'ETI:000000000000000000000000000000000079', RELATIVE_DATE(-1) , null , null , RELATIVE_DATE(-1) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'Schadenfall' , 'creator_user_id' , 'Schadenfall' , null , 3 , -1 , 'READY' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000010' , 'TPK_VIP' , 'DOMAIN_A', 'PI_0000000000079' , 'DOC_0000000000000000079' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );
INSERT INTO TASK VALUES('TKI:000000000000000000000000000000000080', 'ETI:000000000000000000000000000000000080', RELATIVE_DATE(-4) , RELATIVE_DATE(-2) , RELATIVE_DATE(0) , RELATIVE_DATE(0) , null , RELATIVE_DATE(0) , RELATIVE_DATE(1) , 'TaskXY' , 'creator_user_id' , 'TaskXY' , null , 260 , -1 , 'COMPLETED' , 'EXTERN' , 'L1050' , 'CLI:100000000000000000000000000000000003', 'WBI:100000000000000000000000000000000010' , 'TPK_VIP' , 'DOMAIN_A', 'PI_0000000000080' , 'DOC_0000000000000000080' , null , '00' , 'PASystem' , '00' , 'SDNR' , '98769432' , true , false , null , 'NONE' , null , null , null , null , null , null , null , null , null ,null , null , null , null , null , 'abc' , null , null , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 );

View File

@ -125,6 +125,6 @@ class MonitorControllerIntTest {
assertThat(report.getSumRow())
.extracting(RowRepresentationModel::getCells)
.containsExactly(new int[] {23, 0, 0});
.containsExactly(new int[] {25, 1, 0});
}
}

View File

@ -91,7 +91,7 @@ class TaskControllerIntTest {
assertThat(response.getBody()).isNotNull();
assertThat((response.getBody()).getLink(IanaLinkRelations.SELF)).isNotNull();
assertThat(response.getBody().getContent()).hasSize(58);
assertThat(response.getBody().getContent()).hasSize(57);
}
@Test
@ -497,7 +497,7 @@ class TaskControllerIntTest {
TEMPLATE.exchange(url, HttpMethod.GET, auth, TASK_SUMMARY_PAGE_MODEL_TYPE);
assertThat(response.getBody()).isNotNull();
assertThat((response.getBody()).getContent()).hasSize(58);
assertThat((response.getBody()).getContent()).hasSize(57);
String url2 =
restHelper.toUrl(RestEndpoints.URL_TASKS)

View File

@ -0,0 +1 @@
<canvas id="{{id}}" ></canvas>

View File

@ -0,0 +1,44 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
import { CanvasComponent } from './canvas.component';
import { workbasketReportMock } from '../task-priority-report/monitor-mock-data';
describe('CanvasComponent', () => {
let fixture: ComponentFixture<CanvasComponent>;
let debugElement: DebugElement;
let component: CanvasComponent;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([])],
declarations: [CanvasComponent]
}).compileComponents();
fixture = TestBed.createComponent(CanvasComponent);
debugElement = fixture.debugElement;
component = fixture.debugElement.componentInstance;
component.id = '1';
fixture.detectChanges();
})
);
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should show canvas with id from input', () => {
const id = debugElement.nativeElement.querySelector("[id='1']");
expect(id).toBeTruthy();
});
it('should call generateChart()', () => {
component.generateChart = jest.fn();
const reportRow = workbasketReportMock.rows[1];
component.row = reportRow;
fixture.detectChanges();
component.ngAfterViewInit();
expect(component.generateChart).toHaveBeenCalledWith('1', reportRow);
});
});

View File

@ -0,0 +1,52 @@
import { AfterViewInit, Component, Input } from '@angular/core';
import { Chart } from 'chart.js';
import { priorityTypes } from '../../models/priority';
import { ReportRow } from '../../models/report-row';
@Component({
selector: 'taskana-monitor-canvas',
templateUrl: './canvas.component.html',
styleUrls: ['./canvas.component.scss']
})
export class CanvasComponent implements AfterViewInit {
@Input() row: ReportRow;
@Input() id: string;
@Input() isReversed: boolean;
ngAfterViewInit() {
const canvas = document.getElementById(this.id) as HTMLCanvasElement;
if (canvas && this.id && this.row) {
this.generateChart(this.id, this.row);
}
}
generateChart(id: string, row: ReportRow) {
const canvas = document.getElementById(id) as HTMLCanvasElement;
new Chart(canvas, {
type: 'doughnut',
data: {
labels: [priorityTypes.HIGH, priorityTypes.MEDIUM, priorityTypes.LOW],
datasets: [
{
label: 'Tasks by Priority',
// depends on whether backend sends data sorted in ascending or descending order
data: this.isReversed ? row.cells.reverse() : row.cells,
backgroundColor: ['red', 'gold', 'limegreen'],
borderWidth: 0
}
]
},
options: {
rotation: Math.PI,
circumference: Math.PI,
title: {
display: true,
text: String(row.total),
position: 'bottom',
fontSize: 18
}
}
});
}
}

View File

@ -1,6 +1,8 @@
<nav mat-tab-nav-bar class="nav-bar">
<a mat-tab-link class="nav-bar__tasks" routerLink="/taskana/monitor/tasks" [active]="selectedTab === 'tasks'"
(click)="selectedTab = 'tasks'">Tasks</a>
<a mat-tab-link class="nav-bar__tasks--priority" routerLink="/taskana/monitor/tasks-priority" [active]="selectedTab === 'tasks-priority'"
(click)="selectedTab = 'tasks-priority'">Tasks by Priority</a>
<a mat-tab-link class="nav-bar__tasks--status" routerLink="/taskana/monitor/tasks-status" [active]="selectedTab === 'tasks-status'"
(click)="selectedTab = 'tasks-status'">Tasks by Status </a>
<a mat-tab-link class="nav-bar__workbaskets" routerLink="/taskana/monitor/workbaskets"
[active]="selectedTab === 'workbaskets'" (click)="selectedTab = 'workbaskets'">Workbaskets</a>
<a mat-tab-link class="nav-bar__classifications" routerLink="/taskana/monitor/classifications"

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
@ -10,10 +10,10 @@ export class MonitorComponent implements OnInit {
selectedTab = '';
constructor(public router: Router) {
this.router.navigate(['/taskana/monitor/tasks']);
this.router.navigate(['/taskana/monitor/tasks-priority']);
}
ngOnInit(): void {
this.selectedTab = 'tasks';
this.selectedTab = 'tasks-priority';
}
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { Component, Input, OnChanges } from '@angular/core';
import { ReportData } from 'app/monitor/models/report-data';
import { ReportRow } from '../../models/report-row';

View File

@ -0,0 +1,48 @@
import { ReportData } from '../../models/report-data';
export const workbasketReportMock: ReportData = {
meta: {
name: 'WorkbasketPriorityReport',
date: '2021-08-24T11:44:34.023901Z',
header: ['<249', '250 - 500', '>501'],
rowDesc: ['WORKBASKET'],
sumRowDesc: 'Total'
},
rows: [
{
cells: [5, 0, 0],
total: 5,
depth: 0,
desc: ['ADMIN'],
display: true
},
{
cells: [3, 5, 2],
total: 10,
depth: 0,
desc: ['GPK_KSC'],
display: true
}
],
sumRow: [
{
cells: [8, 5, 2],
total: 15,
depth: 0,
desc: ['Total'],
display: true
}
]
};
export const workbasketReportUnexpectedHeaderMock: ReportData = {
meta: {
name: 'WorkbasketPriorityReport',
date: '2021-08-24T11:44:34.023901Z',
header: ['<10', '11 - 100', '>101'],
rowDesc: ['WORKBASKET'],
sumRowDesc: 'Total'
},
rows: [],
sumRow: []
};

View File

@ -0,0 +1,48 @@
<div *ngIf="reportData" class="task-priority-report">
<h4> {{reportData?.meta.name}} ({{reportData?.meta.date | germanTimeFormat }}) </h4>
<div *ngIf="reportData?.rows.length == 0"> There are no Workbaskets with type TOPIC. </div>
<div class="task-priority-report__workbaskets">
<div *ngFor="let row of reportData?.rows; let i = index" class="task-priority-report__workbasket">
<!-- WORKBASKET NAME -->
<div class="task-priority-report__headline">
<h6> {{row.desc[0]}} </h6>
</div>
<!-- CHART -->
<taskana-monitor-canvas [row]="row" [id]="toString(i)" [isReversed]="isReversed"> </taskana-monitor-canvas>
<!-- TABLE -->
<div>
<table mat-table [dataSource]="tableDataArray[i]">
<!-- Column: Priority -->
<ng-container matColumnDef="priority">
<th mat-header-cell *matHeaderCellDef> Priority </th>
<td mat-cell *matCellDef="let element" [ngClass]="{
'task-priority-report__row--high': element.priority == 'High',
'task-priority-report__row--medium': element.priority == 'Medium',
'task-priority-report__row--low': element.priority == 'Low'}">
{{element.priority}} </td>
</ng-container>
<!-- Column: Number of Tasks -->
<ng-container matColumnDef="number">
<th mat-header-cell *matHeaderCellDef> Number of Tasks </th>
<td mat-cell *matCellDef="let element" [ngClass]="{
'task-priority-report__row--high': element.priority == 'High',
'task-priority-report__row--medium': element.priority == 'Medium',
'task-priority-report__row--low': element.priority == 'Low'}">
{{element.number}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columns"></tr>
<tr mat-row *matRowDef="let row; columns: columns;">
</tr>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
table {
width: 100%;
}
.task-priority-report {
padding: 8px;
overflow-y: scroll;
height: calc(100vh - 104px);
&__workbaskets {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
&__workbasket {
display: flex;
flex-direction: column;
width: 360px;
margin: 24px 24px 36px 24px;
}
&__headline {
text-align: center;
margin-bottom: 8px;
}
&__row--low {
color: limegreen;
}
&__row--medium {
color: gold;
}
&__row--high {
color: red;
}
}

View File

@ -0,0 +1,133 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Component, DebugElement, Input, Pipe, PipeTransform } from '@angular/core';
import { NgxsModule } from '@ngxs/store';
import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { TaskPriorityReportComponent } from './task-priority-report.component';
import { MonitorService } from '../../services/monitor.service';
import { of } from 'rxjs';
import { MatTableModule } from '@angular/material/table';
import { priorityTypes } from '../../models/priority';
import { workbasketReportMock, workbasketReportUnexpectedHeaderMock } from './monitor-mock-data';
@Pipe({ name: 'germanTimeFormat' })
class GermanTimeFormatPipe implements PipeTransform {
transform(value: number): number {
return value;
}
}
@Component({ selector: 'taskana-monitor-canvas', template: '' })
class CanvasStub {
@Input() row;
@Input() id;
@Input() isReversed;
}
const monitorServiceSpy: Partial<MonitorService> = {
getTasksByPriorityReport: jest.fn().mockReturnValue(of(workbasketReportMock))
};
const monitorServiceWithDifferentDataSpy: Partial<MonitorService> = {
getTasksByPriorityReport: jest.fn().mockReturnValue(of(workbasketReportUnexpectedHeaderMock))
};
const notificationServiceSpy: Partial<NotificationService> = {
showWarning: jest.fn()
};
describe('TaskPriorityReportComponent', () => {
let fixture: ComponentFixture<TaskPriorityReportComponent>;
let debugElement: DebugElement;
let component: TaskPriorityReportComponent;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([]), MatTableModule],
declarations: [TaskPriorityReportComponent, GermanTimeFormatPipe, CanvasStub],
providers: [
{ provide: MonitorService, useValue: monitorServiceSpy },
{ provide: NotificationService, useValue: notificationServiceSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(TaskPriorityReportComponent);
debugElement = fixture.debugElement;
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
})
);
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should show Canvas component for all Workbaskets', () => {
const canvas = debugElement.nativeElement.querySelectorAll('taskana-monitor-canvas');
expect(canvas).toHaveLength(2);
});
it('should show table for all Workbaskets', () => {
const table = debugElement.nativeElement.querySelectorAll('table');
expect(table).toHaveLength(2);
});
it('should not show warning when actual header matches the expected header', () => {
const showWarningSpy = jest.spyOn(notificationServiceSpy, 'showWarning');
component.ngOnInit();
expect(showWarningSpy).toHaveBeenCalledTimes(0);
});
it('should set isReserved to true when high priority has a higher index than low priority', () => {
expect(component.isReversed).toBeTruthy();
});
it('should set tableDataArray', () => {
const expectedTableData = [
[
{ priority: priorityTypes.HIGH, number: 0 },
{ priority: priorityTypes.MEDIUM, number: 0 },
{ priority: priorityTypes.LOW, number: 5 },
{ priority: 'Total', number: 5 }
],
[
{ priority: priorityTypes.HIGH, number: 2 },
{ priority: priorityTypes.MEDIUM, number: 5 },
{ priority: priorityTypes.LOW, number: 3 },
{ priority: 'Total', number: 10 }
]
];
expect(component.tableDataArray).toStrictEqual(expectedTableData);
});
});
describe('TaskPriorityReportComponent with report data containing an unexpected header', () => {
let fixture: ComponentFixture<TaskPriorityReportComponent>;
let debugElement: DebugElement;
let component: TaskPriorityReportComponent;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot([]), MatTableModule],
declarations: [TaskPriorityReportComponent, GermanTimeFormatPipe, CanvasStub],
providers: [
WorkbasketService,
{ provide: MonitorService, useValue: monitorServiceWithDifferentDataSpy },
{ provide: NotificationService, useValue: notificationServiceSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(TaskPriorityReportComponent);
debugElement = fixture.debugElement;
component = fixture.debugElement.componentInstance;
fixture.detectChanges();
})
);
it('should show warning when actual header does not match the expected header', () => {
const showWarningSpy = jest.spyOn(notificationServiceSpy, 'showWarning');
expect(showWarningSpy).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,52 @@
import { Component, OnInit } from '@angular/core';
import { priorityTypes } from '../../models/priority';
import { ReportData } from '../../models/report-data';
import { MonitorService } from '../../services/monitor.service';
import { take } from 'rxjs/operators';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { WorkbasketType } from '../../../shared/models/workbasket-type';
@Component({
selector: 'taskana-monitor-task-priority-report',
templateUrl: './task-priority-report.component.html',
styleUrls: ['./task-priority-report.component.scss']
})
export class TaskPriorityReportComponent implements OnInit {
columns: string[] = ['priority', 'number'];
reportData: ReportData;
tableDataArray: { priority: string; number: number }[][] = [];
isReversed = true;
constructor(private monitorService: MonitorService, private notificationService: NotificationService) {}
ngOnInit() {
this.monitorService
.getTasksByPriorityReport([WorkbasketType.TOPIC])
.pipe(take(1))
.subscribe((reportData) => {
this.reportData = reportData;
let indexHigh = reportData.meta.header.indexOf('>501');
let indexMedium = reportData.meta.header.indexOf('250 - 500');
let indexLow = reportData.meta.header.indexOf('<249');
if (indexHigh == -1 || indexMedium == -1 || indexLow == -1) {
this.notificationService.showWarning('REPORT_DATA_WRONG_HEADER');
indexHigh = 2;
indexMedium = 1;
indexLow = 0;
}
this.isReversed = indexHigh > indexLow;
reportData.rows.forEach((row) => {
this.tableDataArray.push([
{ priority: priorityTypes.HIGH, number: row.cells[indexHigh] },
{ priority: priorityTypes.MEDIUM, number: row.cells[indexMedium] },
{ priority: priorityTypes.LOW, number: row.cells[indexLow] },
{ priority: 'Total', number: row.total }
]);
});
});
}
toString(i: number): string {
return String(i);
}
}

View File

@ -1,6 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { MetaInfoData } from '../../models/meta-info-data';
import { QueryType } from '../../models/query-type';
enum WorkbasketReports {
DUE_DATE,
PLANNED_DATE
@ -26,7 +25,7 @@ export class WorkbasketReportComponent implements OnInit {
}
getTitle(): string {
return this.selectedComponent
? 'Tasks grouped by workbasket, querying by planned date'
: 'Tasks grouped by workbasket, querying by due date';
? 'Tasks grouped by Workbasket, querying by planned date'
: 'Tasks grouped by Workbasket, querying by due date';
}
}

View File

@ -1,4 +1,4 @@
export class ChartData {
data: Array<number>;
data: number[];
label: string;
}

View File

@ -1,7 +1,7 @@
export class MetaInfoData {
name: string;
date: string;
header: Array<string>;
rowDesc: Array<string>;
header: string[];
rowDesc: string[];
sumRowDesc: string;
}

View File

@ -0,0 +1,5 @@
export enum priorityTypes {
HIGH = 'High',
MEDIUM = 'Medium',
LOW = 'Low'
}

View File

@ -3,6 +3,6 @@ import { MetaInfoData } from './meta-info-data';
export class ReportData {
meta: MetaInfoData;
rows: Array<ReportRow>;
sumRow: Array<ReportRow>;
rows: ReportRow[];
sumRow: ReportRow[];
}

View File

@ -1,7 +1,7 @@
export class ReportRow {
cells: Array<number>;
export interface ReportRow {
cells: number[];
total: number;
depth: number;
desc: Array<string>;
display = false;
desc: string[];
display: boolean;
}

View File

@ -5,6 +5,7 @@ import { TaskReportComponent } from './components/task-report/task-report.compon
import { WorkbasketReportComponent } from './components/workbasket-report/workbasket-report.component';
import { ClassificationReportComponent } from './components/classification-report/classification-report.component';
import { TimestampReportComponent } from './components/timestamp-report/timestamp-report.component';
import { TaskPriorityReportComponent } from './components/task-priority-report/task-priority-report.component';
const routes: Routes = [
{
@ -12,7 +13,11 @@ const routes: Routes = [
component: MonitorComponent,
children: [
{
path: 'tasks',
path: 'tasks-priority',
component: TaskPriorityReportComponent
},
{
path: 'tasks-status',
component: TaskReportComponent
},
{

View File

@ -2,8 +2,6 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { MatTabsModule } from '@angular/material/tabs';
import { MatButtonModule } from '@angular/material/button';
import { AlertModule } from 'ngx-bootstrap/alert';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ChartsModule } from 'ng2-charts';
@ -12,6 +10,9 @@ import { MapToIterable } from 'app/shared/pipes/map-to-iterable.pipe';
import { SharedModule } from '../shared/shared.module';
import { MonitorRoutingModule } from './monitor-routing.module';
/**
* Components
*/
import { ReportTableComponent } from './components/report-table/report-table.component';
import { MonitorComponent } from './components/monitor/monitor.component';
import { TaskReportComponent } from './components/task-report/task-report.component';
@ -20,8 +21,21 @@ import { TimestampReportComponent } from './components/timestamp-report/timestam
import { WorkbasketReportComponent } from './components/workbasket-report/workbasket-report.component';
import { WorkbasketReportPlannedDateComponent } from './components/workbasket-report-planned-date/workbasket-report-planned-date.component';
import { WorkbasketReportDueDateComponent } from './components/workbasket-report-due-date/workbasket-report-due-date.component';
import { TaskPriorityReportComponent } from './components/task-priority-report/task-priority-report.component';
import { CanvasComponent } from './components/canvas/canvas.component';
/**
* Services
*/
import { MonitorService } from './services/monitor.service';
/**
* Material Design
*/
import { MatTabsModule } from '@angular/material/tabs';
import { MatButtonModule } from '@angular/material/button';
import { MatTableModule } from '@angular/material/table';
const MODULES = [
CommonModule,
MonitorRoutingModule,
@ -32,11 +46,15 @@ const MODULES = [
HttpClientModule,
AngularSvgIconModule,
SharedModule,
MatTabsModule
MatTabsModule,
MatButtonModule,
MatTableModule
];
const DECLARATIONS = [
ReportTableComponent,
MonitorComponent,
TaskPriorityReportComponent,
CanvasComponent,
TimestampReportComponent,
WorkbasketReportComponent,
WorkbasketReportPlannedDateComponent,
@ -47,7 +65,7 @@ const DECLARATIONS = [
@NgModule({
declarations: DECLARATIONS,
imports: [MODULES, MatButtonModule],
imports: [MODULES],
providers: [MonitorService, MapToIterable]
})
export class MonitorModule {}

View File

@ -6,6 +6,7 @@ import { ChartData } from 'app/monitor/models/chart-data';
import { ReportData } from '../models/report-data';
import { asUrlQueryString } from '../../shared/util/query-parameters-v2';
import { TaskState } from '../../shared/models/task-state';
import { WorkbasketType } from '../../shared/models/workbasket-type';
const monitorUrl = '/v1/monitor/';
@ -37,7 +38,7 @@ export class MonitorService {
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
return this.httpClient.get<ReportData>(
`${environment.taskanaRestUrl}/v1/monitor/workbasket-report${asUrlQueryString(queryParams)}`
`${environment.taskanaRestUrl + monitorUrl}workbasket-report${asUrlQueryString(queryParams)}`
);
}
@ -49,7 +50,7 @@ export class MonitorService {
return this.httpClient.get<ReportData>(`${environment.taskanaRestUrl + monitorUrl}timestamp-report`);
}
getChartData(source: ReportData): Array<ChartData> {
getChartData(source: ReportData): ChartData[] {
return source.rows.map((row) => {
const rowData = new ChartData();
[rowData.label] = row.desc;
@ -57,4 +58,13 @@ export class MonitorService {
return rowData;
});
}
getTasksByPriorityReport(type: WorkbasketType[] = []): Observable<ReportData> {
const queryParams = {
'workbasket-type': type
};
return this.httpClient.get<ReportData>(
`${environment.taskanaRestUrl + monitorUrl}workbasket-priority-report${asUrlQueryString(queryParams)}`
);
}
}

View File

@ -55,6 +55,10 @@ export class NotificationService {
);
}
showWarning(warningKey: string, messageVariables: object = {}) {
this.toastService.warning(this.obtainMessageService.getMessage(warningKey, messageVariables, messageTypes.WARNING));
}
showDialog(key: string, messageVariables: object = {}, callback: Function) {
const message = this.obtainMessageService.getMessage(key, messageVariables, messageTypes.DIALOG);

View File

@ -96,6 +96,12 @@ export const messageByErrorCode = {
EMPTY_WORKBASKET: 'Selected Workbasket is empty'
},
[messageTypes.WARNING]: {
REPORT_DATA_WRONG_HEADER:
'The received header of the Report data does not match the expected header. ' +
'The data might be displayed incorrectly. Please contact your administrator.'
},
[messageTypes.DIALOG]: {
POPUP_CONFIGURATION: 'This Popup was not configured properly for this request. Please contact your administrator.',

View File

@ -2,5 +2,6 @@ export enum messageTypes {
ERROR,
SUCCESS,
INFORMATION,
WARNING,
DIALOG
}