@memberjunction/ng-dashboards 2.117.0 → 2.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Testing/components/testing-analytics.component.d.ts +56 -0
- package/dist/Testing/components/testing-analytics.component.d.ts.map +1 -0
- package/dist/Testing/components/testing-analytics.component.js +746 -0
- package/dist/Testing/components/testing-analytics.component.js.map +1 -0
- package/dist/Testing/components/testing-execution.component.d.ts +59 -0
- package/dist/Testing/components/testing-execution.component.d.ts.map +1 -0
- package/dist/Testing/components/testing-execution.component.js +649 -0
- package/dist/Testing/components/testing-execution.component.js.map +1 -0
- package/dist/Testing/components/testing-feedback.component.d.ts +55 -0
- package/dist/Testing/components/testing-feedback.component.d.ts.map +1 -0
- package/dist/Testing/components/testing-feedback.component.js +789 -0
- package/dist/Testing/components/testing-feedback.component.js.map +1 -0
- package/dist/Testing/components/testing-overview.component.d.ts +30 -0
- package/dist/Testing/components/testing-overview.component.d.ts.map +1 -0
- package/dist/Testing/components/testing-overview.component.js +342 -0
- package/dist/Testing/components/testing-overview.component.js.map +1 -0
- package/dist/Testing/components/testing-version-comparison.component.d.ts +58 -0
- package/dist/Testing/components/testing-version-comparison.component.d.ts.map +1 -0
- package/dist/Testing/components/testing-version-comparison.component.js +801 -0
- package/dist/Testing/components/testing-version-comparison.component.js.map +1 -0
- package/dist/Testing/components/widgets/cost-display.component.d.ts +16 -0
- package/dist/Testing/components/widgets/cost-display.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/cost-display.component.js +88 -0
- package/dist/Testing/components/widgets/cost-display.component.js.map +1 -0
- package/dist/Testing/components/widgets/oracle-breakdown-table.component.d.ts +20 -0
- package/dist/Testing/components/widgets/oracle-breakdown-table.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/oracle-breakdown-table.component.js +280 -0
- package/dist/Testing/components/widgets/oracle-breakdown-table.component.js.map +1 -0
- package/dist/Testing/components/widgets/score-indicator.component.d.ts +13 -0
- package/dist/Testing/components/widgets/score-indicator.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/score-indicator.component.js +93 -0
- package/dist/Testing/components/widgets/score-indicator.component.js.map +1 -0
- package/dist/Testing/components/widgets/suite-tree.component.d.ts +28 -0
- package/dist/Testing/components/widgets/suite-tree.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/suite-tree.component.js +275 -0
- package/dist/Testing/components/widgets/suite-tree.component.js.map +1 -0
- package/dist/Testing/components/widgets/test-run-detail-panel.component.d.ts +34 -0
- package/dist/Testing/components/widgets/test-run-detail-panel.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/test-run-detail-panel.component.js +373 -0
- package/dist/Testing/components/widgets/test-run-detail-panel.component.js.map +1 -0
- package/dist/Testing/components/widgets/test-run-dialog.component.d.ts +63 -0
- package/dist/Testing/components/widgets/test-run-dialog.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/test-run-dialog.component.js +989 -0
- package/dist/Testing/components/widgets/test-run-dialog.component.js.map +1 -0
- package/dist/Testing/components/widgets/test-status-badge.component.d.ts +10 -0
- package/dist/Testing/components/widgets/test-status-badge.component.d.ts.map +1 -0
- package/dist/Testing/components/widgets/test-status-badge.component.js +68 -0
- package/dist/Testing/components/widgets/test-status-badge.component.js.map +1 -0
- package/dist/Testing/services/testing-instrumentation.service.d.ts +152 -0
- package/dist/Testing/services/testing-instrumentation.service.d.ts.map +1 -0
- package/dist/Testing/services/testing-instrumentation.service.js +579 -0
- package/dist/Testing/services/testing-instrumentation.service.js.map +1 -0
- package/dist/Testing/testing-dashboard.component.d.ts +52 -0
- package/dist/Testing/testing-dashboard.component.d.ts.map +1 -0
- package/dist/Testing/testing-dashboard.component.js +273 -0
- package/dist/Testing/testing-dashboard.component.js.map +1 -0
- package/dist/module.d.ts +33 -20
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +55 -6
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +12 -10
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { takeUntil, map } from 'rxjs/operators';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
import * as i1 from "../services/testing-instrumentation.service";
|
|
6
|
+
import * as i2 from "@angular/forms";
|
|
7
|
+
import * as i3 from "@angular/common";
|
|
8
|
+
const _forTrack0 = ($index, $item) => $item.days;
|
|
9
|
+
const _forTrack1 = ($index, $item) => $item.testName;
|
|
10
|
+
const _forTrack2 = ($index, $item) => $item.label;
|
|
11
|
+
const _c0 = () => [];
|
|
12
|
+
function TestingAnalyticsComponent_For_9_Template(rf, ctx) { if (rf & 1) {
|
|
13
|
+
i0.ɵɵelementStart(0, "option", 6);
|
|
14
|
+
i0.ɵɵtext(1);
|
|
15
|
+
i0.ɵɵelementEnd();
|
|
16
|
+
} if (rf & 2) {
|
|
17
|
+
const range_r1 = ctx.$implicit;
|
|
18
|
+
i0.ɵɵproperty("value", range_r1.days);
|
|
19
|
+
i0.ɵɵadvance();
|
|
20
|
+
i0.ɵɵtextInterpolate(range_r1.label);
|
|
21
|
+
} }
|
|
22
|
+
function TestingAnalyticsComponent_For_20_Template(rf, ctx) { if (rf & 1) {
|
|
23
|
+
i0.ɵɵelementStart(0, "div", 13)(1, "div", 41)(2, "span", 42);
|
|
24
|
+
i0.ɵɵtext(3);
|
|
25
|
+
i0.ɵɵpipe(4, "date");
|
|
26
|
+
i0.ɵɵelementEnd();
|
|
27
|
+
i0.ɵɵelementStart(5, "span", 43);
|
|
28
|
+
i0.ɵɵtext(6);
|
|
29
|
+
i0.ɵɵelementEnd()();
|
|
30
|
+
i0.ɵɵelementStart(7, "div", 44)(8, "div", 45);
|
|
31
|
+
i0.ɵɵelement(9, "i", 46);
|
|
32
|
+
i0.ɵɵelementStart(10, "span");
|
|
33
|
+
i0.ɵɵtext(11);
|
|
34
|
+
i0.ɵɵelementEnd()();
|
|
35
|
+
i0.ɵɵelementStart(12, "div", 47);
|
|
36
|
+
i0.ɵɵelement(13, "i", 48);
|
|
37
|
+
i0.ɵɵelementStart(14, "span");
|
|
38
|
+
i0.ɵɵtext(15);
|
|
39
|
+
i0.ɵɵelementEnd()();
|
|
40
|
+
i0.ɵɵelementStart(16, "div", 49);
|
|
41
|
+
i0.ɵɵelement(17, "i", 50);
|
|
42
|
+
i0.ɵɵelementStart(18, "span");
|
|
43
|
+
i0.ɵɵtext(19);
|
|
44
|
+
i0.ɵɵelementEnd()()();
|
|
45
|
+
i0.ɵɵelementStart(20, "div", 51);
|
|
46
|
+
i0.ɵɵelement(21, "div", 52)(22, "div", 53)(23, "div", 54);
|
|
47
|
+
i0.ɵɵelementEnd();
|
|
48
|
+
i0.ɵɵelementStart(24, "div", 55)(25, "span", 56);
|
|
49
|
+
i0.ɵɵtext(26);
|
|
50
|
+
i0.ɵɵelementEnd();
|
|
51
|
+
i0.ɵɵelementStart(27, "span", 57);
|
|
52
|
+
i0.ɵɵtext(28);
|
|
53
|
+
i0.ɵɵelementEnd()()();
|
|
54
|
+
} if (rf & 2) {
|
|
55
|
+
const trend_r2 = ctx.$implicit;
|
|
56
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
57
|
+
i0.ɵɵadvance(3);
|
|
58
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(4, 19, trend_r2.timestamp, "short"));
|
|
59
|
+
i0.ɵɵadvance(3);
|
|
60
|
+
i0.ɵɵtextInterpolate1("", trend_r2.totalRuns, " runs");
|
|
61
|
+
i0.ɵɵadvance(5);
|
|
62
|
+
i0.ɵɵtextInterpolate(trend_r2.passed);
|
|
63
|
+
i0.ɵɵadvance(4);
|
|
64
|
+
i0.ɵɵtextInterpolate(trend_r2.failed);
|
|
65
|
+
i0.ɵɵadvance(4);
|
|
66
|
+
i0.ɵɵtextInterpolate(trend_r2.skipped);
|
|
67
|
+
i0.ɵɵadvance(2);
|
|
68
|
+
i0.ɵɵstyleProp("width", ctx_r2.calculatePassRate(trend_r2), "%");
|
|
69
|
+
i0.ɵɵadvance();
|
|
70
|
+
i0.ɵɵstyleProp("width", ctx_r2.calculateFailRate(trend_r2), "%");
|
|
71
|
+
i0.ɵɵadvance();
|
|
72
|
+
i0.ɵɵstyleProp("width", ctx_r2.calculateSkipRate(trend_r2), "%");
|
|
73
|
+
i0.ɵɵadvance(2);
|
|
74
|
+
i0.ɵɵclassProp("good", ctx_r2.calculatePassRate(trend_r2) >= 80)("warning", ctx_r2.calculatePassRate(trend_r2) >= 60 && ctx_r2.calculatePassRate(trend_r2) < 80)("poor", ctx_r2.calculatePassRate(trend_r2) < 60);
|
|
75
|
+
i0.ɵɵadvance();
|
|
76
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r2.calculatePassRate(trend_r2).toFixed(1), "% pass rate ");
|
|
77
|
+
i0.ɵɵadvance(2);
|
|
78
|
+
i0.ɵɵtextInterpolate1("$", trend_r2.totalCost.toFixed(2), "");
|
|
79
|
+
} }
|
|
80
|
+
function TestingAnalyticsComponent_For_30_Template(rf, ctx) { if (rf & 1) {
|
|
81
|
+
i0.ɵɵelementStart(0, "div", 19)(1, "div", 58)(2, "div", 59);
|
|
82
|
+
i0.ɵɵtext(3);
|
|
83
|
+
i0.ɵɵelementEnd();
|
|
84
|
+
i0.ɵɵelementStart(4, "div", 60);
|
|
85
|
+
i0.ɵɵtext(5);
|
|
86
|
+
i0.ɵɵelementEnd()();
|
|
87
|
+
i0.ɵɵelementStart(6, "div", 61)(7, "div", 62);
|
|
88
|
+
i0.ɵɵelement(8, "div", 63);
|
|
89
|
+
i0.ɵɵelementEnd()()();
|
|
90
|
+
} if (rf & 2) {
|
|
91
|
+
const test_r4 = ctx.$implicit;
|
|
92
|
+
i0.ɵɵadvance(3);
|
|
93
|
+
i0.ɵɵtextInterpolate(test_r4.testName);
|
|
94
|
+
i0.ɵɵadvance(2);
|
|
95
|
+
i0.ɵɵtextInterpolate2("", test_r4.failureCount, " failures \u2022 ", test_r4.failureRate.toFixed(1), "% fail rate");
|
|
96
|
+
i0.ɵɵadvance(3);
|
|
97
|
+
i0.ɵɵstyleProp("width", test_r4.failureRate, "%");
|
|
98
|
+
} }
|
|
99
|
+
function TestingAnalyticsComponent_ForEmpty_31_Template(rf, ctx) { if (rf & 1) {
|
|
100
|
+
i0.ɵɵelementStart(0, "div", 20);
|
|
101
|
+
i0.ɵɵelement(1, "i", 46);
|
|
102
|
+
i0.ɵɵelementStart(2, "p");
|
|
103
|
+
i0.ɵɵtext(3, "No failing tests!");
|
|
104
|
+
i0.ɵɵelementEnd()();
|
|
105
|
+
} }
|
|
106
|
+
function TestingAnalyticsComponent_For_40_Template(rf, ctx) { if (rf & 1) {
|
|
107
|
+
i0.ɵɵelementStart(0, "div", 19)(1, "div", 58)(2, "div", 59);
|
|
108
|
+
i0.ɵɵtext(3);
|
|
109
|
+
i0.ɵɵelementEnd();
|
|
110
|
+
i0.ɵɵelementStart(4, "div", 60);
|
|
111
|
+
i0.ɵɵtext(5);
|
|
112
|
+
i0.ɵɵelementEnd()();
|
|
113
|
+
i0.ɵɵelementStart(6, "div", 61)(7, "div", 64);
|
|
114
|
+
i0.ɵɵtext(8);
|
|
115
|
+
i0.ɵɵelementEnd()()();
|
|
116
|
+
} if (rf & 2) {
|
|
117
|
+
const test_r5 = ctx.$implicit;
|
|
118
|
+
i0.ɵɵadvance(3);
|
|
119
|
+
i0.ɵɵtextInterpolate(test_r5.testName);
|
|
120
|
+
i0.ɵɵadvance(2);
|
|
121
|
+
i0.ɵɵtextInterpolate1("avg $", test_r5.avgCost.toFixed(4), "");
|
|
122
|
+
i0.ɵɵadvance(3);
|
|
123
|
+
i0.ɵɵtextInterpolate1("$", test_r5.totalCost.toFixed(2), "");
|
|
124
|
+
} }
|
|
125
|
+
function TestingAnalyticsComponent_ForEmpty_41_Template(rf, ctx) { if (rf & 1) {
|
|
126
|
+
i0.ɵɵelementStart(0, "div", 20);
|
|
127
|
+
i0.ɵɵelement(1, "i", 21);
|
|
128
|
+
i0.ɵɵelementStart(2, "p");
|
|
129
|
+
i0.ɵɵtext(3, "No test data");
|
|
130
|
+
i0.ɵɵelementEnd()();
|
|
131
|
+
} }
|
|
132
|
+
function TestingAnalyticsComponent_For_50_Template(rf, ctx) { if (rf & 1) {
|
|
133
|
+
i0.ɵɵelementStart(0, "div", 19)(1, "div", 58)(2, "div", 59);
|
|
134
|
+
i0.ɵɵtext(3);
|
|
135
|
+
i0.ɵɵelementEnd();
|
|
136
|
+
i0.ɵɵelementStart(4, "div", 60);
|
|
137
|
+
i0.ɵɵtext(5);
|
|
138
|
+
i0.ɵɵelementEnd()();
|
|
139
|
+
i0.ɵɵelementStart(6, "div", 61)(7, "div", 65);
|
|
140
|
+
i0.ɵɵtext(8);
|
|
141
|
+
i0.ɵɵelementEnd()()();
|
|
142
|
+
} if (rf & 2) {
|
|
143
|
+
const test_r6 = ctx.$implicit;
|
|
144
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
145
|
+
i0.ɵɵadvance(3);
|
|
146
|
+
i0.ɵɵtextInterpolate(test_r6.testName);
|
|
147
|
+
i0.ɵɵadvance(2);
|
|
148
|
+
i0.ɵɵtextInterpolate1("avg ", ctx_r2.formatDuration(test_r6.avgDuration), "");
|
|
149
|
+
i0.ɵɵadvance(3);
|
|
150
|
+
i0.ɵɵtextInterpolate(ctx_r2.formatDuration(test_r6.maxDuration));
|
|
151
|
+
} }
|
|
152
|
+
function TestingAnalyticsComponent_ForEmpty_51_Template(rf, ctx) { if (rf & 1) {
|
|
153
|
+
i0.ɵɵelementStart(0, "div", 20);
|
|
154
|
+
i0.ɵɵelement(1, "i", 22);
|
|
155
|
+
i0.ɵɵelementStart(2, "p");
|
|
156
|
+
i0.ɵɵtext(3, "No test data");
|
|
157
|
+
i0.ɵɵelementEnd()();
|
|
158
|
+
} }
|
|
159
|
+
function TestingAnalyticsComponent_For_61_Template(rf, ctx) { if (rf & 1) {
|
|
160
|
+
i0.ɵɵelementStart(0, "div", 25)(1, "div", 66);
|
|
161
|
+
i0.ɵɵtext(2);
|
|
162
|
+
i0.ɵɵelementEnd();
|
|
163
|
+
i0.ɵɵelementStart(3, "div", 67);
|
|
164
|
+
i0.ɵɵelement(4, "div", 68);
|
|
165
|
+
i0.ɵɵelementStart(5, "span", 69);
|
|
166
|
+
i0.ɵɵtext(6);
|
|
167
|
+
i0.ɵɵelementEnd()();
|
|
168
|
+
i0.ɵɵelementStart(7, "div", 70);
|
|
169
|
+
i0.ɵɵtext(8);
|
|
170
|
+
i0.ɵɵelementEnd()();
|
|
171
|
+
} if (rf & 2) {
|
|
172
|
+
const bucket_r7 = ctx.$implicit;
|
|
173
|
+
i0.ɵɵadvance(2);
|
|
174
|
+
i0.ɵɵtextInterpolate(bucket_r7.label);
|
|
175
|
+
i0.ɵɵadvance(2);
|
|
176
|
+
i0.ɵɵclassMap(bucket_r7.class);
|
|
177
|
+
i0.ɵɵstyleProp("width", bucket_r7.percentage, "%");
|
|
178
|
+
i0.ɵɵadvance(2);
|
|
179
|
+
i0.ɵɵtextInterpolate(bucket_r7.count);
|
|
180
|
+
i0.ɵɵadvance(2);
|
|
181
|
+
i0.ɵɵtextInterpolate1("", bucket_r7.percentage.toFixed(1), "%");
|
|
182
|
+
} }
|
|
183
|
+
export class TestingAnalyticsComponent {
|
|
184
|
+
instrumentationService;
|
|
185
|
+
cdr;
|
|
186
|
+
initialState;
|
|
187
|
+
stateChange = new EventEmitter();
|
|
188
|
+
destroy$ = new Subject();
|
|
189
|
+
selectedTimeRange = 30;
|
|
190
|
+
timeRanges = [
|
|
191
|
+
{ label: '7 Days', days: 7 },
|
|
192
|
+
{ label: '30 Days', days: 30 },
|
|
193
|
+
{ label: '90 Days', days: 90 }
|
|
194
|
+
];
|
|
195
|
+
trends$;
|
|
196
|
+
topFailingTests$;
|
|
197
|
+
expensiveTests$;
|
|
198
|
+
slowestTests$;
|
|
199
|
+
scoreDistribution$;
|
|
200
|
+
totalCost$;
|
|
201
|
+
avgCostPerTest$;
|
|
202
|
+
avgCostPerRun$;
|
|
203
|
+
projectedMonthlyCost$;
|
|
204
|
+
avgExecutionTime$;
|
|
205
|
+
throughput$;
|
|
206
|
+
reliabilityScore$;
|
|
207
|
+
constructor(instrumentationService, cdr) {
|
|
208
|
+
this.instrumentationService = instrumentationService;
|
|
209
|
+
this.cdr = cdr;
|
|
210
|
+
}
|
|
211
|
+
ngOnInit() {
|
|
212
|
+
this.setupObservables();
|
|
213
|
+
if (this.initialState?.selectedTimeRange) {
|
|
214
|
+
this.selectedTimeRange = this.initialState.selectedTimeRange;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
ngOnDestroy() {
|
|
218
|
+
this.destroy$.next();
|
|
219
|
+
this.destroy$.complete();
|
|
220
|
+
}
|
|
221
|
+
setupObservables() {
|
|
222
|
+
this.trends$ = this.instrumentationService.trends$;
|
|
223
|
+
const analytics$ = this.instrumentationService.analytics$;
|
|
224
|
+
this.topFailingTests$ = analytics$.pipe(map(analytics => analytics.topFailingTests), takeUntil(this.destroy$));
|
|
225
|
+
this.expensiveTests$ = analytics$.pipe(map(analytics => analytics.mostExpensiveTests), takeUntil(this.destroy$));
|
|
226
|
+
this.slowestTests$ = analytics$.pipe(map(analytics => analytics.slowestTests), takeUntil(this.destroy$));
|
|
227
|
+
this.scoreDistribution$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
228
|
+
const buckets = [
|
|
229
|
+
{ label: 'Excellent (≥0.9)', min: 0.9, max: 1.0, class: 'excellent', count: 0 },
|
|
230
|
+
{ label: 'Good (0.8-0.9)', min: 0.8, max: 0.9, class: 'good', count: 0 },
|
|
231
|
+
{ label: 'Fair (0.6-0.8)', min: 0.6, max: 0.8, class: 'fair', count: 0 },
|
|
232
|
+
{ label: 'Poor (0.4-0.6)', min: 0.4, max: 0.6, class: 'poor', count: 0 },
|
|
233
|
+
{ label: 'Fail (<0.4)', min: 0, max: 0.4, class: 'fail', count: 0 }
|
|
234
|
+
];
|
|
235
|
+
runs.forEach(run => {
|
|
236
|
+
const bucket = buckets.find(b => run.score >= b.min && run.score < b.max);
|
|
237
|
+
if (bucket)
|
|
238
|
+
bucket.count++;
|
|
239
|
+
});
|
|
240
|
+
const total = runs.length || 1;
|
|
241
|
+
return buckets.map(b => ({
|
|
242
|
+
...b,
|
|
243
|
+
percentage: (b.count / total) * 100
|
|
244
|
+
}));
|
|
245
|
+
}), takeUntil(this.destroy$));
|
|
246
|
+
this.totalCost$ = this.instrumentationService.testRuns$.pipe(map(runs => runs.reduce((sum, r) => sum + r.cost, 0)));
|
|
247
|
+
this.avgCostPerTest$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
248
|
+
if (runs.length === 0)
|
|
249
|
+
return 0;
|
|
250
|
+
const totalCost = runs.reduce((sum, r) => sum + r.cost, 0);
|
|
251
|
+
const uniqueTests = new Set(runs.map(r => r.testName)).size;
|
|
252
|
+
return uniqueTests > 0 ? totalCost / uniqueTests : 0;
|
|
253
|
+
}));
|
|
254
|
+
this.avgCostPerRun$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
255
|
+
if (runs.length === 0)
|
|
256
|
+
return 0;
|
|
257
|
+
const totalCost = runs.reduce((sum, r) => sum + r.cost, 0);
|
|
258
|
+
return totalCost / runs.length;
|
|
259
|
+
}));
|
|
260
|
+
this.projectedMonthlyCost$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
261
|
+
if (runs.length === 0)
|
|
262
|
+
return 0;
|
|
263
|
+
const totalCost = runs.reduce((sum, r) => sum + r.cost, 0);
|
|
264
|
+
const daysInPeriod = this.selectedTimeRange;
|
|
265
|
+
return (totalCost / daysInPeriod) * 30;
|
|
266
|
+
}));
|
|
267
|
+
this.avgExecutionTime$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
268
|
+
if (runs.length === 0)
|
|
269
|
+
return 0;
|
|
270
|
+
const totalDuration = runs.reduce((sum, r) => sum + r.duration, 0);
|
|
271
|
+
return totalDuration / runs.length;
|
|
272
|
+
}));
|
|
273
|
+
this.throughput$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
274
|
+
const daysInPeriod = this.selectedTimeRange;
|
|
275
|
+
return runs.length / daysInPeriod;
|
|
276
|
+
}));
|
|
277
|
+
this.reliabilityScore$ = this.instrumentationService.testRuns$.pipe(map(runs => {
|
|
278
|
+
if (runs.length === 0)
|
|
279
|
+
return 0;
|
|
280
|
+
const passed = runs.filter(r => r.status === 'Passed').length;
|
|
281
|
+
return (passed / runs.length) * 100;
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
onTimeRangeChange() {
|
|
285
|
+
const days = this.selectedTimeRange;
|
|
286
|
+
const end = new Date();
|
|
287
|
+
const start = new Date(end.getTime() - days * 24 * 60 * 60 * 1000);
|
|
288
|
+
this.instrumentationService.setDateRange(start, end);
|
|
289
|
+
this.emitStateChange();
|
|
290
|
+
}
|
|
291
|
+
refresh() {
|
|
292
|
+
this.instrumentationService.refresh();
|
|
293
|
+
}
|
|
294
|
+
formatDuration(milliseconds) {
|
|
295
|
+
if (milliseconds < 1000)
|
|
296
|
+
return `${milliseconds}ms`;
|
|
297
|
+
const seconds = Math.floor(milliseconds / 1000);
|
|
298
|
+
const minutes = Math.floor(seconds / 60);
|
|
299
|
+
if (minutes > 0)
|
|
300
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
301
|
+
return `${seconds}s`;
|
|
302
|
+
}
|
|
303
|
+
calculatePassRate(trend) {
|
|
304
|
+
if (trend.totalRuns === 0)
|
|
305
|
+
return 0;
|
|
306
|
+
return (trend.passed / trend.totalRuns) * 100;
|
|
307
|
+
}
|
|
308
|
+
calculateFailRate(trend) {
|
|
309
|
+
if (trend.totalRuns === 0)
|
|
310
|
+
return 0;
|
|
311
|
+
return (trend.failed / trend.totalRuns) * 100;
|
|
312
|
+
}
|
|
313
|
+
calculateSkipRate(trend) {
|
|
314
|
+
if (trend.totalRuns === 0)
|
|
315
|
+
return 0;
|
|
316
|
+
return (trend.skipped / trend.totalRuns) * 100;
|
|
317
|
+
}
|
|
318
|
+
emitStateChange() {
|
|
319
|
+
this.stateChange.emit({
|
|
320
|
+
selectedTimeRange: this.selectedTimeRange
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
static ɵfac = function TestingAnalyticsComponent_Factory(t) { return new (t || TestingAnalyticsComponent)(i0.ɵɵdirectiveInject(i1.TestingInstrumentationService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
324
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestingAnalyticsComponent, selectors: [["app-testing-analytics"]], inputs: { initialState: "initialState" }, outputs: { stateChange: "stateChange" }, decls: 134, vars: 57, consts: [[1, "testing-analytics"], [1, "analytics-header"], [1, "header-left"], [1, "fa-solid", "fa-chart-bar"], [1, "header-actions"], [1, "time-range-select", 3, "ngModelChange", "change", "ngModel"], [3, "value"], [1, "action-btn", 3, "click"], [1, "fa-solid", "fa-refresh"], [1, "analytics-section"], [1, "section-header"], [1, "fa-solid", "fa-chart-line"], [1, "trends-grid"], [1, "trend-card"], [1, "analytics-grid"], [1, "analytics-card"], [1, "card-header"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "card-content"], [1, "analytics-row"], [1, "empty-state"], [1, "fa-solid", "fa-dollar-sign"], [1, "fa-solid", "fa-clock"], [1, "fa-solid", "fa-chart-pie"], [1, "score-distribution"], [1, "score-bucket"], [1, "fa-solid", "fa-wallet"], [1, "cost-summary"], [1, "cost-item", "total"], [1, "cost-label"], [1, "cost-amount"], [1, "cost-item"], [1, "fa-solid", "fa-gauge"], [1, "performance-metrics"], [1, "perf-metric"], [1, "perf-icon"], [1, "fa-solid", "fa-bolt"], [1, "perf-content"], [1, "perf-value"], [1, "perf-label"], [1, "fa-solid", "fa-check-double"], [1, "trend-header"], [1, "trend-date"], [1, "trend-total"], [1, "trend-metrics"], [1, "metric", "passed"], [1, "fa-solid", "fa-check-circle"], [1, "metric", "failed"], [1, "fa-solid", "fa-times-circle"], [1, "metric", "skipped"], [1, "fa-solid", "fa-minus-circle"], [1, "trend-chart"], [1, "chart-bar", "passed"], [1, "chart-bar", "failed"], [1, "chart-bar", "skipped"], [1, "trend-footer"], [1, "pass-rate"], [1, "cost"], [1, "row-info"], [1, "row-name"], [1, "row-meta"], [1, "row-metrics"], [1, "fail-bar-container"], [1, "fail-bar"], [1, "cost-value"], [1, "duration-value"], [1, "bucket-label"], [1, "bucket-bar-container"], [1, "bucket-bar"], [1, "bucket-count"], [1, "bucket-percentage"]], template: function TestingAnalyticsComponent_Template(rf, ctx) { if (rf & 1) {
|
|
325
|
+
i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "h2");
|
|
326
|
+
i0.ɵɵelement(4, "i", 3);
|
|
327
|
+
i0.ɵɵtext(5, " Testing Analytics ");
|
|
328
|
+
i0.ɵɵelementEnd()();
|
|
329
|
+
i0.ɵɵelementStart(6, "div", 4)(7, "select", 5);
|
|
330
|
+
i0.ɵɵtwoWayListener("ngModelChange", function TestingAnalyticsComponent_Template_select_ngModelChange_7_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.selectedTimeRange, $event) || (ctx.selectedTimeRange = $event); return $event; });
|
|
331
|
+
i0.ɵɵlistener("change", function TestingAnalyticsComponent_Template_select_change_7_listener() { return ctx.onTimeRangeChange(); });
|
|
332
|
+
i0.ɵɵrepeaterCreate(8, TestingAnalyticsComponent_For_9_Template, 2, 2, "option", 6, _forTrack0);
|
|
333
|
+
i0.ɵɵelementEnd();
|
|
334
|
+
i0.ɵɵelementStart(10, "button", 7);
|
|
335
|
+
i0.ɵɵlistener("click", function TestingAnalyticsComponent_Template_button_click_10_listener() { return ctx.refresh(); });
|
|
336
|
+
i0.ɵɵelement(11, "i", 8);
|
|
337
|
+
i0.ɵɵtext(12, " Refresh ");
|
|
338
|
+
i0.ɵɵelementEnd()()();
|
|
339
|
+
i0.ɵɵelementStart(13, "div", 9)(14, "div", 10)(15, "h3");
|
|
340
|
+
i0.ɵɵelement(16, "i", 11);
|
|
341
|
+
i0.ɵɵtext(17, " Test Execution Trends ");
|
|
342
|
+
i0.ɵɵelementEnd()();
|
|
343
|
+
i0.ɵɵelementStart(18, "div", 12);
|
|
344
|
+
i0.ɵɵrepeaterCreate(19, TestingAnalyticsComponent_For_20_Template, 29, 22, "div", 13, i0.ɵɵrepeaterTrackByIndex);
|
|
345
|
+
i0.ɵɵpipe(21, "async");
|
|
346
|
+
i0.ɵɵelementEnd()();
|
|
347
|
+
i0.ɵɵelementStart(22, "div", 14)(23, "div", 15)(24, "div", 16)(25, "h4");
|
|
348
|
+
i0.ɵɵelement(26, "i", 17);
|
|
349
|
+
i0.ɵɵtext(27, " Top Failing Tests ");
|
|
350
|
+
i0.ɵɵelementEnd()();
|
|
351
|
+
i0.ɵɵelementStart(28, "div", 18);
|
|
352
|
+
i0.ɵɵrepeaterCreate(29, TestingAnalyticsComponent_For_30_Template, 9, 5, "div", 19, _forTrack1, false, TestingAnalyticsComponent_ForEmpty_31_Template, 4, 0, "div", 20);
|
|
353
|
+
i0.ɵɵpipe(32, "async");
|
|
354
|
+
i0.ɵɵelementEnd()();
|
|
355
|
+
i0.ɵɵelementStart(33, "div", 15)(34, "div", 16)(35, "h4");
|
|
356
|
+
i0.ɵɵelement(36, "i", 21);
|
|
357
|
+
i0.ɵɵtext(37, " Most Expensive Tests ");
|
|
358
|
+
i0.ɵɵelementEnd()();
|
|
359
|
+
i0.ɵɵelementStart(38, "div", 18);
|
|
360
|
+
i0.ɵɵrepeaterCreate(39, TestingAnalyticsComponent_For_40_Template, 9, 3, "div", 19, _forTrack1, false, TestingAnalyticsComponent_ForEmpty_41_Template, 4, 0, "div", 20);
|
|
361
|
+
i0.ɵɵpipe(42, "async");
|
|
362
|
+
i0.ɵɵelementEnd()();
|
|
363
|
+
i0.ɵɵelementStart(43, "div", 15)(44, "div", 16)(45, "h4");
|
|
364
|
+
i0.ɵɵelement(46, "i", 22);
|
|
365
|
+
i0.ɵɵtext(47, " Slowest Tests ");
|
|
366
|
+
i0.ɵɵelementEnd()();
|
|
367
|
+
i0.ɵɵelementStart(48, "div", 18);
|
|
368
|
+
i0.ɵɵrepeaterCreate(49, TestingAnalyticsComponent_For_50_Template, 9, 3, "div", 19, _forTrack1, false, TestingAnalyticsComponent_ForEmpty_51_Template, 4, 0, "div", 20);
|
|
369
|
+
i0.ɵɵpipe(52, "async");
|
|
370
|
+
i0.ɵɵelementEnd()();
|
|
371
|
+
i0.ɵɵelementStart(53, "div", 15)(54, "div", 16)(55, "h4");
|
|
372
|
+
i0.ɵɵelement(56, "i", 23);
|
|
373
|
+
i0.ɵɵtext(57, " Score Distribution ");
|
|
374
|
+
i0.ɵɵelementEnd()();
|
|
375
|
+
i0.ɵɵelementStart(58, "div", 18)(59, "div", 24);
|
|
376
|
+
i0.ɵɵrepeaterCreate(60, TestingAnalyticsComponent_For_61_Template, 9, 7, "div", 25, _forTrack2);
|
|
377
|
+
i0.ɵɵpipe(62, "async");
|
|
378
|
+
i0.ɵɵelementEnd()()();
|
|
379
|
+
i0.ɵɵelementStart(63, "div", 15)(64, "div", 16)(65, "h4");
|
|
380
|
+
i0.ɵɵelement(66, "i", 26);
|
|
381
|
+
i0.ɵɵtext(67, " Cost Breakdown ");
|
|
382
|
+
i0.ɵɵelementEnd()();
|
|
383
|
+
i0.ɵɵelementStart(68, "div", 18)(69, "div", 27)(70, "div", 28)(71, "span", 29);
|
|
384
|
+
i0.ɵɵtext(72, "Total Cost");
|
|
385
|
+
i0.ɵɵelementEnd();
|
|
386
|
+
i0.ɵɵelementStart(73, "span", 30);
|
|
387
|
+
i0.ɵɵtext(74);
|
|
388
|
+
i0.ɵɵpipe(75, "async");
|
|
389
|
+
i0.ɵɵpipe(76, "number");
|
|
390
|
+
i0.ɵɵelementEnd()();
|
|
391
|
+
i0.ɵɵelementStart(77, "div", 31)(78, "span", 29);
|
|
392
|
+
i0.ɵɵtext(79, "Avg per Test");
|
|
393
|
+
i0.ɵɵelementEnd();
|
|
394
|
+
i0.ɵɵelementStart(80, "span", 30);
|
|
395
|
+
i0.ɵɵtext(81);
|
|
396
|
+
i0.ɵɵpipe(82, "async");
|
|
397
|
+
i0.ɵɵpipe(83, "number");
|
|
398
|
+
i0.ɵɵelementEnd()();
|
|
399
|
+
i0.ɵɵelementStart(84, "div", 31)(85, "span", 29);
|
|
400
|
+
i0.ɵɵtext(86, "Avg per Run");
|
|
401
|
+
i0.ɵɵelementEnd();
|
|
402
|
+
i0.ɵɵelementStart(87, "span", 30);
|
|
403
|
+
i0.ɵɵtext(88);
|
|
404
|
+
i0.ɵɵpipe(89, "async");
|
|
405
|
+
i0.ɵɵpipe(90, "number");
|
|
406
|
+
i0.ɵɵelementEnd()();
|
|
407
|
+
i0.ɵɵelementStart(91, "div", 31)(92, "span", 29);
|
|
408
|
+
i0.ɵɵtext(93, "Projected Monthly");
|
|
409
|
+
i0.ɵɵelementEnd();
|
|
410
|
+
i0.ɵɵelementStart(94, "span", 30);
|
|
411
|
+
i0.ɵɵtext(95);
|
|
412
|
+
i0.ɵɵpipe(96, "async");
|
|
413
|
+
i0.ɵɵpipe(97, "number");
|
|
414
|
+
i0.ɵɵelementEnd()()()()();
|
|
415
|
+
i0.ɵɵelementStart(98, "div", 15)(99, "div", 16)(100, "h4");
|
|
416
|
+
i0.ɵɵelement(101, "i", 32);
|
|
417
|
+
i0.ɵɵtext(102, " Performance Metrics ");
|
|
418
|
+
i0.ɵɵelementEnd()();
|
|
419
|
+
i0.ɵɵelementStart(103, "div", 18)(104, "div", 33)(105, "div", 34)(106, "div", 35);
|
|
420
|
+
i0.ɵɵelement(107, "i", 36);
|
|
421
|
+
i0.ɵɵelementEnd();
|
|
422
|
+
i0.ɵɵelementStart(108, "div", 37)(109, "div", 38);
|
|
423
|
+
i0.ɵɵtext(110);
|
|
424
|
+
i0.ɵɵpipe(111, "async");
|
|
425
|
+
i0.ɵɵelementEnd();
|
|
426
|
+
i0.ɵɵelementStart(112, "div", 39);
|
|
427
|
+
i0.ɵɵtext(113, "Avg Execution Time");
|
|
428
|
+
i0.ɵɵelementEnd()()();
|
|
429
|
+
i0.ɵɵelementStart(114, "div", 34)(115, "div", 35);
|
|
430
|
+
i0.ɵɵelement(116, "i", 11);
|
|
431
|
+
i0.ɵɵelementEnd();
|
|
432
|
+
i0.ɵɵelementStart(117, "div", 37)(118, "div", 38);
|
|
433
|
+
i0.ɵɵtext(119);
|
|
434
|
+
i0.ɵɵpipe(120, "async");
|
|
435
|
+
i0.ɵɵpipe(121, "number");
|
|
436
|
+
i0.ɵɵelementEnd();
|
|
437
|
+
i0.ɵɵelementStart(122, "div", 39);
|
|
438
|
+
i0.ɵɵtext(123, "Tests per Day");
|
|
439
|
+
i0.ɵɵelementEnd()()();
|
|
440
|
+
i0.ɵɵelementStart(124, "div", 34)(125, "div", 35);
|
|
441
|
+
i0.ɵɵelement(126, "i", 40);
|
|
442
|
+
i0.ɵɵelementEnd();
|
|
443
|
+
i0.ɵɵelementStart(127, "div", 37)(128, "div", 38);
|
|
444
|
+
i0.ɵɵtext(129);
|
|
445
|
+
i0.ɵɵpipe(130, "async");
|
|
446
|
+
i0.ɵɵpipe(131, "number");
|
|
447
|
+
i0.ɵɵelementEnd();
|
|
448
|
+
i0.ɵɵelementStart(132, "div", 39);
|
|
449
|
+
i0.ɵɵtext(133, "Reliability Score");
|
|
450
|
+
i0.ɵɵelementEnd()()()()()()()();
|
|
451
|
+
} if (rf & 2) {
|
|
452
|
+
let tmp_2_0;
|
|
453
|
+
let tmp_3_0;
|
|
454
|
+
let tmp_4_0;
|
|
455
|
+
let tmp_5_0;
|
|
456
|
+
i0.ɵɵadvance(7);
|
|
457
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.selectedTimeRange);
|
|
458
|
+
i0.ɵɵadvance();
|
|
459
|
+
i0.ɵɵrepeater(ctx.timeRanges);
|
|
460
|
+
i0.ɵɵadvance(11);
|
|
461
|
+
i0.ɵɵrepeater((tmp_2_0 = i0.ɵɵpipeBind1(21, 11, ctx.trends$)) !== null && tmp_2_0 !== undefined ? tmp_2_0 : i0.ɵɵpureFunction0(53, _c0));
|
|
462
|
+
i0.ɵɵadvance(10);
|
|
463
|
+
i0.ɵɵrepeater((tmp_3_0 = i0.ɵɵpipeBind1(32, 13, ctx.topFailingTests$)) !== null && tmp_3_0 !== undefined ? tmp_3_0 : i0.ɵɵpureFunction0(54, _c0));
|
|
464
|
+
i0.ɵɵadvance(10);
|
|
465
|
+
i0.ɵɵrepeater((tmp_4_0 = i0.ɵɵpipeBind1(42, 15, ctx.expensiveTests$)) !== null && tmp_4_0 !== undefined ? tmp_4_0 : i0.ɵɵpureFunction0(55, _c0));
|
|
466
|
+
i0.ɵɵadvance(10);
|
|
467
|
+
i0.ɵɵrepeater((tmp_5_0 = i0.ɵɵpipeBind1(52, 17, ctx.slowestTests$)) !== null && tmp_5_0 !== undefined ? tmp_5_0 : i0.ɵɵpureFunction0(56, _c0));
|
|
468
|
+
i0.ɵɵadvance(11);
|
|
469
|
+
i0.ɵɵrepeater(i0.ɵɵpipeBind1(62, 19, ctx.scoreDistribution$));
|
|
470
|
+
i0.ɵɵadvance(14);
|
|
471
|
+
i0.ɵɵtextInterpolate1("$", i0.ɵɵpipeBind2(76, 23, i0.ɵɵpipeBind1(75, 21, ctx.totalCost$) || 0, "1.2-2"), "");
|
|
472
|
+
i0.ɵɵadvance(7);
|
|
473
|
+
i0.ɵɵtextInterpolate1("$", i0.ɵɵpipeBind2(83, 28, i0.ɵɵpipeBind1(82, 26, ctx.avgCostPerTest$) || 0, "1.4-4"), "");
|
|
474
|
+
i0.ɵɵadvance(7);
|
|
475
|
+
i0.ɵɵtextInterpolate1("$", i0.ɵɵpipeBind2(90, 33, i0.ɵɵpipeBind1(89, 31, ctx.avgCostPerRun$) || 0, "1.4-4"), "");
|
|
476
|
+
i0.ɵɵadvance(7);
|
|
477
|
+
i0.ɵɵtextInterpolate1("$", i0.ɵɵpipeBind2(97, 38, i0.ɵɵpipeBind1(96, 36, ctx.projectedMonthlyCost$) || 0, "1.2-2"), "");
|
|
478
|
+
i0.ɵɵadvance(15);
|
|
479
|
+
i0.ɵɵtextInterpolate(ctx.formatDuration(i0.ɵɵpipeBind1(111, 41, ctx.avgExecutionTime$) || 0));
|
|
480
|
+
i0.ɵɵadvance(9);
|
|
481
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(121, 45, i0.ɵɵpipeBind1(120, 43, ctx.throughput$) || 0, "1.1-1"));
|
|
482
|
+
i0.ɵɵadvance(10);
|
|
483
|
+
i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind2(131, 50, i0.ɵɵpipeBind1(130, 48, ctx.reliabilityScore$) || 0, "1.1-1"), "%");
|
|
484
|
+
} }, dependencies: [i2.NgSelectOption, i2.ɵNgSelectMultipleOption, i2.SelectControlValueAccessor, i2.NgControlStatus, i2.NgModel, i3.AsyncPipe, i3.DecimalPipe, i3.DatePipe], styles: [".testing-analytics[_ngcontent-%COMP%] {\n padding: 20px;\n height: 100%;\n overflow-y: auto;\n background: #f8f9fa;\n }\n\n .analytics-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 24px;\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .header-left[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .header-left[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .time-range-select[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n cursor: pointer;\n }\n\n .action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid #ddd;\n border-radius: 4px;\n background: white;\n color: #666;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .action-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n }\n\n .analytics-section[_ngcontent-%COMP%] {\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n margin-bottom: 24px;\n }\n\n .section-header[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n padding-bottom: 12px;\n border-bottom: 2px solid #f0f0f0;\n }\n\n .section-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .section-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .trends-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 16px;\n }\n\n .trend-card[_ngcontent-%COMP%] {\n background: #f8f9fa;\n padding: 16px;\n border-radius: 6px;\n border: 1px solid #e0e0e0;\n }\n\n .trend-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n }\n\n .trend-date[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #666;\n font-weight: 600;\n }\n\n .trend-total[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #2196f3;\n font-weight: 600;\n }\n\n .trend-metrics[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n .metric[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n }\n\n .metric.passed[_ngcontent-%COMP%] {\n color: #4caf50;\n }\n\n .metric.failed[_ngcontent-%COMP%] {\n color: #f44336;\n }\n\n .metric.skipped[_ngcontent-%COMP%] {\n color: #9e9e9e;\n }\n\n .trend-chart[_ngcontent-%COMP%] {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 12px;\n background: #e0e0e0;\n }\n\n .chart-bar[_ngcontent-%COMP%] {\n transition: width 0.3s ease;\n }\n\n .chart-bar.passed[_ngcontent-%COMP%] {\n background: #4caf50;\n }\n\n .chart-bar.failed[_ngcontent-%COMP%] {\n background: #f44336;\n }\n\n .chart-bar.skipped[_ngcontent-%COMP%] {\n background: #9e9e9e;\n }\n\n .trend-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 11px;\n }\n\n .pass-rate[_ngcontent-%COMP%] {\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 12px;\n }\n\n .pass-rate.good[_ngcontent-%COMP%] {\n background: #e8f5e9;\n color: #4caf50;\n }\n\n .pass-rate.warning[_ngcontent-%COMP%] {\n background: #fff3e0;\n color: #ff9800;\n }\n\n .pass-rate.poor[_ngcontent-%COMP%] {\n background: #ffebee;\n color: #f44336;\n }\n\n .cost[_ngcontent-%COMP%] {\n color: #666;\n font-weight: 600;\n }\n\n .analytics-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));\n gap: 20px;\n }\n\n .analytics-card[_ngcontent-%COMP%] {\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n\n .card-header[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .card-header[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .card-header[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .card-content[_ngcontent-%COMP%] {\n padding: 16px;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .analytics-row[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px;\n border-bottom: 1px solid #f0f0f0;\n transition: background 0.2s ease;\n }\n\n .analytics-row[_ngcontent-%COMP%]:hover {\n background: #f8f9fa;\n }\n\n .analytics-row[_ngcontent-%COMP%]:last-child {\n border-bottom: none;\n }\n\n .row-info[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .row-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n color: #333;\n margin-bottom: 4px;\n }\n\n .row-meta[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #666;\n }\n\n .row-metrics[_ngcontent-%COMP%] {\n min-width: 120px;\n text-align: right;\n }\n\n .fail-bar-container[_ngcontent-%COMP%] {\n width: 100px;\n height: 6px;\n background: #f0f0f0;\n border-radius: 3px;\n overflow: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n .fail-bar[_ngcontent-%COMP%] {\n height: 100%;\n background: #f44336;\n transition: width 0.3s ease;\n }\n\n .cost-value[_ngcontent-%COMP%], \n .duration-value[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 600;\n color: #2196f3;\n }\n\n .empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 40px 20px;\n color: #999;\n }\n\n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 13px;\n margin: 0;\n }\n\n .score-distribution[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .score-bucket[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 80px 1fr 60px;\n gap: 12px;\n align-items: center;\n }\n\n .bucket-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n }\n\n .bucket-bar-container[_ngcontent-%COMP%] {\n position: relative;\n height: 24px;\n background: #f0f0f0;\n border-radius: 4px;\n overflow: hidden;\n }\n\n .bucket-bar[_ngcontent-%COMP%] {\n height: 100%;\n transition: width 0.3s ease;\n display: flex;\n align-items: center;\n padding: 0 8px;\n }\n\n .bucket-bar.excellent[_ngcontent-%COMP%] {\n background: #4caf50;\n }\n\n .bucket-bar.good[_ngcontent-%COMP%] {\n background: #8bc34a;\n }\n\n .bucket-bar.fair[_ngcontent-%COMP%] {\n background: #ff9800;\n }\n\n .bucket-bar.poor[_ngcontent-%COMP%] {\n background: #ff5722;\n }\n\n .bucket-bar.fail[_ngcontent-%COMP%] {\n background: #f44336;\n }\n\n .bucket-count[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n color: white;\n }\n\n .bucket-percentage[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-align: right;\n }\n\n .cost-summary[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .cost-item[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px;\n background: #f8f9fa;\n border-radius: 6px;\n }\n\n .cost-item.total[_ngcontent-%COMP%] {\n background: #e3f2fd;\n border: 2px solid #2196f3;\n }\n\n .cost-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n }\n\n .cost-item.total[_ngcontent-%COMP%] .cost-label[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .cost-amount[_ngcontent-%COMP%] {\n font-size: 16px;\n font-weight: 700;\n color: #333;\n }\n\n .cost-item.total[_ngcontent-%COMP%] .cost-amount[_ngcontent-%COMP%] {\n color: #2196f3;\n font-size: 20px;\n }\n\n .performance-metrics[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .perf-metric[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px;\n background: #f8f9fa;\n border-radius: 6px;\n }\n\n .perf-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n background: #2196f3;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .perf-content[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .perf-value[_ngcontent-%COMP%] {\n font-size: 20px;\n font-weight: 700;\n color: #333;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .perf-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #666;\n font-weight: 600;\n text-transform: uppercase;\n }\n\n @media (max-width: 1200px) {\n .analytics-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n }"], changeDetection: 0 });
|
|
485
|
+
}
|
|
486
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestingAnalyticsComponent, [{
|
|
487
|
+
type: Component,
|
|
488
|
+
args: [{ selector: 'app-testing-analytics', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
489
|
+
<div class="testing-analytics">
|
|
490
|
+
<div class="analytics-header">
|
|
491
|
+
<div class="header-left">
|
|
492
|
+
<h2>
|
|
493
|
+
<i class="fa-solid fa-chart-bar"></i>
|
|
494
|
+
Testing Analytics
|
|
495
|
+
</h2>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="header-actions">
|
|
498
|
+
<select [(ngModel)]="selectedTimeRange" (change)="onTimeRangeChange()" class="time-range-select">
|
|
499
|
+
@for (range of timeRanges; track range.days) {
|
|
500
|
+
<option [value]="range.days">{{ range.label }}</option>
|
|
501
|
+
}
|
|
502
|
+
</select>
|
|
503
|
+
<button class="action-btn" (click)="refresh()">
|
|
504
|
+
<i class="fa-solid fa-refresh"></i>
|
|
505
|
+
Refresh
|
|
506
|
+
</button>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<!-- Trend Overview -->
|
|
511
|
+
<div class="analytics-section">
|
|
512
|
+
<div class="section-header">
|
|
513
|
+
<h3>
|
|
514
|
+
<i class="fa-solid fa-chart-line"></i>
|
|
515
|
+
Test Execution Trends
|
|
516
|
+
</h3>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="trends-grid">
|
|
519
|
+
@for (trend of (trends$ | async) ?? []; track $index) {
|
|
520
|
+
<div class="trend-card">
|
|
521
|
+
<div class="trend-header">
|
|
522
|
+
<span class="trend-date">{{ trend.timestamp | date:'short' }}</span>
|
|
523
|
+
<span class="trend-total">{{ trend.totalRuns }} runs</span>
|
|
524
|
+
</div>
|
|
525
|
+
<div class="trend-metrics">
|
|
526
|
+
<div class="metric passed">
|
|
527
|
+
<i class="fa-solid fa-check-circle"></i>
|
|
528
|
+
<span>{{ trend.passed }}</span>
|
|
529
|
+
</div>
|
|
530
|
+
<div class="metric failed">
|
|
531
|
+
<i class="fa-solid fa-times-circle"></i>
|
|
532
|
+
<span>{{ trend.failed }}</span>
|
|
533
|
+
</div>
|
|
534
|
+
<div class="metric skipped">
|
|
535
|
+
<i class="fa-solid fa-minus-circle"></i>
|
|
536
|
+
<span>{{ trend.skipped }}</span>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
<div class="trend-chart">
|
|
540
|
+
<div class="chart-bar passed" [style.width.%]="calculatePassRate(trend)"></div>
|
|
541
|
+
<div class="chart-bar failed" [style.width.%]="calculateFailRate(trend)"></div>
|
|
542
|
+
<div class="chart-bar skipped" [style.width.%]="calculateSkipRate(trend)"></div>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="trend-footer">
|
|
545
|
+
<span class="pass-rate" [class.good]="calculatePassRate(trend) >= 80" [class.warning]="calculatePassRate(trend) >= 60 && calculatePassRate(trend) < 80" [class.poor]="calculatePassRate(trend) < 60">
|
|
546
|
+
{{ calculatePassRate(trend).toFixed(1) }}% pass rate
|
|
547
|
+
</span>
|
|
548
|
+
<span class="cost">\${{ trend.totalCost.toFixed(2) }}</span>
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<!-- Analytics Grid -->
|
|
556
|
+
<div class="analytics-grid">
|
|
557
|
+
<!-- Top Failing Tests -->
|
|
558
|
+
<div class="analytics-card">
|
|
559
|
+
<div class="card-header">
|
|
560
|
+
<h4>
|
|
561
|
+
<i class="fa-solid fa-exclamation-triangle"></i>
|
|
562
|
+
Top Failing Tests
|
|
563
|
+
</h4>
|
|
564
|
+
</div>
|
|
565
|
+
<div class="card-content">
|
|
566
|
+
@for (test of (topFailingTests$ | async) ?? []; track test.testName) {
|
|
567
|
+
<div class="analytics-row">
|
|
568
|
+
<div class="row-info">
|
|
569
|
+
<div class="row-name">{{ test.testName }}</div>
|
|
570
|
+
<div class="row-meta">{{ test.failureCount }} failures • {{ test.failureRate.toFixed(1) }}% fail rate</div>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="row-metrics">
|
|
573
|
+
<div class="fail-bar-container">
|
|
574
|
+
<div class="fail-bar" [style.width.%]="test.failureRate"></div>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
} @empty {
|
|
579
|
+
<div class="empty-state">
|
|
580
|
+
<i class="fa-solid fa-check-circle"></i>
|
|
581
|
+
<p>No failing tests!</p>
|
|
582
|
+
</div>
|
|
583
|
+
}
|
|
584
|
+
</div>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<!-- Most Expensive Tests -->
|
|
588
|
+
<div class="analytics-card">
|
|
589
|
+
<div class="card-header">
|
|
590
|
+
<h4>
|
|
591
|
+
<i class="fa-solid fa-dollar-sign"></i>
|
|
592
|
+
Most Expensive Tests
|
|
593
|
+
</h4>
|
|
594
|
+
</div>
|
|
595
|
+
<div class="card-content">
|
|
596
|
+
@for (test of (expensiveTests$ | async) ?? []; track test.testName) {
|
|
597
|
+
<div class="analytics-row">
|
|
598
|
+
<div class="row-info">
|
|
599
|
+
<div class="row-name">{{ test.testName }}</div>
|
|
600
|
+
<div class="row-meta">avg \${{ test.avgCost.toFixed(4) }}</div>
|
|
601
|
+
</div>
|
|
602
|
+
<div class="row-metrics">
|
|
603
|
+
<div class="cost-value">\${{ test.totalCost.toFixed(2) }}</div>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
} @empty {
|
|
607
|
+
<div class="empty-state">
|
|
608
|
+
<i class="fa-solid fa-dollar-sign"></i>
|
|
609
|
+
<p>No test data</p>
|
|
610
|
+
</div>
|
|
611
|
+
}
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
<!-- Slowest Tests -->
|
|
616
|
+
<div class="analytics-card">
|
|
617
|
+
<div class="card-header">
|
|
618
|
+
<h4>
|
|
619
|
+
<i class="fa-solid fa-clock"></i>
|
|
620
|
+
Slowest Tests
|
|
621
|
+
</h4>
|
|
622
|
+
</div>
|
|
623
|
+
<div class="card-content">
|
|
624
|
+
@for (test of (slowestTests$ | async) ?? []; track test.testName) {
|
|
625
|
+
<div class="analytics-row">
|
|
626
|
+
<div class="row-info">
|
|
627
|
+
<div class="row-name">{{ test.testName }}</div>
|
|
628
|
+
<div class="row-meta">avg {{ formatDuration(test.avgDuration) }}</div>
|
|
629
|
+
</div>
|
|
630
|
+
<div class="row-metrics">
|
|
631
|
+
<div class="duration-value">{{ formatDuration(test.maxDuration) }}</div>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
} @empty {
|
|
635
|
+
<div class="empty-state">
|
|
636
|
+
<i class="fa-solid fa-clock"></i>
|
|
637
|
+
<p>No test data</p>
|
|
638
|
+
</div>
|
|
639
|
+
}
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<!-- Test Score Distribution -->
|
|
644
|
+
<div class="analytics-card">
|
|
645
|
+
<div class="card-header">
|
|
646
|
+
<h4>
|
|
647
|
+
<i class="fa-solid fa-chart-pie"></i>
|
|
648
|
+
Score Distribution
|
|
649
|
+
</h4>
|
|
650
|
+
</div>
|
|
651
|
+
<div class="card-content">
|
|
652
|
+
<div class="score-distribution">
|
|
653
|
+
@for (bucket of scoreDistribution$ | async; track bucket.label) {
|
|
654
|
+
<div class="score-bucket">
|
|
655
|
+
<div class="bucket-label">{{ bucket.label }}</div>
|
|
656
|
+
<div class="bucket-bar-container">
|
|
657
|
+
<div class="bucket-bar" [style.width.%]="bucket.percentage" [class]="bucket.class"></div>
|
|
658
|
+
<span class="bucket-count">{{ bucket.count }}</span>
|
|
659
|
+
</div>
|
|
660
|
+
<div class="bucket-percentage">{{ bucket.percentage.toFixed(1) }}%</div>
|
|
661
|
+
</div>
|
|
662
|
+
}
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Cost Breakdown -->
|
|
668
|
+
<div class="analytics-card">
|
|
669
|
+
<div class="card-header">
|
|
670
|
+
<h4>
|
|
671
|
+
<i class="fa-solid fa-wallet"></i>
|
|
672
|
+
Cost Breakdown
|
|
673
|
+
</h4>
|
|
674
|
+
</div>
|
|
675
|
+
<div class="card-content">
|
|
676
|
+
<div class="cost-summary">
|
|
677
|
+
<div class="cost-item total">
|
|
678
|
+
<span class="cost-label">Total Cost</span>
|
|
679
|
+
<span class="cost-amount">\${{ (totalCost$ | async) || 0 | number:'1.2-2' }}</span>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="cost-item">
|
|
682
|
+
<span class="cost-label">Avg per Test</span>
|
|
683
|
+
<span class="cost-amount">\${{ (avgCostPerTest$ | async) || 0 | number:'1.4-4' }}</span>
|
|
684
|
+
</div>
|
|
685
|
+
<div class="cost-item">
|
|
686
|
+
<span class="cost-label">Avg per Run</span>
|
|
687
|
+
<span class="cost-amount">\${{ (avgCostPerRun$ | async) || 0 | number:'1.4-4' }}</span>
|
|
688
|
+
</div>
|
|
689
|
+
<div class="cost-item">
|
|
690
|
+
<span class="cost-label">Projected Monthly</span>
|
|
691
|
+
<span class="cost-amount">\${{ (projectedMonthlyCost$ | async) || 0 | number:'1.2-2' }}</span>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
</div>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<!-- Performance Metrics -->
|
|
698
|
+
<div class="analytics-card">
|
|
699
|
+
<div class="card-header">
|
|
700
|
+
<h4>
|
|
701
|
+
<i class="fa-solid fa-gauge"></i>
|
|
702
|
+
Performance Metrics
|
|
703
|
+
</h4>
|
|
704
|
+
</div>
|
|
705
|
+
<div class="card-content">
|
|
706
|
+
<div class="performance-metrics">
|
|
707
|
+
<div class="perf-metric">
|
|
708
|
+
<div class="perf-icon">
|
|
709
|
+
<i class="fa-solid fa-bolt"></i>
|
|
710
|
+
</div>
|
|
711
|
+
<div class="perf-content">
|
|
712
|
+
<div class="perf-value">{{ formatDuration((avgExecutionTime$ | async) || 0) }}</div>
|
|
713
|
+
<div class="perf-label">Avg Execution Time</div>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
<div class="perf-metric">
|
|
717
|
+
<div class="perf-icon">
|
|
718
|
+
<i class="fa-solid fa-chart-line"></i>
|
|
719
|
+
</div>
|
|
720
|
+
<div class="perf-content">
|
|
721
|
+
<div class="perf-value">{{ (throughput$ | async) || 0 | number:'1.1-1' }}</div>
|
|
722
|
+
<div class="perf-label">Tests per Day</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="perf-metric">
|
|
726
|
+
<div class="perf-icon">
|
|
727
|
+
<i class="fa-solid fa-check-double"></i>
|
|
728
|
+
</div>
|
|
729
|
+
<div class="perf-content">
|
|
730
|
+
<div class="perf-value">{{ (reliabilityScore$ | async) || 0 | number:'1.1-1' }}%</div>
|
|
731
|
+
<div class="perf-label">Reliability Score</div>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
739
|
+
`, styles: ["\n .testing-analytics {\n padding: 20px;\n height: 100%;\n overflow-y: auto;\n background: #f8f9fa;\n }\n\n .analytics-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 24px;\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .header-left h2 {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .header-left h2 i {\n color: #2196f3;\n }\n\n .header-actions {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .time-range-select {\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n cursor: pointer;\n }\n\n .action-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: 1px solid #ddd;\n border-radius: 4px;\n background: white;\n color: #666;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .action-btn:hover {\n background: #f5f5f5;\n }\n\n .analytics-section {\n background: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n margin-bottom: 24px;\n }\n\n .section-header {\n margin-bottom: 20px;\n padding-bottom: 12px;\n border-bottom: 2px solid #f0f0f0;\n }\n\n .section-header h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .section-header h3 i {\n color: #2196f3;\n }\n\n .trends-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 16px;\n }\n\n .trend-card {\n background: #f8f9fa;\n padding: 16px;\n border-radius: 6px;\n border: 1px solid #e0e0e0;\n }\n\n .trend-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n }\n\n .trend-date {\n font-size: 11px;\n color: #666;\n font-weight: 600;\n }\n\n .trend-total {\n font-size: 11px;\n color: #2196f3;\n font-weight: 600;\n }\n\n .trend-metrics {\n display: flex;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n .metric {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n }\n\n .metric.passed {\n color: #4caf50;\n }\n\n .metric.failed {\n color: #f44336;\n }\n\n .metric.skipped {\n color: #9e9e9e;\n }\n\n .trend-chart {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 12px;\n background: #e0e0e0;\n }\n\n .chart-bar {\n transition: width 0.3s ease;\n }\n\n .chart-bar.passed {\n background: #4caf50;\n }\n\n .chart-bar.failed {\n background: #f44336;\n }\n\n .chart-bar.skipped {\n background: #9e9e9e;\n }\n\n .trend-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 11px;\n }\n\n .pass-rate {\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 12px;\n }\n\n .pass-rate.good {\n background: #e8f5e9;\n color: #4caf50;\n }\n\n .pass-rate.warning {\n background: #fff3e0;\n color: #ff9800;\n }\n\n .pass-rate.poor {\n background: #ffebee;\n color: #f44336;\n }\n\n .cost {\n color: #666;\n font-weight: 600;\n }\n\n .analytics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));\n gap: 20px;\n }\n\n .analytics-card {\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n }\n\n .card-header {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .card-header h4 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: #333;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .card-header h4 i {\n color: #2196f3;\n }\n\n .card-content {\n padding: 16px;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .analytics-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px;\n border-bottom: 1px solid #f0f0f0;\n transition: background 0.2s ease;\n }\n\n .analytics-row:hover {\n background: #f8f9fa;\n }\n\n .analytics-row:last-child {\n border-bottom: none;\n }\n\n .row-info {\n flex: 1;\n }\n\n .row-name {\n font-size: 13px;\n font-weight: 500;\n color: #333;\n margin-bottom: 4px;\n }\n\n .row-meta {\n font-size: 11px;\n color: #666;\n }\n\n .row-metrics {\n min-width: 120px;\n text-align: right;\n }\n\n .fail-bar-container {\n width: 100px;\n height: 6px;\n background: #f0f0f0;\n border-radius: 3px;\n overflow: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n .fail-bar {\n height: 100%;\n background: #f44336;\n transition: width 0.3s ease;\n }\n\n .cost-value,\n .duration-value {\n font-size: 13px;\n font-weight: 600;\n color: #2196f3;\n }\n\n .empty-state {\n text-align: center;\n padding: 40px 20px;\n color: #999;\n }\n\n .empty-state i {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n }\n\n .empty-state p {\n font-size: 13px;\n margin: 0;\n }\n\n .score-distribution {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .score-bucket {\n display: grid;\n grid-template-columns: 80px 1fr 60px;\n gap: 12px;\n align-items: center;\n }\n\n .bucket-label {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n }\n\n .bucket-bar-container {\n position: relative;\n height: 24px;\n background: #f0f0f0;\n border-radius: 4px;\n overflow: hidden;\n }\n\n .bucket-bar {\n height: 100%;\n transition: width 0.3s ease;\n display: flex;\n align-items: center;\n padding: 0 8px;\n }\n\n .bucket-bar.excellent {\n background: #4caf50;\n }\n\n .bucket-bar.good {\n background: #8bc34a;\n }\n\n .bucket-bar.fair {\n background: #ff9800;\n }\n\n .bucket-bar.poor {\n background: #ff5722;\n }\n\n .bucket-bar.fail {\n background: #f44336;\n }\n\n .bucket-count {\n font-size: 11px;\n font-weight: 600;\n color: white;\n }\n\n .bucket-percentage {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n text-align: right;\n }\n\n .cost-summary {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .cost-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px;\n background: #f8f9fa;\n border-radius: 6px;\n }\n\n .cost-item.total {\n background: #e3f2fd;\n border: 2px solid #2196f3;\n }\n\n .cost-label {\n font-size: 12px;\n font-weight: 600;\n color: #666;\n }\n\n .cost-item.total .cost-label {\n color: #2196f3;\n }\n\n .cost-amount {\n font-size: 16px;\n font-weight: 700;\n color: #333;\n }\n\n .cost-item.total .cost-amount {\n color: #2196f3;\n font-size: 20px;\n }\n\n .performance-metrics {\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n\n .perf-metric {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px;\n background: #f8f9fa;\n border-radius: 6px;\n }\n\n .perf-icon {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n background: #2196f3;\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .perf-content {\n flex: 1;\n }\n\n .perf-value {\n font-size: 20px;\n font-weight: 700;\n color: #333;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .perf-label {\n font-size: 11px;\n color: #666;\n font-weight: 600;\n text-transform: uppercase;\n }\n\n @media (max-width: 1200px) {\n .analytics-grid {\n grid-template-columns: 1fr;\n }\n }\n "] }]
|
|
740
|
+
}], () => [{ type: i1.TestingInstrumentationService }, { type: i0.ChangeDetectorRef }], { initialState: [{
|
|
741
|
+
type: Input
|
|
742
|
+
}], stateChange: [{
|
|
743
|
+
type: Output
|
|
744
|
+
}] }); })();
|
|
745
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestingAnalyticsComponent, { className: "TestingAnalyticsComponent", filePath: "src/Testing/components/testing-analytics.component.ts", lineNumber: 756 }); })();
|
|
746
|
+
//# sourceMappingURL=testing-analytics.component.js.map
|