From f9f9d8c3cc36fbd7f20688175fcc67eab6035613 Mon Sep 17 00:00:00 2001 From: Ludek Novy <13610612+ludeknovy@users.noreply.github.com> Date: Tue, 24 Jan 2023 18:21:34 +0100 Subject: [PATCH] label chart reworked (#301) --- package-lock.json | 18 ++-- package.json | 4 +- src/app/_services/item-chart.service.ts | 26 +----- .../analyze-charts.component.ts | 14 +-- .../item-detail/item-detail.component.html | 8 +- src/app/item-detail/item-detail.component.ts | 6 +- src/app/item-detail/item-detail.module.ts | 2 +- .../label-chart/label-chart.component.html | 13 +-- .../label-chart/label-chart.component.spec.ts | 7 +- .../label-chart/label-chart.component.ts | 92 ++++++++++++------- .../request-stats-compare.component.css | 6 +- .../request-stats-compare.component.html | 83 ++++++++++------- .../request-stats-compare.component.ts | 12 +++ 13 files changed, 164 insertions(+), 127 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ba68482..a9941807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,8 +25,8 @@ "core-js": "^2.5.4", "deepmerge": "^4.2.2", "file-saver": "^2.0.5", - "highcharts": "^9.2.2", - "highcharts-angular": "^2.10.0", + "highcharts": "^10.3.3", + "highcharts-angular": "^3.0.0", "html2canvas": "^1.4.1", "moment": "^2.29.4", "ngx-spinner": "^12.0.0", @@ -8204,18 +8204,20 @@ "license": "MIT" }, "node_modules/highcharts": { - "version": "9.2.2", - "license": "https://www.highcharts.com/license" + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.3.tgz", + "integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw==" }, "node_modules/highcharts-angular": { - "version": "2.10.0", - "license": "SEE LICENSE IN ", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/highcharts-angular/-/highcharts-angular-3.0.0.tgz", + "integrity": "sha512-v2jMlvmzmLqqKVQdsARBewmYaQOL44lmioF9WEseuIF2KyTeZIeUtYbrEqG9uz+/dl0bsDZL6WtUrgCa42D3DQ==", "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/common": ">=6.0.0", - "@angular/core": ">=6.0.0", + "@angular/common": ">=9.0.0", + "@angular/core": ">=9.0.0", "highcharts": ">=6.0.0" } }, diff --git a/package.json b/package.json index e4800cf7..9e388edb 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "core-js": "^2.5.4", "deepmerge": "^4.2.2", "file-saver": "^2.0.5", - "highcharts": "^9.2.2", - "highcharts-angular": "^2.10.0", + "highcharts": "^10.3.3", + "highcharts-angular": "^3.0.0", "html2canvas": "^1.4.1", "moment": "^2.29.4", "ngx-spinner": "^12.0.0", diff --git a/src/app/_services/item-chart.service.ts b/src/app/_services/item-chart.service.ts index d20c70dd..d665b9f5 100644 --- a/src/app/_services/item-chart.service.ts +++ b/src/app/_services/item-chart.service.ts @@ -1,7 +1,6 @@ import { Injectable } from "@angular/core"; import { BehaviorSubject } from "rxjs"; -import { commonGraphSettings, errorLineSettings, networkLineSettings, threadLineSettings, throughputLineSettings } from "../graphs/item-detail"; -import { logScaleButton } from "../graphs/log-scale-button"; +import { errorLineSettings, networkLineSettings, threadLineSettings, throughputLineSettings } from "../graphs/item-detail"; import { bytesToMbps } from "../item-detail/calculations"; import { Metrics } from "../item-detail/metrics"; @@ -10,7 +9,7 @@ import { Metrics } from "../item-detail/metrics"; }) export class ItemChartService { - private plot$ = new BehaviorSubject({ labelCharts: new Map(), chartLines: null }); + private plot$ = new BehaviorSubject({ chartLines: null }); selectedPlot$ = this.plot$.asObservable(); setCurrentPlot(plot) { @@ -35,8 +34,6 @@ export class ItemChartService { labels: new Map(), statusCodes: new Map() } - // full chart configs - const labelCharts = new Map() if (overAllNetworkV2) { const networkMbps = overAllNetworkV2.data.map((_) => { @@ -61,46 +58,30 @@ export class ItemChartService { _.data = _.data.map(__ => [__[0], bytesToMbps(__[1])]); return _; }); - const networkChartOptions = { - ...commonGraphSettings("mbps"), - series: [...networkMbps, threadLine], ...logScaleButton - }; chartLines.labels.set(Metrics.Network, networkMbps.map((label) => ({ ...label, suffix: " mbps" }))); - labelCharts.set(Metrics.Network, networkChartOptions); } if (minResponseTime) { chartLines.labels.set(Metrics.ResponseTimeMin, minResponseTime.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeMin, { ...commonGraphSettings("ms"), series: [...minResponseTime, threadLine] }); } if (maxResponseTime) { chartLines.labels.set(Metrics.ResponseTimeMax, maxResponseTime.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeMax, { ...commonGraphSettings("ms"), series: [...maxResponseTime, threadLine] }); } if (percentile90) { chartLines.labels.set(Metrics.ResponseTimeP90, percentile90.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeP90, { ...commonGraphSettings("ms"), series: [...percentile90, threadLine] }); } if (percentile95) { chartLines.labels.set(Metrics.ResponseTimeP95, percentile95.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeP95, { ...commonGraphSettings("ms"), series: [...percentile95, threadLine] }); } if (percentile99) { chartLines.labels.set(Metrics.ResponseTimeP99, percentile99.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeP99, { ...commonGraphSettings("ms"), series: [...percentile99, threadLine] }); } - chartLines.labels.set(Metrics.ResponseTimeAvg, responseTime.map((label) => ({ ...label, suffix: " ms" }))); - labelCharts.set(Metrics.ResponseTimeAvg, { ...commonGraphSettings("ms"), series: [...responseTime, threadLine] }); - - chartLines.labels.set(Metrics.Throughput, throughput.map((label) => ({ ...label, suffix: " reqs/s" }))); - labelCharts.set(Metrics.Throughput, { ...commonGraphSettings("reqs/s"), series: [...throughput, threadLine] }); - - return { labelCharts, chartLines } + return { chartLines } } @@ -109,7 +90,6 @@ export class ItemChartService { interface ChartLines { - labelCharts: Map, chartLines: ChartLine } diff --git a/src/app/item-detail/analyze-charts/analyze-charts.component.ts b/src/app/item-detail/analyze-charts/analyze-charts.component.ts index 11fd5d4b..14822e95 100644 --- a/src/app/item-detail/analyze-charts/analyze-charts.component.ts +++ b/src/app/item-detail/analyze-charts/analyze-charts.component.ts @@ -79,15 +79,15 @@ export class AnalyzeChartsComponent implements OnInit { if (Array.isArray(currentChartSeries) && currentChartSeries.length > 0) { this.chartLines = plot.chartLines - const updatedChartSeries = currentChartSeries.map(serie => { - const labelChart = serie.label - const name = labelChart + const updatedChartSeries = currentChartSeries.map(series => { + const labelChart = series.label + const name = labelChart ? labelChart : "overall" - - return { + + return { name, - metric: serie.metric + metric: series.metric } }) this.updateChart(updatedChartSeries) @@ -135,7 +135,7 @@ export class AnalyzeChartsComponent implements OnInit { id: `${metric}: ${name}`, metric, label: name, - tooltip: { + tooltip: { valueSuffix: labelMetric.suffix }, yAxis diff --git a/src/app/item-detail/item-detail.component.html b/src/app/item-detail/item-detail.component.html index 0a172b5b..fcc92928 100644 --- a/src/app/item-detail/item-detail.component.html +++ b/src/app/item-detail/item-detail.component.html @@ -323,11 +323,7 @@
Status Codes Chart
-
-
- -
-
+
  • @@ -406,7 +402,7 @@
    Target System Statistics
    - +
    diff --git a/src/app/item-detail/item-detail.component.ts b/src/app/item-detail/item-detail.component.ts index 17fe63f9..74e9ea15 100644 --- a/src/app/item-detail/item-detail.component.ts +++ b/src/app/item-detail/item-detail.component.ts @@ -81,7 +81,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { async ngOnInit() { - this.spinner.show(); + await this.spinner.show(); this.route.params.pipe( withLatestFrom(_ => { this.sharedMainBarService.setProjectName(_.projectName); @@ -135,7 +135,6 @@ export class ItemDetailComponent implements OnInit, OnDestroy { private selectedPlotSubscription () { this.itemChartService.selectedPlot$.subscribe((value) => { this.chartLines = value.chartLines; - if (this.chartLines) { const overallChartSeries = Array.from(this.chartLines?.overall?.values()); this.overallChartOptions.series = JSON.parse(JSON.stringify(overallChartSeries)) @@ -240,9 +239,10 @@ export class ItemDetailComponent implements OnInit, OnDestroy { this.externalSearchTerm = $event.label; } + chartCallback: Highcharts.ChartCallbackFunction = function (chart): void { setTimeout(() => { chart.reflow(); },0); -} + } } diff --git a/src/app/item-detail/item-detail.module.ts b/src/app/item-detail/item-detail.module.ts index 46013001..5ef25b21 100644 --- a/src/app/item-detail/item-detail.module.ts +++ b/src/app/item-detail/item-detail.module.ts @@ -43,7 +43,7 @@ const routes: Routes = [ { CreateNewShareLinkComponent, MonitoringStatsComponent, ReloadCustomChartComponent, ChartIntervalComponent, ForbiddenComponent ], imports: [ CommonModule, NgbModule, RouterModule.forRoot(routes), DataTableModule, SharedItemModule, SharedModule, HighchartsChartModule, - ReactiveFormsModule, FormsModule, RoleModule + ReactiveFormsModule, FormsModule, RoleModule, ], exports: [], providers: [ExcelService] diff --git a/src/app/item-detail/label-chart/label-chart.component.html b/src/app/item-detail/label-chart/label-chart.component.html index 0c9ad63a..dd788810 100644 --- a/src/app/item-detail/label-chart/label-chart.component.html +++ b/src/app/item-detail/label-chart/label-chart.component.html @@ -1,20 +1,17 @@
    - Label Chart - {{labelChartMetric}} + Label Chart - {{chartMetric}} - - -
    -
    -
    diff --git a/src/app/item-detail/label-chart/label-chart.component.spec.ts b/src/app/item-detail/label-chart/label-chart.component.spec.ts index 1b95525e..844fccf9 100644 --- a/src/app/item-detail/label-chart/label-chart.component.spec.ts +++ b/src/app/item-detail/label-chart/label-chart.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { HighchartsChartModule } from "highcharts-angular"; import { LabelChartComponent } from "./label-chart.component"; +import { Metrics } from "../metrics"; describe("LabelChartComponent", () => { let component: LabelChartComponent; @@ -20,8 +21,10 @@ describe("LabelChartComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(LabelChartComponent); component = fixture.componentInstance; - component.labelCharts = new Map([ - ["Throughput", {}]]); + component.chartLines = { + labels: new Map([[Metrics.Network, [{ name: "name", suffix: "mbps", data: [] }] ]]), + overall: new Map([[Metrics.Threads, { name: "virtual-users", data: [] }]]) + }; fixture.detectChanges(); }); diff --git a/src/app/item-detail/label-chart/label-chart.component.ts b/src/app/item-detail/label-chart/label-chart.component.ts index 0789bc3b..b83b3eba 100644 --- a/src/app/item-detail/label-chart/label-chart.component.ts +++ b/src/app/item-detail/label-chart/label-chart.component.ts @@ -1,69 +1,97 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, Input, OnChanges, OnInit, SimpleChanges } from "@angular/core"; import * as Highcharts from "highcharts"; import { commonGraphSettings } from "src/app/graphs/item-detail"; import * as deepmerge from "deepmerge"; -import { ItemChartService } from "src/app/_services/item-chart.service"; +import { ChartLine } from "src/app/_services/item-chart.service"; +import { Metrics } from "../metrics"; @Component({ selector: "app-label-chart", templateUrl: "./label-chart.component.html", styleUrls: ["./label-chart.component.css", "../item-detail.component.scss"] }) -export class LabelChartComponent implements OnInit { +export class LabelChartComponent implements OnInit, OnChanges { + @Input() chartLines: ChartLine; + @Input() label: string; + @Input() activated: boolean; Highcharts: typeof Highcharts = Highcharts; - chartConstructor = "chart"; - labelChartMetric = "Throughput"; + chartMetric = "Response Times"; labelCompareChartMetric; labelChartOptions = commonGraphSettings("reqs/s"); updateLabelChartFlag = false; chartKeys; - seriesVisibilityToggle = true; - seriesVisibilityToggleText = "Hide all"; chartShouldExpand = false; - chart; - labelCharts + chart: Highcharts.Chart; + chartCallback; + labelCharts = new Map(); + private responseTimeMetricGroup: string[]; - constructor(private itemChartService: ItemChartService) {} + metricChartMap = new Map([ + [Metrics.Throughput, commonGraphSettings("reqs/s")], + [Metrics.Network, commonGraphSettings("mbps")], + [Metrics.ResponseTimeAvg, commonGraphSettings("ms")], + [Metrics.ResponseTimeMax, commonGraphSettings("ms")], + [Metrics.ResponseTimeMin, commonGraphSettings("ms")], + [Metrics.ResponseTimeP90, commonGraphSettings("ms")], + [Metrics.ResponseTimeP95, commonGraphSettings("ms")], + [Metrics.ResponseTimeP99, commonGraphSettings("ms")], + ]); + + constructor() { + this.chartCallback = chart => { + this.chart = chart; + }; + this.responseTimeMetricGroup = [ + Metrics.ResponseTimeP90, Metrics.ResponseTimeAvg, Metrics.ResponseTimeMin, + Metrics.ResponseTimeMax, Metrics.ResponseTimeP95, Metrics.ResponseTimeP99]; + } ngOnInit() { - this.itemChartService.selectedPlot$.subscribe(plot => { - this.labelCharts = plot.labelCharts - this.labelChartOptions = deepmerge(this.labelCharts.get(this.labelChartMetric), {}); - this.updateLabelChartFlag = true; - this.getChartsKey(); - }) + const threadLine = this.chartLines.overall.get(Metrics.Threads); + const availableMetrics = Array.from(this.chartLines.labels.keys()); + const responseTimesSeries = []; + availableMetrics.forEach((metric: Metrics) => { + const labelMetricsData = this.chartLines.labels.get(metric).find(data => data.name === this.label); + const chartSettings = this.metricChartMap.get(metric); + if (this.responseTimeMetricGroup.includes(metric)) { + responseTimesSeries.push({ data: labelMetricsData.data, suffix: labelMetricsData.suffix, name: metric, yAxis: 0 }); + } else { + this.labelCharts.set(metric, { ...chartSettings, series: [labelMetricsData, threadLine] }); + } + }); + this.labelCharts.set("Response Times", { ...commonGraphSettings("ms"), series: [...responseTimesSeries, threadLine] }); + this.labelChartOptions = commonGraphSettings("ms"); + this.updateLabelChartFlag = true; + this.getChartsKey(); } + ngOnChanges(changes: SimpleChanges) { + if (changes.activated.currentValue) { + this.changeChart({ target: { innerText: this.chartMetric } }); + this.chart.reflow(); + } + } + + private getChartsKey() { this.chartKeys = Array.from(this.labelCharts.keys()); } - changeChart(event) { - this.labelChartMetric = event.target.innerText; - - this.labelChartOptions = deepmerge(this.labelCharts.get(this.labelChartMetric), {}); + changeChart(event) { + this.chartMetric = event.target.innerText; + this.labelChartOptions = deepmerge(this.labelCharts.get(this.chartMetric), {}); this.updateLabelChartFlag = true; } - toggleSeriesVisibility() { - this.labelChartOptions.series.map(_ => _.visible = !this.seriesVisibilityToggle); - this.seriesVisibilityToggleText = this.seriesVisibilityToggle ? "Show all" : "Hide all"; - this.seriesVisibilityToggle = !this.seriesVisibilityToggle; - this.updateLabelChartFlag = true; - } collapseChart() { this.chartShouldExpand = !this.chartShouldExpand; this.chart.setSize(undefined, this.chartShouldExpand ? 650 : 350); } - - getInstance(chart): void { - setTimeout(() => { - chart.reflow(); - },0); - } } + + diff --git a/src/app/item-detail/request-stats/request-stats-compare.component.css b/src/app/item-detail/request-stats/request-stats-compare.component.css index ca00d358..46f5737f 100644 --- a/src/app/item-detail/request-stats/request-stats-compare.component.css +++ b/src/app/item-detail/request-stats/request-stats-compare.component.css @@ -4,4 +4,8 @@ #download { display: none; -} \ No newline at end of file +} + +.expand-button { + width: 30px; +} diff --git a/src/app/item-detail/request-stats/request-stats-compare.component.html b/src/app/item-detail/request-stats/request-stats-compare.component.html index 3f8d60db..a8a91a2f 100644 --- a/src/app/item-detail/request-stats/request-stats-compare.component.html +++ b/src/app/item-detail/request-stats/request-stats-compare.component.html @@ -53,6 +53,7 @@
    Request Statistics + @@ -96,56 +97,70 @@
    Request Statistics
    - - - - + + + + - - - - - - + + + + - + - + - + - + - + - + + + + + + +
    label
    {{_.label}} {{_.samples | number }} +
    + {{_.label}} {{_.samples | number }} {{_.avgResponseTime | number: '1.0-2'}} - + {{_.minResponseTime | number: '1.0-2'}} - + {{_.maxResponseTime | number: '1.0-2'}} - {{_.n0 | number: '1.0-2'}} - {{_.n5 | number: '1.0-2'}} - {{_.n9 | number: '1.0-2'}} - + {{_.n0 | number: '1.0-2'}} + {{_.n5 | number: '1.0-2'}} + {{_.n9 | number: '1.0-2'}} + {{_.throughput | number: '1.0-2'}} - + {{convertBytesToMbps(_.bytesPerSecond + _.bytesSentPerSecond) || 0 | number: '1.0-2' }} - + {{_.errorRate | number: '1.0-2'}} - + {{ calculateApdex(_.apdex?.satisfaction,_.apdex?.toleration, _.samples)}} - + - - - - + + + + - -
    diff --git a/src/app/item-detail/request-stats/request-stats-compare.component.ts b/src/app/item-detail/request-stats/request-stats-compare.component.ts index feb457af..e06819e6 100644 --- a/src/app/item-detail/request-stats/request-stats-compare.component.ts +++ b/src/app/item-detail/request-stats/request-stats-compare.component.ts @@ -20,6 +20,7 @@ export class RequestStatsCompareComponent implements OnInit, OnDestroy { @Input() itemData: ItemDetail; @Input() isAnonymous: boolean; @Input() params: ItemParams; + @Input() chartLines; @ViewChild("screen") screen: ElementRef; @ViewChild("canvas") canvas: ElementRef; @@ -34,6 +35,7 @@ export class RequestStatsCompareComponent implements OnInit, OnDestroy { defaultUnit = true; externalSearchTerm = ""; displayApdexColumn = false; + collapsableSettings = {} constructor( private itemsService: ItemsApiService, @@ -333,4 +335,14 @@ export class RequestStatsCompareComponent implements OnInit, OnDestroy { shouldApdexColumnBeDisplayed(): boolean { return this.displayApdexColumn } + + toggleSectionVisibility(event, index) { + // eslint-disable-next-line no-prototype-builtins + if (!this.collapsableSettings.hasOwnProperty(index)) { + this.collapsableSettings[index] = true + } else { + this.collapsableSettings[index] = !this.collapsableSettings[index]; + + } + } }