diff --git a/nginx.conf b/nginx.conf index ff4d1c3f..52b518c7 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,13 +5,13 @@ events { } http { - client_max_body_size 2048M; + client_max_body_size 5120M; server { listen 80; server_name localhost; - client_max_body_size 2048M; + client_max_body_size 5120M; root /usr/share/nginx/html; index index.html index.htm; include /etc/nginx/mime.types; diff --git a/src/app/graphs/scenarios.ts b/src/app/graphs/scenarios.ts index 8ecdaf7d..f2728d96 100644 --- a/src/app/graphs/scenarios.ts +++ b/src/app/graphs/scenarios.ts @@ -11,7 +11,7 @@ export const scenarioHistory = (inputData) => { }; } const dt = inputData.map(_ => { - return { percentil: _.percentil, date: _.startDate }; + return { percentile90: _.percentile90, date: _.startDate }; }); return { type: "bar", @@ -20,7 +20,7 @@ export const scenarioHistory = (inputData) => { labels: dt.map(_ => _.date), datasets: [ { - data: dt.map(_ => _.percentil), + data: dt.map(_ => _.percentile90), backgroundColor: "rgb(17,122,139, 0.8)", fill: true, borderWidth: 1, diff --git a/src/app/item-detail/item-detail.component.html b/src/app/item-detail/item-detail.component.html index db669599..739f1edb 100644 --- a/src/app/item-detail/item-detail.component.html +++ b/src/app/item-detail/item-detail.component.html @@ -125,20 +125,52 @@

{{itemData.overview.throughput > 1000 ? -
+
-

{{ - Math.round((itemData.overview.percentil / 1000) * 100) / 100}} s +

{{ + Math.round(((itemData.overview.percentil || itemData.overview.percentile90) / 1000) * 100) / 100}} s

-

{{ - itemData.overview.percentil}} ms +

{{ + itemData.overview.percentil || itemData.overview.percentile90}} ms

+ +
+
+
+

{{ + Math.round((itemData.overview.percentile95 / 1000) * 100) / 100}} s +

+

{{ + itemData.overview.percentile95}} ms +

+

N/A ms

+ +
+ +
+
+ +
+
+
+

{{ + Math.round((itemData.overview.percentile99 / 1000) * 100) / 100}} s +

+

{{ + itemData.overview.percentile99}} ms +

+

N/A ms

+
+ +
+
+
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 +}