diff --git a/src/app/item-detail/request-stats/request-stats-compare.component.spec.ts b/src/app/item-detail/request-stats/request-stats-compare.component.spec.ts
index 1abf124b..00cad592 100644
--- a/src/app/item-detail/request-stats/request-stats-compare.component.spec.ts
+++ b/src/app/item-detail/request-stats/request-stats-compare.component.spec.ts
@@ -62,7 +62,10 @@ describe("RequestStatsCompareComponent", () => {
endDate: "",
startDate: "",
errorCount: 0,
- percentil: 10,
+ percentil: null,
+ percentile90: 10,
+ percentile95: 10,
+ percentile99: 10,
errorRate: 0,
throughput: 100
},
diff --git a/src/app/items.service.model.ts b/src/app/items.service.model.ts
index 460776e5..ba0e90ef 100644
--- a/src/app/items.service.model.ts
+++ b/src/app/items.service.model.ts
@@ -75,7 +75,10 @@ interface TopMetricsSettings {
avgResponseTime: boolean;
avgConnectionTime: boolean;
avgLatency: boolean;
- percentile: boolean;
+ percentile: boolean; // legacy setting
+ percentile90: boolean;
+ percentile95: boolean;
+ percentile99: boolean
}
interface ItemOverview {
@@ -86,20 +89,10 @@ interface ItemOverview {
endDate: string;
errorRate: number;
maxVu: number;
- percentil: number;
- startDate: string;
- throughput: number;
- errorCount?: number;
-}
-
-interface ItemOverview {
- avgLatency: number;
- avgResponseTime: number;
- duration: number;
- endDate: string;
- errorRate: number;
- maxVu: number;
- percentil: number;
+ percentil?: number; // legacy, it needs to be kept for backwards compatibility
+ percentile90: number;
+ percentile95: number;
+ percentile99: number;
startDate: string;
throughput: number;
errorCount?: number;
diff --git a/src/app/project/graph/scenarios-graph.component.ts b/src/app/project/graph/scenarios-graph.component.ts
index 227a10a8..75d32290 100644
--- a/src/app/project/graph/scenarios-graph.component.ts
+++ b/src/app/project/graph/scenarios-graph.component.ts
@@ -11,6 +11,7 @@ import {
} from "@angular/core";
import { Chart } from "chart.js";
import { scenarioHistory } from "src/app/graphs/scenarios";
+import { normalizeOverviewData } from "../../utils/normalizeOverviewData";
@Component({
selector: "app-scenarios-graph",
@@ -46,7 +47,7 @@ export class ScenariosGraphComponent implements AfterViewInit, OnDestroy {
data.datasets[0].data[i] = 0;
}
},
- // after the update ..
+ // after the update
afterUpdate: function(chart) {
if (length === -1) { return; }
},
@@ -68,7 +69,7 @@ export class ScenariosGraphComponent implements AfterViewInit, OnDestroy {
}
}
});
- this.chart = new Chart(this.chartCanvas.nativeElement, scenarioHistory(this.graphData));
+ this.chart = new Chart(this.chartCanvas.nativeElement, scenarioHistory(this.graphData.map(normalizeOverviewData)));
}
ngOnDestroy(): void {
diff --git a/src/app/scenario/scenario-trends/scenario-trends.component.ts b/src/app/scenario/scenario-trends/scenario-trends.component.ts
index a1dc4ae4..c844e7c9 100644
--- a/src/app/scenario/scenario-trends/scenario-trends.component.ts
+++ b/src/app/scenario/scenario-trends/scenario-trends.component.ts
@@ -7,11 +7,12 @@ import { bytesToMbps } from "src/app/item-detail/calculations";
import { LabelTrendsData, ScenarioTrendsData, ScenarioTrendsUserSettings } from "src/app/items.service.model";
import { ScenarioService } from "src/app/scenario.service";
import { Metrics } from "../../item-detail/metrics";
+import { normalizeOverviewData } from "../../utils/normalizeOverviewData";
@Component({
- selector: "app-scenario-trends",
- templateUrl: "./scenario-trends.component.html",
- styleUrls: ["./scenario-trends.component.scss"]
+ selector: 'app-scenario-trends',
+ templateUrl: './scenario-trends.component.html',
+ styleUrls: ['./scenario-trends.component.scss']
})
export class ScenarioTrendsComponent implements OnInit {
@Input() params;
@@ -43,7 +44,7 @@ export class ScenarioTrendsComponent implements OnInit {
constructor(private scenarioService: ScenarioService, private router: Router,
) {
this.chartDataMapping = new Map([
- ["percentil", { name: Metrics.ResponseTimeP90, onLoad: true, color: "rgb(17,122,139, 0.8)", tooltip: { valueSuffix: " ms" } }],
+ ["percentile90", { name: Metrics.ResponseTimeP90, onLoad: true, color: "rgb(17,122,139, 0.8)", tooltip: { valueSuffix: " ms" } }],
["avgResponseTime", { name: Metrics.ResponseTimeAvg, onLoad: false, tooltip: { valueSuffix: " ms" } }],
["avgLatency", { name: Metrics.LatencyAvg, onLoad: false, tooltip: { valueSuffix: " ms" } }],
["avgConnect", { name: Metrics.ConnectAvg, onLoad: false, tooltip: { valueSuffix: " ms" } }],
@@ -53,7 +54,7 @@ export class ScenarioTrendsComponent implements OnInit {
["network", { name: Metrics.Network, yAxis: 4, onLoad: false, transform: this.networkTransform, tooltip: { valueSuffix: " mbps" } }],
]);
}
-
+
ngOnInit() {
this.scenarioService.trends$.subscribe((_: {
aggregatedTrends: ScenarioTrendsData[],
@@ -65,19 +66,22 @@ export class ScenarioTrendsComponent implements OnInit {
return;
}
this.userSettings = _.userSettings;
- this.generateAggregateChartLines(_.aggregatedTrends);
+ this.generateAggregatedChartLines(_.aggregatedTrends);
this.generateLabelChartLines(_.labelTrends);
this.generateDegradationCurve(_.responseTimeDegradationCurve);
});
}
- generateAggregateChartLines(data: ScenarioTrendsData[]) {
+ generateAggregatedChartLines(data: ScenarioTrendsData[]) {
if (!Array.isArray(data)) {
return;
}
const dates = data.map(_ => moment(_.overview.startDate).format("DD. MM. YYYY HH:mm:ss"));
const series = [];
- const seriesData = data.reduce((acc, current) => {
+ const seriesData = data.map((scenarioTrends) => {
+ scenarioTrends.overview = normalizeOverviewData(scenarioTrends.overview);
+ return scenarioTrends;
+ }).reduce((acc, current) => {
for (const key of Object.keys(current.overview)) {
if (!["startDate", "endDate", "duration"].includes(key)) {
@@ -91,7 +95,7 @@ export class ScenarioTrendsComponent implements OnInit {
return acc;
}, {});
- this.setItemIds(data)
+ this.setItemIds(data);
for (const key of Object.keys(seriesData)) {
const chartSeriesSettings = this.chartDataMapping.get(key);
@@ -126,9 +130,18 @@ export class ScenarioTrendsComponent implements OnInit {
for (const key of Object.keys(data)) {
// Adding item id to correctly identify it when clicking a point.
- seriesP90.push({ name: key, data: data[key].percentile90.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] })) });
- seriesErrorRate.push({ name: key, data: data[key].errorRate.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] })) });
- seriesThroughput.push({ name: key, data: data[key].throughput.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] })) });
+ seriesP90.push({
+ name: key,
+ data: data[key].percentile90.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] }))
+ });
+ seriesErrorRate.push({
+ name: key,
+ data: data[key].errorRate.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] }))
+ });
+ seriesThroughput.push({
+ name: key,
+ data: data[key].throughput.map(dataValue => ({ y: dataValue[1], name: dataValue[0], itemId: dataValue[2] }))
+ });
}
this.updateLabelChart(this.labelScenarioTrendChartP90Option, seriesP90);
this.updateLabelChart(this.labelScenarioTrendChartThroughputOption, seriesThroughput);
@@ -150,7 +163,7 @@ export class ScenarioTrendsComponent implements OnInit {
// Label series have item id amended to open correct detail, it's needed for a case when a labels do not match, eg:
// label2 start at point 0, but label2 starts at point 1, it leads to off by N issues.
// It's not needed for aggregated trend chart, as that is column chart and only one point can be clicked.
- const boundItemId = event.point.series.data[event.point.index]?.options?.itemId
+ const boundItemId = event.point.series.data[event.point.index]?.options?.itemId;
const itemId = boundItemId || Array.from(this.itemIds)[event.point.index];
const { projectName, scenarioName } = this.params;
@@ -174,9 +187,9 @@ export class ScenarioTrendsComponent implements OnInit {
private setItemIds = (data: ScenarioTrendsData[]) => {
if (this.itemIds.size > 0) {
- this.itemIds.clear()
+ this.itemIds.clear();
}
- data.forEach(line => this.itemIds.add(line.id))
- }
+ data.forEach(line => this.itemIds.add(line.id));
+ };
}
diff --git a/src/app/shared/shared-project/project-settings/project-settings.component.html b/src/app/shared/shared-project/project-settings/project-settings.component.html
index 184c14c9..b8701878 100644
--- a/src/app/shared/shared-project/project-settings/project-settings.component.html
+++ b/src/app/shared/shared-project/project-settings/project-settings.component.html
@@ -63,10 +63,23 @@
Test Report Top Statistics Bar
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/shared-project/project-settings/project-settings.component.ts b/src/app/shared/shared-project/project-settings/project-settings.component.ts
index c36aeae1..7a376738 100644
--- a/src/app/shared/shared-project/project-settings/project-settings.component.ts
+++ b/src/app/shared/shared-project/project-settings/project-settings.component.ts
@@ -10,21 +10,23 @@ import { UserService } from "../../../_services/user.service";
import { UserRole, Users } from "../../../_services/users.model";
@Component({
- selector: "app-project-settings",
- templateUrl: "./project-settings.component.html",
- styleUrls: ["./project-settings.component.css"]
+ selector: 'app-project-settings',
+ templateUrl: './project-settings.component.html',
+ styleUrls: ['./project-settings.component.css']
})
export class ProjectSettingsComponent implements OnInit {
projectSettingsForm: FormGroup;
projectMembersForm: FormGroup;
projectMembers: FormArray;
- projectMembersData
+ projectMembersData;
metricsEditable;
formControls = {
virtualUsers: null,
throughput: null,
- percentile: null,
+ percentile90: null,
+ percentile95: null,
+ percentile99: null,
avgResponseTime: null,
avgConnectionTime: null,
avgLatency: null,
@@ -49,7 +51,8 @@ export class ProjectSettingsComponent implements OnInit {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
- ngOnInit() {}
+ ngOnInit() {
+ }
get usersFormArray() {
return this.projectMembersForm.controls.projectMembers as FormArray;
@@ -57,23 +60,25 @@ export class ProjectSettingsComponent implements OnInit {
createFormControls(settings) {
this.formControls.virtualUsers = new FormControl(settings.topMetricsSettings.virtualUsers, []);
- this.formControls.percentile = new FormControl(settings.topMetricsSettings.percentile, []);
+ this.formControls.percentile90 = new FormControl((settings.topMetricsSettings.percentile || settings.topMetricsSettings.percentile90), []);
+ this.formControls.percentile95 = new FormControl(settings.topMetricsSettings.percentile95 || false, []);
+ this.formControls.percentile99 = new FormControl(settings.topMetricsSettings.percentile99 || false, []);
this.formControls.throughput = new FormControl(settings.topMetricsSettings.throughput, []);
this.formControls.errorRate = new FormControl(settings.topMetricsSettings.errorRate, []);
this.formControls.errorCount = new FormControl(settings.topMetricsSettings.errorCount || false, []),
- this.formControls.network = new FormControl(settings.topMetricsSettings.network, []);
+ this.formControls.network = new FormControl(settings.topMetricsSettings.network, []);
this.formControls.networkSent = new FormControl(settings.topMetricsSettings.networkSent || false, []);
this.formControls.networkReceived = new FormControl(settings.topMetricsSettings.networkReceived || false, []);
this.formControls.avgLatency = new FormControl(settings.topMetricsSettings.avgLatency, []);
this.formControls.avgConnectionTime = new FormControl(settings.topMetricsSettings.avgConnectionTime, []);
this.formControls.avgResponseTime = new FormControl(settings.topMetricsSettings.avgResponseTime, []);
- this.formControls.scenarioUpsert = new FormControl(settings.upsertScenario, [])
+ this.formControls.scenarioUpsert = new FormControl(settings.upsertScenario, []);
this.formControls.projectName = new FormControl(settings.projectName, [
Validators.required,
Validators.maxLength(50),
Validators.minLength(3),
]);
- this.formControls.projectMembers = new FormArray([])
+ this.formControls.projectMembers = new FormArray([]);
}
createForm() {
@@ -81,7 +86,9 @@ export class ProjectSettingsComponent implements OnInit {
virtualUsers: this.formControls.virtualUsers,
errorRate: this.formControls.errorRate,
errorCount: this.formControls.errorCount,
- percentile: this.formControls.percentile,
+ percentile90: this.formControls.percentile90,
+ percentile95: this.formControls.percentile95,
+ percentile99: this.formControls.percentile99,
throughput: this.formControls.throughput,
network: this.formControls.network,
networkSent: this.formControls.networkSent,
@@ -95,7 +102,7 @@ export class ProjectSettingsComponent implements OnInit {
});
this.projectMembersForm = new FormGroup({
projectMembers: this.formControls.projectMembers,
- })
+ });
}
open(content) {
@@ -104,9 +111,9 @@ export class ProjectSettingsComponent implements OnInit {
if (role === "admin") {
this.userService.fetchUsers().subscribe((users) => {
- this.mapProjectMembersToUsersData(r.body.projectMembers, users)
- this.addCheckboxes()
- })
+ this.mapProjectMembersToUsersData(r.body.projectMembers, users);
+ this.addCheckboxes();
+ });
}
this.createFormControls(r.body);
@@ -123,7 +130,7 @@ export class ProjectSettingsComponent implements OnInit {
const projectMembers = this.projectMembersData
.filter(member => member.role !== UserRole.Admin)
.filter(member => member.isMember)
- .map(member => member.id)
+ .map(member => member.id);
const payload = {
projectName: this.formControls.projectName.value,
@@ -131,7 +138,9 @@ export class ProjectSettingsComponent implements OnInit {
topMetricsSettings: {
virtualUsers: this.formControls.virtualUsers.value,
errorRate: this.formControls.errorRate.value,
- percentile: this.formControls.percentile.value,
+ percentile90: this.formControls.percentile90.value,
+ percentile95: this.formControls.percentile95.value,
+ percentile99: this.formControls.percentile99.value,
throughput: this.formControls.throughput.value,
network: this.formControls.network.value,
avgLatency: this.formControls.avgLatency.value,
@@ -151,15 +160,16 @@ export class ProjectSettingsComponent implements OnInit {
this.projectService.loadProjects();
return this.projectApiService.setData(message);
});
- this.projectSettingsForm.reset()
+ this.projectSettingsForm.reset();
this.modalService.dismissAll();
}
}
isEditable() {
- const enabledMetrics = Object.values([this.formControls.virtualUsers, this.formControls.errorRate, this.formControls.percentile,
- this.formControls.throughput, this.formControls.network, this.formControls.avgLatency, this.formControls.avgResponseTime,
- this.formControls.avgConnectionTime, this.formControls.errorCount, this.formControls.networkSent, this.formControls.networkReceived])
+ const enabledMetrics = Object.values([this.formControls.virtualUsers, this.formControls.errorRate, this.formControls.percentile90,
+ this.formControls.percentile95, this.formControls.percentile99, this.formControls.throughput, this.formControls.network,
+ this.formControls.avgLatency, this.formControls.avgResponseTime,
+ this.formControls.avgConnectionTime, this.formControls.errorCount, this.formControls.networkSent, this.formControls.networkReceived])
.map(control => control.value).filter(value => value === true);
this.metricsEditable = enabledMetrics.length > 5;
}
@@ -171,17 +181,17 @@ export class ProjectSettingsComponent implements OnInit {
private mapProjectMembersToUsersData = (projectMembers: string[], users: Users[]) => {
this.projectMembersData = users
.map(user => {
- const isMember = projectMembers.find(member => member === user.id)
- return {
- id: user.id,
- username: user.username,
- isMember: user.role === UserRole.Admin ? true : !!isMember,
- role: user.role,
- isDisabled: user.role === UserRole.Admin
- }
- })
+ const isMember = projectMembers.find(member => member === user.id);
+ return {
+ id: user.id,
+ username: user.username,
+ isMember: user.role === UserRole.Admin ? true : !!isMember,
+ role: user.role,
+ isDisabled: user.role === UserRole.Admin
+ };
+ });
- }
+ };
private addCheckboxes() {
this.projectMembersData.forEach((projectMember) => this.usersFormArray.push(new FormControl(projectMember.isMember)));
diff --git a/src/app/utils/normalizeOverviewData.ts b/src/app/utils/normalizeOverviewData.ts
new file mode 100644
index 00000000..4815bc57
--- /dev/null
+++ b/src/app/utils/normalizeOverviewData.ts
@@ -0,0 +1,7 @@
+export const normalizeOverviewData = (overviewData: any) => {
+ // legacy property `percentil` mapped to a new property
+ if (overviewData["percentil"]) {
+ overviewData["percentile90"] = overviewData["percentil"]
+ }
+ return overviewData
+}