Files
task_tracker/web/angular/src/app/project-dashboard/project-dashboard.component.ts
2019-03-09 19:03:15 +00:00

432 lines
14 KiB
TypeScript

import {Component, OnInit} from '@angular/core';
import {ApiService} from '../api.service';
import {Project} from '../models/project';
import {ActivatedRoute} from '@angular/router';
import {Chart} from 'chart.js';
import {AssignedTasks, MonitoringSnapshot} from '../models/monitoring';
import {TranslateService} from '@ngx-translate/core';
import {MessengerService} from '../messenger.service';
import {AuthService} from '../auth.service';
import {MatDialog} from '@angular/material';
import {AreYouSureComponent} from '../are-you-sure/are-you-sure.component';
import * as moment from 'moment';
@Component({
selector: 'app-project-dashboard',
templateUrl: './project-dashboard.component.html',
styleUrls: ['./project-dashboard.component.css']
})
export class ProjectDashboardComponent implements OnInit {
private projectId;
project: Project;
noTasks = false;
private timeline: Chart;
private statusPie: Chart;
private assigneesPie: Chart;
private avgTask: number;
eta: string;
private colors = {
new: '#76FF03',
failed: '#FF3D00',
closed: '#E0E0E0',
awaiting: '#FFB74D',
random: [
'#3D5AFE', '#2979FF', '#2196F3',
'#7C4DFF', '#673AB7', '#7C4DFF',
'#FFC400', '#FFD740', '#FFC107',
'#FF3D00', '#FF6E40', '#FF5722',
'#76FF03', '#B2FF59', '#8BC34A'
]
};
snapshots: MonitoringSnapshot[] = [];
lastSnapshot: MonitoringSnapshot;
assignees: AssignedTasks[];
constructor(private apiService: ApiService,
private route: ActivatedRoute,
private translate: TranslateService,
public auth: AuthService,
public dialog: MatDialog,
private messenger: MessengerService) {
}
ngOnInit(): void {
this.route.params.subscribe(params => {
this.projectId = params['id'];
this.getProject();
});
}
public isSafeUrl(url: string) {
if (url.substr(0, 'http'.length) == 'http') {
return true;
}
}
public refresh() {
this.apiService.getMonitoringSnapshots(60, this.projectId)
.subscribe((data: any) => {
this.snapshots = data.content.snapshots;
this.averageTaskPerSecond();
this.calculateEta();
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
return b.time_stamp - a.time_stamp;
})[0] : null;
if (this.lastSnapshot == null || (this.lastSnapshot.awaiting_verification_count == 0 &&
this.lastSnapshot.closed_task_count == 0 &&
this.lastSnapshot.new_task_count == 0 &&
this.lastSnapshot.failed_task_count == 0)) {
this.noTasks = true;
return;
}
this.noTasks = false;
this.timeline.data.labels = this.snapshots.map(s => s.time_stamp * 1000 as any);
this.timeline.data.datasets = this.makeTimelineDataset(this.snapshots);
this.timeline.update();
this.statusPie.data.datasets = [
{
label: 'Task status',
data: [
this.lastSnapshot.new_task_count,
this.lastSnapshot.failed_task_count,
this.lastSnapshot.closed_task_count,
this.lastSnapshot.awaiting_verification_count,
],
backgroundColor: [
this.colors.new,
this.colors.failed,
this.colors.closed,
this.colors.awaiting
],
}
];
this.statusPie.update();
this.apiService.getAssigneeStats(this.projectId)
.subscribe((data: any) => {
this.assignees = data.content.assignees;
const colors = this.assignees.map(() => {
return this.colors.random[Math.floor(Math.random() * this.colors.random.length)];
});
this.assigneesPie.data.labels = this.assignees.map(x => x.assignee);
this.assigneesPie.data.datasets = [
{
label: 'Task status',
data: this.assignees.map(x => x.task_count),
backgroundColor: colors,
}
];
this.assigneesPie.update();
});
});
}
private makeTimelineDataset(snapshots: MonitoringSnapshot[]) {
return [
{
label: 'New',
type: 'line',
fill: false,
borderColor: this.colors.new,
backgroundColor: this.colors.new,
data: snapshots.map(s => s.new_task_count),
pointRadius: 0,
lineTension: 0.2,
},
{
label: 'Failed',
type: 'line',
fill: false,
borderColor: this.colors.failed,
backgroundColor: this.colors.failed,
data: snapshots.map(s => s.failed_task_count),
pointRadius: 0,
lineTension: 0.2,
},
{
label: 'Closed',
type: 'line',
fill: false,
borderColor: this.colors.closed,
backgroundColor: this.colors.closed,
pointRadius: 0,
data: snapshots.map(s => s.closed_task_count),
lineTension: 0.2,
},
{
label: 'Awaiting verification',
type: 'line',
fill: false,
borderColor: this.colors.awaiting,
backgroundColor: this.colors.awaiting,
data: snapshots.map(s => s.awaiting_verification_count),
pointRadius: 0,
lineTension: 0.2,
},
];
}
private setupTimeline() {
const elem = document.getElementById('timeline') as any;
const ctx = elem.getContext('2d');
this.timeline = new Chart(ctx, {
type: 'bar',
data: {
labels: this.snapshots.map(s => s.time_stamp * 1000 as any),
datasets: this.makeTimelineDataset(this.snapshots),
},
options: {
title: {
display: true,
text: 'Task status timeline',
position: 'bottom'
},
legend: {
position: 'left',
},
scales: {
xAxes: [{
type: 'time',
distribution: 'series',
ticks: {
source: 'auto'
},
}]
},
tooltips: {
enabled: true,
intersect: false,
mode: 'index',
position: 'nearest',
},
responsive: true
}
});
}
private setupStatusPie() {
if (this.lastSnapshot == undefined || (this.lastSnapshot.awaiting_verification_count == 0 &&
this.lastSnapshot.closed_task_count == 0 &&
this.lastSnapshot.new_task_count == 0 &&
this.lastSnapshot.failed_task_count == 0)) {
this.noTasks = true;
this.lastSnapshot = {
closed_task_count: 0, time_stamp: 0, failed_task_count: 0,
new_task_count: 0, awaiting_verification_count: 0
};
}
const elem = document.getElementById('status-pie') as any;
const ctx = elem.getContext('2d');
this.statusPie = new Chart(ctx, {
type: 'doughnut',
data: {
labels: [
'New',
'Failed',
'Closed',
'Awaiting verification',
],
datasets: [
{
label: 'Task status',
data: [
this.lastSnapshot.new_task_count,
this.lastSnapshot.failed_task_count,
this.lastSnapshot.closed_task_count,
this.lastSnapshot.awaiting_verification_count,
],
backgroundColor: [
this.colors.new,
this.colors.failed,
this.colors.closed,
this.colors.awaiting
],
}
],
},
options: {
responsive: true,
legend: {
position: 'left',
},
title: {
display: true,
text: 'Current task status',
position: 'bottom'
},
animation: {
animateScale: true,
animateRotate: true
},
}
});
}
private setupAssigneesPie() {
const elem = document.getElementById('assignees-pie') as any;
const ctx = elem.getContext('2d');
const colors = this.assignees.map(() => {
return this.colors.random[Math.floor(Math.random() * this.colors.random.length)];
});
this.assigneesPie = new Chart(ctx, {
type: 'doughnut',
data: {
labels: this.assignees.map(x => x.assignee),
datasets: [
{
label: 'Task status',
data: this.assignees.map(x => x.task_count),
backgroundColor: colors,
}
],
},
options: {
responsive: true,
legend: {
position: 'left',
},
title: {
display: true,
text: 'Task assignment',
position: 'bottom'
},
animation: {
animateScale: true,
animateRotate: true
},
}
});
}
private getProject() {
this.apiService.getProject(this.projectId).subscribe((data: any) => {
this.project = data.content.project;
this.apiService.getMonitoringSnapshots(60, this.projectId)
.subscribe((data: any) => {
this.snapshots = data.content.snapshots;
this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => {
return b.time_stamp - a.time_stamp;
})[0] : null;
this.setupTimeline();
this.setupStatusPie();
if (!this.snapshots) {
return;
}
this.apiService.getAssigneeStats(this.projectId)
.subscribe((data: any) => {
this.assignees = data.content.assignees;
this.setupAssigneesPie();
});
this.averageTaskPerSecond();
this.calculateEta();
});
},
error => {
this.translate.get('messenger.unauthorized').subscribe(t =>
this.messenger.show(t));
});
}
resetFailedTasks() {
this.dialog.open(AreYouSureComponent, {
width: '250px',
}).afterClosed().subscribe(result => {
if (result) {
this.apiService.resetFailedTasks(this.projectId).subscribe(
data => {
this.translate.get('project.reset_response').subscribe(t =>
this.messenger.show(t + data['content']['affected_tasks']));
},
error => {
this.translate.get('messenger.unauthorized').subscribe(t =>
this.messenger.show(t));
}
);
}
});
}
pauseProject() {
this.setPaused(true);
}
resumeProject() {
this.setPaused(false);
}
private averageTaskPerSecond() {
const averageDelta = ([x, ...xs]) => {
if (x === undefined) {
return NaN;
} else {
return xs.reduce(
([acc, last], x) => [acc + (x - last), x],
[0, x]
) [0] / xs.length;
}
};
const interval = this.snapshots.length > 1 ? this.snapshots[0].time_stamp - this.snapshots[1].time_stamp : 0;
if (interval != 0) {
this.avgTask = averageDelta(this.snapshots.reverse().map(s => s.closed_task_count) as any) / interval;
} else {
return 0;
}
}
private calculateEta() {
if (this.snapshots.length > 0) {
this.eta = moment.utc(this.snapshots[0].new_task_count / this.avgTask * 1000).format('D[d] HH[h]mm[m]ss[s]');
} else {
this.eta = 'N/A';
}
}
private setPaused(paused: boolean) {
this.dialog.open(AreYouSureComponent, {
width: '250px',
}).afterClosed().subscribe(result => {
this.project.paused = paused;
this.apiService.updateProject(this.project).subscribe(() => {
this.translate.get('messenger.acknowledged').subscribe(t =>
this.messenger.show(t));
}, error => {
this.translate.get('messenger.unauthorized').subscribe(t =>
this.messenger.show(t));
});
});
}
hardReset() {
}
}