diff --git a/src/app/dashboard/dashboard.component.html b/src/app/dashboard/dashboard.component.html index 6303bf1b..40cfd30a 100644 --- a/src/app/dashboard/dashboard.component.html +++ b/src/app/dashboard/dashboard.component.html @@ -104,13 +104,10 @@
Recent Test Runs
- - - - + + diff --git a/src/app/dashboard/dashboard.component.ts b/src/app/dashboard/dashboard.component.ts index c852d183..6752e9ef 100644 --- a/src/app/dashboard/dashboard.component.ts +++ b/src/app/dashboard/dashboard.component.ts @@ -3,7 +3,7 @@ import { ProjectApiService } from "../project-api.service"; import { ItemsListing, ProjectsOverallStats } from "../items.service.model"; import { Router } from "@angular/router"; import { SharedMainBarService } from "../shared-main-bar.service"; -import { showZeroErrorWarning } from "../utils/showZeroErrorTolerance"; +import { getValidationResults } from "../utils/showZeroErrorTolerance"; @Component({ selector: "app-dashboard", @@ -19,9 +19,10 @@ export class DashboardComponent implements OnInit { private projectService: ProjectApiService, private router: Router, private sharedMainBarService: SharedMainBarService - ) { - this.Math = Math; - } + ) { + this.Math = Math; + } + ngOnInit(): void { this.fetchLatestItems(); this.fetchOverallStats(); @@ -29,7 +30,10 @@ export class DashboardComponent implements OnInit { } fetchLatestItems() { - this.projectService.fetchLatestItems().subscribe(_ => this.latestItems = _); + this.projectService.fetchLatestItems() + .subscribe(_ => { + this.latestItems = _; + }); } fetchOverallStats() { @@ -40,8 +44,14 @@ export class DashboardComponent implements OnInit { this.router.navigate([`./project/${projectName}/scenario/${scenarioName}/item/${itemId}`]); } - showZeroErrorToleranceWarning(errorCount, errorRate) { - return showZeroErrorWarning(errorRate, errorCount); + displayItemValidationError(zeroErrorEnabled, errorCount, errorRate, duration, minTestDuration) { + const { zeroErrorToleranceValidation, minTestDurationValidation } = getValidationResults(zeroErrorEnabled, errorRate, errorCount, duration, minTestDuration); + return zeroErrorToleranceValidation || minTestDurationValidation + } + + + isValidationEnabled(zeroErrorEnabled, minTestDuration): boolean { + return zeroErrorEnabled || minTestDuration > 0; } } diff --git a/src/app/item-detail/item-detail.component.html b/src/app/item-detail/item-detail.component.html index 23814817..cb898153 100644 --- a/src/app/item-detail/item-detail.component.html +++ b/src/app/item-detail/item-detail.component.html @@ -65,9 +65,19 @@ -
+
- +
+
+
Test failure
+
+ +
+
+ +
+
+
diff --git a/src/app/item-detail/item-detail.component.ts b/src/app/item-detail/item-detail.component.ts index e8cc4c5b..65df30fc 100644 --- a/src/app/item-detail/item-detail.component.ts +++ b/src/app/item-detail/item-detail.component.ts @@ -15,7 +15,7 @@ import { bytesToMbps } from "./calculations"; import { ItemStatusValue } from "./item-detail.model"; import { Metrics } from "./metrics"; import { AnalyzeChartService } from "../analyze-chart.service"; -import { showZeroErrorWarning } from "../utils/showZeroErrorTolerance"; +import { getValidationResults } from "../utils/showZeroErrorTolerance"; import { ItemChartService } from "../_services/item-chart.service"; exporting(Highcharts); @@ -59,6 +59,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { userSettings: null, errorSummary: null, status: null, + minTestDuration: null, }; overallChartOptions; scatterChartOptions; @@ -76,10 +77,13 @@ export class ItemDetailComponent implements OnInit, OnDestroy { chartLines; activeId = 1; performanceAnalysisLines = null; - externalSearchTerm = null; totalRequests = null; - plotRangeMin = null - plotRangeMax = null + plotRangeMin = null; + plotRangeMax = null; + validations = { + zeroErrorValidation: null, + minTestDurationValidation: null, + }; constructor( @@ -129,6 +133,11 @@ export class ItemDetailComponent implements OnInit, OnDestroy { this.selectedPlotSubscription(); this.plotRangeSubscription(); this.calculateTotalRequests(); + const validations = this.showValidationWarning(this.itemData); + this.validations = { + zeroErrorValidation: validations.zeroErrorToleranceValidation, + minTestDurationValidation: validations.minTestDurationValidation + }; this.spinner.hide(); }); this.analyzeChartService.currentData.subscribe(data => { @@ -140,6 +149,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { this.overallChartOptions = { ...overallChartSettings("ms") }; + } ngOnDestroy() { @@ -189,14 +199,14 @@ export class ItemDetailComponent implements OnInit, OnDestroy { */ private plotRangeSubscription() { this.itemChartService.plotRange$.subscribe((value) => { - this.updateMinMaxOfCharts(value?.start?.getTime(), value?.end?.getTime()) + this.updateMinMaxOfCharts(value?.start?.getTime(), value?.end?.getTime()); }); } private updateMinMaxOfCharts(min, max) { if (min && max) { - this.plotRangeMin = min - this.plotRangeMax = max + this.plotRangeMin = min; + this.plotRangeMax = max; for (const chartOptions of [this.overallChartOptions, this.scatterChartOptions, this.statusChartOptions]) { chartOptions.xAxis.min = min; chartOptions.xAxis.max = max; @@ -279,21 +289,22 @@ export class ItemDetailComponent implements OnInit, OnDestroy { return bytesToMbps(bytes); } - showZeroErrorToleranceWarning(): boolean | string { - if (this.itemData.zeroErrorToleranceEnabled) { - return showZeroErrorWarning(this.itemData.overview.errorRate, - this.itemData.overview.errorCount); + showValidationWarning(itemData: ItemDetail): { zeroErrorToleranceValidation: boolean, minTestDurationValidation: boolean } { + if (itemData.zeroErrorToleranceEnabled || itemData.minTestDuration > 0) { + return getValidationResults( + itemData.zeroErrorToleranceEnabled, + itemData.overview.errorRate, + itemData.overview.errorCount, + itemData.overview.duration, + itemData.minTestDuration + ); } - return false; - } - - focusOnLabel($event: { label: string, metrics: Metrics[] }) { - this.activeId = 2; - this.performanceAnalysisLines = $event; - this.externalSearchTerm = $event.label; + return { + zeroErrorToleranceValidation: false, + minTestDurationValidation: false, + }; } - chartCallback: Highcharts.ChartCallbackFunction = function (chart): void { setTimeout(() => { chart.reflow(); @@ -314,13 +325,13 @@ export class ItemDetailComponent implements OnInit, OnDestroy { } else { const originalSeries = Array.from(this.chartLines?.overall?.values()); this.overallChartOptions = overallChartSettings(""); - this.overallChartOptions.series = originalSeries + this.overallChartOptions.series = originalSeries; } // we need always to set the correct zoom range - this.overallChartOptions.xAxis.min = this.plotRangeMin - this.overallChartOptions.xAxis.max = this.plotRangeMax + this.overallChartOptions.xAxis.min = this.plotRangeMin; + this.overallChartOptions.xAxis.max = this.plotRangeMax; - this.updateOverallChartFlag = true; + this.updateOverallChartFlag = true; } } diff --git a/src/app/item-detail/item-detail.module.ts b/src/app/item-detail/item-detail.module.ts index a1edc3c3..2a17beba 100644 --- a/src/app/item-detail/item-detail.module.ts +++ b/src/app/item-detail/item-detail.module.ts @@ -9,7 +9,6 @@ import { AuthGuard } from "../auth.guard"; import { RouterModule, Routes } from "@angular/router"; import { SharedItemModule } from "../shared/shared-item/shared-item.module"; import { SharedModule } from "../shared/shared.module"; -import { ZeroErrorToleranceWarningComponent } from "./zero-error-tolerance-warning/zero-error-tolerance-warning.component"; import { HighchartsChartModule } from "highcharts-angular"; import { LabelChartComponent } from "./label-chart/label-chart.component"; import { AnalyzeChartsComponent } from "./analyze-charts/analyze-charts.component"; @@ -31,6 +30,7 @@ import { ForbiddenComponent } from "../forbidden/forbidden.component"; import { ThresholdFailureComponent } from "./request-stats/threshold-failure/threshold-failure.component"; import { ZoomChartsComponent } from "./zoom-charts/zoom-charts.component"; import { ErrorSummaryComponent } from "./error-summary/error-summary.component"; +import { ValidationsModule } from "./validations/validations.module"; const routes: Routes = [{ @@ -41,11 +41,11 @@ const routes: Routes = [{ @NgModule({ declarations: [ItemDetailComponent, RequestStatsCompareComponent, ThresholdsAlertComponent, - PerformanceAnalysisComponent, ZeroErrorToleranceWarningComponent, LabelChartComponent, AnalyzeChartsComponent, + PerformanceAnalysisComponent, LabelChartComponent, AnalyzeChartsComponent, LabelHealthComponent, LabelTrendComponent, StatsCompareComponent, AddMetricComponent, ShareComponent, DeleteShareLinkComponent, CreateNewShareLinkComponent, MonitoringStatsComponent, ReloadCustomChartComponent, ChartIntervalComponent, ForbiddenComponent, ThresholdFailureComponent, ZoomChartsComponent, ErrorSummaryComponent], imports: [ - CommonModule, NgbModule, RouterModule.forRoot(routes), DataTableModule, SharedItemModule, SharedModule, HighchartsChartModule, + CommonModule, NgbModule, RouterModule.forRoot(routes), DataTableModule, SharedItemModule, SharedModule, HighchartsChartModule, ValidationsModule, ReactiveFormsModule, FormsModule, RoleModule, ], exports: [], diff --git a/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.css b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.html b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.html new file mode 100644 index 00000000..872b8098 --- /dev/null +++ b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.html @@ -0,0 +1,8 @@ + +
+
+ The test duration was too short
+
+ The scenario is set with minimum test duration. +
+
diff --git a/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.spec.ts b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.spec.ts new file mode 100644 index 00000000..65f08f74 --- /dev/null +++ b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { MinTestDurationWarningComponent } from "./min-test-duration-warning.component"; + +describe("MinTestDurationWarningComponent", () => { + let component: MinTestDurationWarningComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MinTestDurationWarningComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MinTestDurationWarningComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.ts b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.ts new file mode 100644 index 00000000..337c56e0 --- /dev/null +++ b/src/app/item-detail/validations/min-test-duration-warning/min-test-duration-warning.component.ts @@ -0,0 +1,10 @@ +import { Component, OnInit } from "@angular/core"; + +@Component({ + selector: "app-min-test-duration-warning", + templateUrl: "./min-test-duration-warning.component.html", + styleUrls: ["./min-test-duration-warning.component.css"] +}) +export class MinTestDurationWarningComponent { + +} diff --git a/src/app/item-detail/validations/validations.module.ts b/src/app/item-detail/validations/validations.module.ts new file mode 100644 index 00000000..efb5d357 --- /dev/null +++ b/src/app/item-detail/validations/validations.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { MinTestDurationWarningComponent } from "./min-test-duration-warning/min-test-duration-warning.component"; +import { ZeroErrorToleranceWarningComponent } from "./zero-error-tolerance-warning/zero-error-tolerance-warning.component"; + + +@NgModule({ + declarations: [MinTestDurationWarningComponent, ZeroErrorToleranceWarningComponent], + exports: [ + ZeroErrorToleranceWarningComponent, + MinTestDurationWarningComponent + ], + imports: [ + CommonModule + ] +}) +export class ValidationsModule { +} diff --git a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.css b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.css similarity index 100% rename from src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.css rename to src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.css diff --git a/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html new file mode 100644 index 00000000..33d89aa3 --- /dev/null +++ b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html @@ -0,0 +1,8 @@ +
+
+ Error(s) detected
+
+ The scenario has zero error tolerance check enabled. +
+
diff --git a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.spec.ts b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.spec.ts similarity index 100% rename from src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.spec.ts rename to src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.spec.ts diff --git a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts similarity index 89% rename from src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts rename to src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts index 43599072..e6bcbed8 100644 --- a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts +++ b/src/app/item-detail/validations/zero-error-tolerance-warning/zero-error-tolerance-warning.component.ts @@ -3,7 +3,7 @@ import { Component, OnInit } from "@angular/core"; @Component({ selector: "app-zero-error-tolerance-warning", templateUrl: "./zero-error-tolerance-warning.component.html", - styleUrls: ["./zero-error-tolerance-warning.component.css", "../item-detail.component.scss"] + styleUrls: ["./zero-error-tolerance-warning.component.css", "../../item-detail.component.scss"] }) export class ZeroErrorToleranceWarningComponent { diff --git a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html b/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html deleted file mode 100644 index 7b0368a4..00000000 --- a/src/app/item-detail/zero-error-tolerance-warning/zero-error-tolerance-warning.component.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-
Test failure
-
-
- Error(s) detected
-
- The scenario has zero error tolerance check enabled. -
-
-
-
diff --git a/src/app/items.service.model.ts b/src/app/items.service.model.ts index 3e7c669f..460776e5 100644 --- a/src/app/items.service.model.ts +++ b/src/app/items.service.model.ts @@ -7,6 +7,7 @@ export interface ItemsListing { environment: string; status: string; zeroErrorToleranceEnabled: boolean; + minTestDuration: number, thresholdPassed?: boolean; overview: ItemOverview; @@ -63,6 +64,7 @@ export interface ItemDetail { }; errorSummary: ErrorSummary status: string + minTestDuration: number } interface TopMetricsSettings { diff --git a/src/app/scenario.service.model.ts b/src/app/scenario.service.model.ts index c9643cc3..c3087947 100644 --- a/src/app/scenario.service.model.ts +++ b/src/app/scenario.service.model.ts @@ -1,6 +1,7 @@ export interface Scenario { analysisEnabled: boolean; zeroErrorToleranceEnabled: boolean; + minTestDuration: boolean; name: string; keepTestRunsPeriod: number; thresholds: { diff --git a/src/app/scenario/scenario-settings/scenario-settings.component.html b/src/app/scenario/scenario-settings/scenario-settings.component.html index 414d77bd..571f08db 100644 --- a/src/app/scenario/scenario-settings/scenario-settings.component.html +++ b/src/app/scenario/scenario-settings/scenario-settings.component.html @@ -74,6 +74,28 @@
Zero error tolerance

+
+
Minimum test duration
+
+ Set the minimum test duration required to consider a test as invalid. Setting this value to zero will deactivate this feature.
+
+
+ + + +
+
+
+ +
+
Delete sample data after processing
diff --git a/src/app/scenario/scenario-settings/scenario-settings.component.ts b/src/app/scenario/scenario-settings/scenario-settings.component.ts index 09558706..16353196 100644 --- a/src/app/scenario/scenario-settings/scenario-settings.component.ts +++ b/src/app/scenario/scenario-settings/scenario-settings.component.ts @@ -33,6 +33,7 @@ export class SettingsScenarioComponent implements OnInit { throughput: null, enabled: null, zeroErrorToleranceEnabled: null, + minTestDuration: null, deleteSamples: null, keepTestRunsPeriod: null, generateShareToken: null, @@ -52,7 +53,6 @@ export class SettingsScenarioComponent implements OnInit { errorRate: null, failures: null, }; - labelFilterControls = {}; requestStatsCormControls = { samples: null, avg: null, @@ -74,10 +74,7 @@ export class SettingsScenarioComponent implements OnInit { satisfyingThreshold: null, toleratingThreshold: null }; - - params; - keepTestRunPeriods = [ { period: 0, @@ -110,7 +107,7 @@ export class SettingsScenarioComponent implements OnInit { labelFilterOperators = ["includes", "match"]; labelFilters: FormArray; - hasBaselineReport = false + hasBaselineReport = false; constructor( @@ -124,7 +121,7 @@ export class SettingsScenarioComponent implements OnInit { ngOnInit(): void { this.route.params.subscribe(_ => this.params = _); this.scenarioApiService.getScenario(this.params.projectName, this.params.scenarioName).subscribe(_ => { - this.hasBaselineReport = !!_.baselineReport + this.hasBaselineReport = !!_.baselineReport; if (_.name) { this.createFormControls(_); this.createForm(); @@ -133,8 +130,6 @@ export class SettingsScenarioComponent implements OnInit { } - - this.apdexSettingsForm.valueChanges.subscribe(value => { const { satisfyingThreshold, toleratingThreshold } = value; if (satisfyingThreshold && toleratingThreshold) { @@ -185,8 +180,16 @@ export class SettingsScenarioComponent implements OnInit { Validators.required ]); this.formControls.zeroErrorToleranceEnabled = new FormControl(settings.zeroErrorToleranceEnabled, [ + Validators.min(0), + Validators.max(1000), Validators.required ]); + this.formControls.minTestDuration = new FormControl(settings.minTestDuration, [ + Validators.required, + Validators.min(0), + Validators.max(1000), + ]); + this.formControls.deleteSamples = new FormControl(settings.deleteSamples, [ Validators.required ]); @@ -245,6 +248,7 @@ export class SettingsScenarioComponent implements OnInit { thresholdThroughput: this.formControls.throughput, thresholdErrorRate: this.formControls.errorRate, zeroErrorToleranceEnabled: this.formControls.zeroErrorToleranceEnabled, + minTestDuration: this.formControls.minTestDuration, deleteSamples: this.formControls.deleteSamples, keepTestRunsPeriod: this.formControls.keepTestRunsPeriod, generateShareToken: this.formControls.generateShareToken, @@ -305,16 +309,18 @@ export class SettingsScenarioComponent implements OnInit { thresholdThroughput, deleteSamples, zeroErrorToleranceEnabled, + minTestDuration, keepTestRunsPeriod, generateShareToken, extraAggregations } = this.scenarioSettingsForm.value; - const { apdexEnabled, satisfyingThreshold, toleratingThreshold } = this.apdexSettingsForm.value + const { apdexEnabled, satisfyingThreshold, toleratingThreshold } = this.apdexSettingsForm.value; const { projectName, scenarioName: currentScenarioName } = this.params; const body = { scenarioName, analysisEnabled, zeroErrorToleranceEnabled, + minTestDuration, keepTestRunsPeriod, deleteSamples, generateShareToken, @@ -365,5 +371,6 @@ export class SettingsScenarioComponent implements OnInit { return name === this.params.scenarioName; } + protected readonly JSON = JSON; } diff --git a/src/app/scenario/scenario.component.html b/src/app/scenario/scenario.component.html index eedc4534..5b0cddf8 100644 --- a/src/app/scenario/scenario.component.html +++ b/src/app/scenario/scenario.component.html @@ -119,13 +119,10 @@
Test Runs class="far fa-circle text-secondary status-icon"> - - - - + + { - this.zeroErrorToleranceEnabled = _.zeroErrorToleranceEnabled; + this.validationEnabled = this.isValidationEnabled(_.zeroErrorToleranceEnabled, _.minTestDuration) + this.minTestDuration = _.minTestDuration }); return new Observable().pipe(catchError(err => of([]))); }) @@ -108,18 +111,18 @@ export class ScenarioComponent implements OnInit, OnDestroy { this.router.navigate([`./project/${projectName}/scenario/${scenarioName}/item/${itemId}`]); } - navigateBack() { - this.router.navigate([`./project/${this.params.projectName}/scenarios`]); - } - updateScenarioName(scenarioName: string) { this.router.navigate( [".", "project", this.params.projectName, "scenario", scenarioName, "items"]); } - showZeroErrorToleranceWarning(errorCount, errorRate) { - return showZeroErrorWarning(errorRate, errorCount); + displayItemValidationError(zeroErrorEnabled, errorCount, errorRate, duration, minTestDuration) { + const { zeroErrorToleranceValidation, minTestDurationValidation } = getValidationResults(zeroErrorEnabled, errorRate, errorCount, duration, minTestDuration); + return zeroErrorToleranceValidation || minTestDurationValidation + } + private isValidationEnabled(zeroErrorEnabled, minTestDuration): boolean { + return zeroErrorEnabled || minTestDuration > 0 } diff --git a/src/app/utils/showZeroErrorTolerance.ts b/src/app/utils/showZeroErrorTolerance.ts index 3a8ccf08..896aae0d 100644 --- a/src/app/utils/showZeroErrorTolerance.ts +++ b/src/app/utils/showZeroErrorTolerance.ts @@ -1,11 +1,17 @@ -export const showZeroErrorWarning = (errorRate, errorCount) => { +export const getValidationResults = (zeroErrorToleranceEnabled: boolean, errorRate, errorCount, duration, minTestDuration) => { + return { + zeroErrorToleranceValidation: zeroErrorToleranceEnabled ? showZeroErrorValidation(errorRate, errorCount) : false, + minTestDurationValidation: minTestDuration > 0 ? duration <= minTestDuration : false, + } +}; + +const showZeroErrorValidation = (errorRate, errorCount) => { if (errorRate > 0) { return true; } else { if (errorCount === null || errorCount === undefined) { - return "unknown"; + return false } return errorCount && errorCount > 0; } -}; - +}