Skip to content

Commit

Permalink
Improve chart by caching progresses, add chart for average step durat…
Browse files Browse the repository at this point in the history
…ion inside scenario (#211)

* Improve chart by caching progresses

* Add chart that displays the average duration inside a scenario

* fix that clearing scenarios does not work

* Rework to use map instead of list

* Fix labels to display scenario name

* remove logs and fix another label
  • Loading branch information
jggoebel authored May 7, 2024
1 parent d2f8ced commit 4151298
Show file tree
Hide file tree
Showing 7 changed files with 469 additions and 75 deletions.
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { EnvironmentDetailComponent } from './configuration/environments/environ
import { VmTemplateDetailComponent } from './configuration/vmtemplates/vmtemplate-detail/vmtemplate-detail.component';
import { NgChartsModule } from 'ng2-charts';
import { SessionStatisticsComponent } from './session-statistics/session-statistics.component';
import { SessionTimeStatisticsComponent } from './session-statistics/session-time-statistics/session-time-statistics.component';
import { VMTemplateServiceFormComponent } from './configuration/vmtemplates/edit-vmtemplate/vmtemplate-service-form/vmtemplate-service-form.component';
import { FilterScenariosComponent } from './filter-scenarios/filter-scenarios.component';
import { MDEditorComponent } from './scenario/md-editor/md-editor.component';
Expand Down Expand Up @@ -211,6 +212,7 @@ export function jwtOptionsFactory(): JwtConfig {
AppComponent,
HomeComponent,
SessionStatisticsComponent,
SessionTimeStatisticsComponent,
HeaderComponent,
EventComponent,
LoginComponent,
Expand Down
24 changes: 18 additions & 6 deletions src/app/session-statistics/session-statistics.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ <h3>Session Statistics</h3>
name="scenarios"
clrMulti="true"
>
<ng-container *clrOptionSelected="let scenarioSelected">
{{ scenarioSelected }}
<ng-container *clrOptionSelected="let scenario of scenarioSelected">
{{ getScenarioName(scenario) }}
</ng-container>
<clr-options>
<clr-option
*clrOptionItems="let scenario of scenariosWithSession"
[clrValue]="scenario"
*clrOptionItems="
let scenario of scenariosWithSessionMap | keyvalue
"
[clrValue]="scenario.key"
>
<cds-icon shape="node-group"></cds-icon> {{ scenario }}
<cds-icon shape="node-group"></cds-icon> {{ scenario.value }}
</clr-option>
</clr-options>
</clr-combobox>
Expand Down Expand Up @@ -165,10 +167,20 @@ <h4>Started Sessions (List)</h4>
<clr-dg-row
*clrDgItems="let item of totalSessionsPerScenario | keyvalue"
>
<clr-dg-cell>{{ item.key }}</clr-dg-cell>
<clr-dg-cell>{{ getScenarioName(item.key) }}</clr-dg-cell>
<clr-dg-cell>{{ item.value }}</clr-dg-cell>
</clr-dg-row>
</clr-datagrid>
</div>
</div>
<h3>Scenario Statistics</h3>
<div class="clr-row" *ngIf="progressesCache">
<div class="clr-col-12">
<app-session-time-statistics
[progresses]="progressesCache"
[scenariosWithSessionMap]="scenariosWithSessionMap"
>
</app-session-time-statistics>
</div>
</div>
</div>
123 changes: 77 additions & 46 deletions src/app/session-statistics/session-statistics.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
Component,
Input,
OnChanges,
OnInit,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
import { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';
import { BaseChartDirective } from 'ng2-charts';
import DataLabelsPlugin, { Context } from 'chartjs-plugin-datalabels';
Expand Down Expand Up @@ -39,11 +32,15 @@ const ONE_DAY = 1000 * 60 * 60 * 24;
styleUrls: ['./session-statistics.component.scss'],
})
export class SessionStatisticsComponent implements OnInit, OnChanges {
// If no scheduledEvent is given, we display statistics about all progresses for a given time range
// If a scheduledEvent is given, we display statistics about all progresses from this scheduledEvent
@Input()
public scheduledEvent: ScheduledEvent;

public currentScheduledEvent: ScheduledEvent;

public progressesCache: Progress[];

public startView: 'minute' | 'day' | 'month' | 'year' = 'day';
public minView: 'minute' | 'day' | 'month' | 'year' = 'day';
public options: Intl.DateTimeFormatOptions = {
Expand Down Expand Up @@ -108,7 +105,7 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
},
},
];
public scenariosWithSession: string[] = [];
public scenariosWithSessionMap: Map<string, string> = new Map(); // Maps the id to the name
public totalSessionsPerScenario: Map<string, number> = new Map();
public descSort = ClrDatagridSortOrder.DESC;

Expand All @@ -123,7 +120,14 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
this.currentScheduledEvent
) {
this.currentScheduledEvent = this.scheduledEvent;
this.setDatesToScheduledEvent(this.scheduledEvent);
this.progressesCache = null; // Reset cache so data from the changed SE can be retreived
this.scenariosWithSessionMap = new Map();
this.totalSessionsPerScenario = new Map();
this.chartDetails.controls.scenarios.setValue(['*']);
this.setDatesToScheduledEvent(
this.scheduledEvent,
this.chartDetails.controls.observationPeriod.value
);
this.updateData(this.chartDetails.controls.observationPeriod.value);
}
}
Expand Down Expand Up @@ -174,7 +178,7 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
);

if (this.scheduledEvent) {
this.setDatesToScheduledEvent(this.scheduledEvent);
this.setDatesToScheduledEvent(this.scheduledEvent, 'daily');
}

this.updateLabels('daily');
Expand Down Expand Up @@ -214,7 +218,7 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
}

public clearScenarios(): void {
this.chartDetails.controls.scenarios.reset();
this.chartDetails.controls.scenarios.setValue(['*']);
}

private validateStartDate(): ValidatorFn {
Expand Down Expand Up @@ -248,7 +252,10 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
};
}

private setDatesToScheduledEvent(se: ScheduledEvent) {
private setDatesToScheduledEvent(
se: ScheduledEvent,
observationPeriod: 'daily' | 'weekly' | 'monthly'
) {
const currentDate = new Date();

// Set default start date to beginning of the scheduledEvent
Expand All @@ -261,12 +268,13 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
this.endDate = currentDate;
}

if (this.chartDetails) {
this.chartDetails.controls.startDate.setValue(
this.startDate.toDateString()
);
this.chartDetails.controls.endDate.setValue(this.endDate.toDateString());
}
this.chartDetails.controls.endDate.setValue(
this.endDate.toLocaleDateString('en-US', this.options)
);
this.chartDetails.controls.startDate.setValue(
this.startDate.toLocaleDateString('en-US', this.options)
);
this.updateLabels(observationPeriod);
}

private updateData(observationPeriod: 'daily' | 'weekly' | 'monthly') {
Expand All @@ -281,20 +289,24 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
this.progressService
.listByRange(this.startDate, this.endDate)
.subscribe((progresses: Progress[]) => {
this.progressesCache = progresses;
this.processData(progresses, observationPeriod);
});
}

private updateDataByScheduledEvent(
observationPeriod: 'daily' | 'weekly' | 'monthly'
) {
this.progressService
.listByScheduledEvent(this.scheduledEvent.id, true)
.subscribe((progresses: Progress[]) => {
// TODO: Filter by range
progresses.forEach((p) => {});
this.processData(progresses, observationPeriod);
});
if (this.progressesCache) {
this.processData(this.progressesCache, observationPeriod);
} else {
this.progressService
.listByScheduledEvent(this.scheduledEvent.id, true)
.subscribe((progresses: Progress[]) => {
this.progressesCache = progresses;
this.processData(this.progressesCache, observationPeriod);
});
}
}

private processData(
Expand Down Expand Up @@ -362,33 +374,45 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
}
}

public setStartDate(d: DlDateTimePickerChange<Date>) {
this.startDate = d.value;
private updateStartDate(d: Date) {
this.startDate = d;
this.chartDetails.controls.startDate.setValue(
this.startDate.toLocaleDateString('en-US', this.options)
);
const observationPeriod: 'daily' | 'weekly' | 'monthly' =
this.chartDetails.controls.observationPeriod.value;
this.updateLabels(observationPeriod);
this.updateData(observationPeriod);
this.startDateSignpost.close();
}

public setEndDate(d: DlDateTimePickerChange<Date>) {
public setStartDate(d: DlDateTimePickerChange<Date>) {
this.updateStartDate(d.value);
if (this.startDateSignpost) {
this.startDateSignpost.close();
}
}

private updateEndDate(d: Date) {
const observationPeriod: 'daily' | 'weekly' | 'monthly' =
this.chartDetails.controls.observationPeriod.value;
if (observationPeriod != 'monthly') {
this.endDate = d.value;
this.endDate = d;
} else {
this.endDate = new Date(d.value.getFullYear(), d.value.getMonth() + 1, 0);
this.endDate = new Date(d.getFullYear(), d.getMonth() + 1, 0);
}
this.endDate.setHours(23, 59, 59, 999);
this.updateLabels(observationPeriod);
this.updateData(observationPeriod);
this.chartDetails.controls.endDate.setValue(
this.endDate.toLocaleDateString('en-US', this.options)
);
this.endDateSignpost.close();
}

public setEndDate(d: DlDateTimePickerChange<Date>) {
this.updateEndDate(d.value);
if (this.endDateSignpost) {
this.endDateSignpost.close();
}
}

// events
Expand All @@ -413,29 +437,30 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
}

private setupScenariosWithSessions(progressData: Progress[]) {
this.scenariosWithSession = [];
this.scenariosWithSessionMap = new Map();
progressData.forEach((prog: Progress) => {
if (!this.scenariosWithSession.includes(prog.scenario_name)) {
this.scenariosWithSession.push(prog.scenario_name);
}
this.scenariosWithSessionMap.set(prog.scenario, prog.scenario_name);
});
}

private allScenariosSelected(): boolean {
const selectedScenarios = this.chartDetails.controls.scenarios.value;
return selectedScenarios.length === 1 && selectedScenarios[0] === '*';
return (
!selectedScenarios ||
(selectedScenarios.length === 1 && selectedScenarios[0] === '*')
);
}

private prepareBarchartDatasets() {
this.barChartData.datasets.length = 0;
const selectedScenarios = this.chartDetails.controls.scenarios.value;
if (this.allScenariosSelected()) {
this.scenariosWithSession.forEach((sWithSession: string) => {
this.scenariosWithSessionMap.forEach((sWithSession: string) => {
this.barChartData.datasets.push({
data: Array.from<number>({
length: this.barChartData.labels.length,
}).fill(0),
label: sWithSession,
label: this.getScenarioName(sWithSession),
stack: 'a',
});
});
Expand All @@ -445,7 +470,7 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
data: Array.from<number>({
length: this.barChartData.labels.length,
}).fill(0),
label: sWithSession,
label: this.getScenarioName(sWithSession),
stack: 'a',
});
});
Expand Down Expand Up @@ -482,16 +507,18 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
return;
}
let evaluatedProgressData: Progress[] = progressData;
let selectedScenarios: string[] = this.scenariosWithSession;
let selectedScenarios: string[] = Array.from(
this.scenariosWithSessionMap.keys()
);
if (!this.allScenariosSelected()) {
selectedScenarios = this.chartDetails.controls.scenarios.value;
evaluatedProgressData = progressData.filter((progress: Progress) =>
selectedScenarios.includes(progress.scenario_name)
selectedScenarios.includes(progress.scenario)
);
}
evaluatedProgressData.forEach((prog: Progress) => {
const index = getIndex(prog);
(this.barChartData.datasets[selectedScenarios.indexOf(prog.scenario_name)]
(this.barChartData.datasets[selectedScenarios.indexOf(prog.scenario)]
.data[index] as number) += 1;
});
}
Expand All @@ -500,8 +527,8 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
this.totalSessionsPerScenario.clear();
this.totalSessionsPerScenario = progressData.reduce(
(totalSessions, progress) => {
const partialSum = totalSessions.get(progress.scenario_name) ?? 0;
totalSessions.set(progress.scenario_name, partialSum + 1);
const partialSum = totalSessions.get(progress.scenario) ?? 0;
totalSessions.set(progress.scenario, partialSum + 1);
return totalSessions;
},
new Map<string, number>()
Expand Down Expand Up @@ -559,4 +586,8 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
this.barChartData.labels.push(labelDateString);
}
}

public getScenarioName(scenarioId: string) {
return this.scenariosWithSessionMap.get(scenarioId) ?? scenarioId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<div class="clr-row">
<div class="clr-col-lg-4 clr-col-md-4 clr-col-12">
<form
[formGroup]="chartDetails"
[class.clr-error]="chartDetails.errors?.endDateLowerThanStartDate"
>
<clr-select-container>
<label>Scenario</label>
<select clrSelect formControlName="scenario">
<option
*ngFor="let scenario of scenariosWithSessionMap | keyvalue"
[value]="scenario.key"
>
{{ scenario.value }}
</option>
</select>
</clr-select-container>
</form>
</div>
</div>
<div class="clr-row">
<div class="clr-col-lg-8 clr-col-md-8 clr-col-12">
<div style="display: block">
<canvas
baseChart
[data]="barChartData"
[options]="barChartOptions"
[plugins]="barChartPlugins"
[type]="barChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"
>
</canvas>
</div>
</div>
<div class="clr-col-lg-4 clr-col-md-4 clr-col-12">
<clr-datagrid>
<clr-dg-column [clrDgField]="'key'">Step</clr-dg-column>
<clr-dg-column>Average Duration</clr-dg-column>
<clr-dg-column>Sessions</clr-dg-column>
<clr-dg-row *clrDgItems="let duration of avgDuration; index as i">
<clr-dg-cell>Step {{ i + 1 }}</clr-dg-cell>
<clr-dg-cell>{{ formatDuration(duration) }}</clr-dg-cell>
<clr-dg-cell>{{ getNumberOfSessionsForStep(i) }}</clr-dg-cell>
</clr-dg-row>
<clr-dg-row>
<clr-dg-cell><b>Total</b></clr-dg-cell>
<clr-dg-cell
><b>{{ formatDuration(totalDuration) }}</b></clr-dg-cell
>
<clr-dg-cell
><b>{{ getNumberOfSessionsForStep(0) }}</b></clr-dg-cell
>
</clr-dg-row>
</clr-datagrid>
</div>
</div>
Empty file.
Loading

0 comments on commit 4151298

Please sign in to comment.