@memberjunction/ng-dashboards 2.129.0 → 2.130.1

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.
Files changed (22) hide show
  1. package/dist/Testing/components/testing-execution.component.d.ts +16 -5
  2. package/dist/Testing/components/testing-execution.component.d.ts.map +1 -1
  3. package/dist/Testing/components/testing-execution.component.js +452 -273
  4. package/dist/Testing/components/testing-execution.component.js.map +1 -1
  5. package/dist/Testing/components/testing-feedback.component.d.ts +70 -14
  6. package/dist/Testing/components/testing-feedback.component.d.ts.map +1 -1
  7. package/dist/Testing/components/testing-feedback.component.js +1177 -479
  8. package/dist/Testing/components/testing-feedback.component.js.map +1 -1
  9. package/dist/Testing/components/testing-overview.component.d.ts.map +1 -1
  10. package/dist/Testing/components/testing-overview.component.js +182 -162
  11. package/dist/Testing/components/testing-overview.component.js.map +1 -1
  12. package/dist/Testing/components/testing-version-comparison.component.d.ts +4 -0
  13. package/dist/Testing/components/testing-version-comparison.component.d.ts.map +1 -1
  14. package/dist/Testing/components/testing-version-comparison.component.js +19 -5
  15. package/dist/Testing/components/testing-version-comparison.component.js.map +1 -1
  16. package/dist/Testing/services/testing-instrumentation.service.d.ts +47 -1
  17. package/dist/Testing/services/testing-instrumentation.service.d.ts.map +1 -1
  18. package/dist/Testing/services/testing-instrumentation.service.js +243 -60
  19. package/dist/Testing/services/testing-instrumentation.service.js.map +1 -1
  20. package/dist/Testing/testing-dashboard.component.js +36 -34
  21. package/dist/Testing/testing-dashboard.component.js.map +1 -1
  22. package/package.json +27 -27
@@ -1,97 +1,104 @@
1
1
  import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
2
- import { Subject, combineLatest } from 'rxjs';
2
+ import { Subject, combineLatest, BehaviorSubject } from 'rxjs';
3
3
  import { takeUntil, map } from 'rxjs/operators';
4
- import { CompositeKey } from '@memberjunction/core';
4
+ import { CompositeKey, Metadata } from '@memberjunction/core';
5
5
  import { SharedService } from '@memberjunction/ng-shared';
6
+ import { GraphQLTestingClient } from '@memberjunction/graphql-dataprovider';
6
7
  import { TestRunDialogComponent } from '@memberjunction/ng-testing';
7
8
  import * as i0 from "@angular/core";
8
9
  import * as i1 from "../services/testing-instrumentation.service";
9
10
  import * as i2 from "@progress/kendo-angular-dialog";
10
- import * as i3 from "@angular/common";
11
- import * as i4 from "@angular/forms";
12
- import * as i5 from "@memberjunction/ng-testing";
11
+ import * as i3 from "@angular/forms";
12
+ import * as i4 from "@memberjunction/ng-testing";
13
+ import * as i5 from "@memberjunction/ng-shared-generic";
14
+ import * as i6 from "@angular/common";
13
15
  const _forTrack0 = ($index, $item) => $item.id;
14
16
  const _c0 = () => [];
15
- function TestingExecutionComponent_div_6_Template(rf, ctx) { if (rf & 1) {
16
- i0.ɵɵelementStart(0, "div", 51);
17
- i0.ɵɵelement(1, "span", 52);
18
- i0.ɵɵelementStart(2, "span", 53);
17
+ function TestingExecutionComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
18
+ i0.ɵɵelementStart(0, "div", 9);
19
+ i0.ɵɵelement(1, "span", 61);
20
+ i0.ɵɵelementStart(2, "span", 62);
19
21
  i0.ɵɵtext(3, "Live");
20
22
  i0.ɵɵelementEnd()();
21
23
  } }
22
- function TestingExecutionComponent_button_48_Template(rf, ctx) { if (rf & 1) {
24
+ function TestingExecutionComponent_Conditional_51_Template(rf, ctx) { if (rf & 1) {
23
25
  const _r1 = i0.ɵɵgetCurrentView();
24
- i0.ɵɵelementStart(0, "button", 54);
25
- i0.ɵɵlistener("click", function TestingExecutionComponent_button_48_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.clearSearch()); });
26
- i0.ɵɵelement(1, "i", 55);
26
+ i0.ɵɵelementStart(0, "button", 63);
27
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Conditional_51_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.clearSearch()); });
28
+ i0.ɵɵelement(1, "i", 23);
27
29
  i0.ɵɵelementEnd();
28
30
  } }
29
- function TestingExecutionComponent_Conditional_103_Template(rf, ctx) { if (rf & 1) {
31
+ function TestingExecutionComponent_Conditional_112_Template(rf, ctx) { if (rf & 1) {
32
+ i0.ɵɵelementStart(0, "div", 58);
33
+ i0.ɵɵelement(1, "mj-loading", 64);
34
+ i0.ɵɵelementEnd();
35
+ } }
36
+ function TestingExecutionComponent_Conditional_114_Template(rf, ctx) { if (rf & 1) {
30
37
  const _r3 = i0.ɵɵgetCurrentView();
31
- i0.ɵɵelementStart(0, "div", 49);
32
- i0.ɵɵelement(1, "i", 56);
38
+ i0.ɵɵelementStart(0, "div", 59);
39
+ i0.ɵɵelement(1, "i", 65);
33
40
  i0.ɵɵelementStart(2, "p");
34
41
  i0.ɵɵtext(3, "No test executions found");
35
42
  i0.ɵɵelementEnd();
36
- i0.ɵɵelementStart(4, "button", 8);
37
- i0.ɵɵlistener("click", function TestingExecutionComponent_Conditional_103_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.startNewTest()); });
38
- i0.ɵɵelement(5, "i", 9);
43
+ i0.ɵɵelementStart(4, "button", 66);
44
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Conditional_114_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.startNewTest()); });
45
+ i0.ɵɵelement(5, "i", 14);
39
46
  i0.ɵɵtext(6, " Run Your First Test ");
40
47
  i0.ɵɵelementEnd()();
41
48
  } }
42
- function TestingExecutionComponent_For_106_Conditional_9_Template(rf, ctx) { if (rf & 1) {
43
- i0.ɵɵelementStart(0, "div", 64);
44
- i0.ɵɵelement(1, "div", 76);
49
+ function TestingExecutionComponent_For_116_Conditional_9_Template(rf, ctx) { if (rf & 1) {
50
+ i0.ɵɵelementStart(0, "div", 74);
51
+ i0.ɵɵelement(1, "div", 86);
45
52
  i0.ɵɵelementEnd();
46
53
  } if (rf & 2) {
47
54
  const execution_r5 = i0.ɵɵnextContext().$implicit;
48
55
  i0.ɵɵadvance();
49
56
  i0.ɵɵstyleProp("width", execution_r5.progress, "%");
50
57
  } }
51
- function TestingExecutionComponent_For_106_Conditional_22_Template(rf, ctx) { if (rf & 1) {
58
+ function TestingExecutionComponent_For_116_Conditional_22_Template(rf, ctx) { if (rf & 1) {
52
59
  const _r6 = i0.ɵɵgetCurrentView();
53
- i0.ɵɵelementStart(0, "button", 77);
54
- i0.ɵɵlistener("click", function TestingExecutionComponent_For_106_Conditional_22_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r6); const execution_r5 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelExecution(execution_r5)); });
55
- i0.ɵɵelement(1, "i", 78);
60
+ i0.ɵɵelementStart(0, "button", 87);
61
+ i0.ɵɵlistener("click", function TestingExecutionComponent_For_116_Conditional_22_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r6); const execution_r5 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelExecution(execution_r5)); });
62
+ i0.ɵɵelement(1, "i", 88);
56
63
  i0.ɵɵelementEnd();
57
64
  } }
58
- function TestingExecutionComponent_For_106_Conditional_23_Template(rf, ctx) { if (rf & 1) {
65
+ function TestingExecutionComponent_For_116_Conditional_23_Template(rf, ctx) { if (rf & 1) {
59
66
  const _r7 = i0.ɵɵgetCurrentView();
60
- i0.ɵɵelementStart(0, "button", 79);
61
- i0.ɵɵlistener("click", function TestingExecutionComponent_For_106_Conditional_23_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const execution_r5 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.rerunTest(execution_r5)); });
62
- i0.ɵɵelement(1, "i", 80);
67
+ i0.ɵɵelementStart(0, "button", 89);
68
+ i0.ɵɵlistener("click", function TestingExecutionComponent_For_116_Conditional_23_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const execution_r5 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.rerunTest(execution_r5)); });
69
+ i0.ɵɵelement(1, "i", 90);
63
70
  i0.ɵɵelementEnd();
64
71
  } }
65
- function TestingExecutionComponent_For_106_Template(rf, ctx) { if (rf & 1) {
72
+ function TestingExecutionComponent_For_116_Template(rf, ctx) { if (rf & 1) {
66
73
  const _r4 = i0.ɵɵgetCurrentView();
67
- i0.ɵɵelementStart(0, "div", 57)(1, "div", 58)(2, "div", 59)(3, "div", 60);
74
+ i0.ɵɵelementStart(0, "div", 67)(1, "div", 68)(2, "div", 69)(3, "div", 70);
68
75
  i0.ɵɵtext(4);
69
76
  i0.ɵɵelementEnd();
70
- i0.ɵɵelementStart(5, "div", 61);
77
+ i0.ɵɵelementStart(5, "div", 71);
71
78
  i0.ɵɵtext(6);
72
79
  i0.ɵɵelementEnd()()();
73
- i0.ɵɵelementStart(7, "div", 62);
74
- i0.ɵɵelement(8, "app-test-status-badge", 63);
75
- i0.ɵɵtemplate(9, TestingExecutionComponent_For_106_Conditional_9_Template, 2, 2, "div", 64);
80
+ i0.ɵɵelementStart(7, "div", 72);
81
+ i0.ɵɵelement(8, "app-test-status-badge", 73);
82
+ i0.ɵɵtemplate(9, TestingExecutionComponent_For_116_Conditional_9_Template, 2, 2, "div", 74);
76
83
  i0.ɵɵelementEnd();
77
- i0.ɵɵelementStart(10, "div", 65);
78
- i0.ɵɵelement(11, "app-score-indicator", 66);
84
+ i0.ɵɵelementStart(10, "div", 75);
85
+ i0.ɵɵelement(11, "app-score-indicator", 76);
79
86
  i0.ɵɵelementEnd();
80
- i0.ɵɵelementStart(12, "div", 67);
87
+ i0.ɵɵelementStart(12, "div", 77);
81
88
  i0.ɵɵtext(13);
82
89
  i0.ɵɵelementEnd();
83
- i0.ɵɵelementStart(14, "div", 68);
84
- i0.ɵɵelement(15, "app-cost-display", 69);
90
+ i0.ɵɵelementStart(14, "div", 78);
91
+ i0.ɵɵelement(15, "app-cost-display", 79);
85
92
  i0.ɵɵelementEnd();
86
- i0.ɵɵelementStart(16, "div", 70);
93
+ i0.ɵɵelementStart(16, "div", 80);
87
94
  i0.ɵɵtext(17);
88
95
  i0.ɵɵpipe(18, "date");
89
96
  i0.ɵɵelementEnd();
90
- i0.ɵɵelementStart(19, "div", 71)(20, "button", 72);
91
- i0.ɵɵlistener("click", function TestingExecutionComponent_For_106_Template_button_click_20_listener() { const execution_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.viewDetails(execution_r5)); });
92
- i0.ɵɵelement(21, "i", 73);
97
+ i0.ɵɵelementStart(19, "div", 81)(20, "button", 82);
98
+ i0.ɵɵlistener("click", function TestingExecutionComponent_For_116_Template_button_click_20_listener() { const execution_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.viewDetails(execution_r5)); });
99
+ i0.ɵɵelement(21, "i", 83);
93
100
  i0.ɵɵelementEnd();
94
- i0.ɵɵtemplate(22, TestingExecutionComponent_For_106_Conditional_22_Template, 2, 0, "button", 74)(23, TestingExecutionComponent_For_106_Conditional_23_Template, 2, 0, "button", 75);
101
+ i0.ɵɵtemplate(22, TestingExecutionComponent_For_116_Conditional_22_Template, 2, 0, "button", 84)(23, TestingExecutionComponent_For_116_Conditional_23_Template, 2, 0, "button", 85);
95
102
  i0.ɵɵelementEnd()();
96
103
  } if (rf & 2) {
97
104
  const execution_r5 = ctx.$implicit;
@@ -120,17 +127,24 @@ export class TestingExecutionComponent {
120
127
  instrumentationService;
121
128
  dialogService;
122
129
  cdr;
130
+ viewContainerRef;
123
131
  initialState;
124
132
  stateChange = new EventEmitter();
125
133
  destroy$ = new Subject();
126
134
  activeDialogRef = null;
127
135
  isRefreshing = false;
136
+ isLoading = false;
137
+ lastUpdated = new Date();
128
138
  filters = {
129
139
  status: 'all',
130
140
  suite: 'all',
131
- timeRange: 'today',
141
+ timeRange: 'month', // Default to "This Month" to show more data
132
142
  searchText: ''
133
143
  };
144
+ // Track previous time range to detect changes requiring server re-query
145
+ previousTimeRange = 'month';
146
+ // BehaviorSubject to trigger client-side filter updates
147
+ filterTrigger$ = new BehaviorSubject(undefined);
134
148
  executions$;
135
149
  filteredExecutions$;
136
150
  runningCount$;
@@ -138,16 +152,29 @@ export class TestingExecutionComponent {
138
152
  failedTodayCount$;
139
153
  avgDurationToday$;
140
154
  hasRunningTests$;
141
- constructor(instrumentationService, dialogService, cdr) {
155
+ testingClient;
156
+ constructor(instrumentationService, dialogService, cdr, viewContainerRef) {
142
157
  this.instrumentationService = instrumentationService;
143
158
  this.dialogService = dialogService;
144
159
  this.cdr = cdr;
160
+ this.viewContainerRef = viewContainerRef;
161
+ // Initialize GraphQL testing client for cancel/rerun operations
162
+ const dataProvider = Metadata.Provider;
163
+ this.testingClient = new GraphQLTestingClient(dataProvider);
164
+ // Subscribe to loading state
165
+ this.instrumentationService.isLoading$.pipe(takeUntil(this.destroy$)).subscribe(loading => {
166
+ this.isLoading = loading;
167
+ this.cdr.markForCheck();
168
+ });
145
169
  }
146
170
  ngOnInit() {
147
- this.setupObservables();
171
+ // Apply initial state if provided
148
172
  if (this.initialState) {
149
173
  this.filters = { ...this.filters, ...this.initialState.filters };
150
174
  }
175
+ // Set the service date range based on the selected time range filter
176
+ this.updateServiceDateRange();
177
+ this.setupObservables();
151
178
  }
152
179
  ngOnDestroy() {
153
180
  // Close any open dialog when component is destroyed
@@ -165,30 +192,22 @@ export class TestingExecutionComponent {
165
192
  }
166
193
  setupObservables() {
167
194
  this.executions$ = this.instrumentationService.testRuns$.pipe(map(runs => runs.map(run => this.mapToExecutionItem(run))), takeUntil(this.destroy$));
195
+ // Combine executions with filter trigger to react to client-side filter changes
168
196
  this.filteredExecutions$ = combineLatest([
169
- this.executions$
197
+ this.executions$,
198
+ this.filterTrigger$
170
199
  ]).pipe(map(([executions]) => this.applyFilters(executions)), takeUntil(this.destroy$));
200
+ // KPI counts are now based on the full executions$ (which respects the service date range)
201
+ // This means the counts will match the selected time range filter
171
202
  this.runningCount$ = this.executions$.pipe(map(execs => execs.filter(e => e.status === 'Running').length));
172
- this.completedTodayCount$ = this.executions$.pipe(map(execs => {
173
- const today = new Date();
174
- today.setHours(0, 0, 0, 0);
175
- return execs.filter(e => e.status === 'Passed' &&
176
- e.startedAt >= today).length;
177
- }));
178
- this.failedTodayCount$ = this.executions$.pipe(map(execs => {
179
- const today = new Date();
180
- today.setHours(0, 0, 0, 0);
181
- return execs.filter(e => e.status === 'Failed' &&
182
- e.startedAt >= today).length;
183
- }));
203
+ this.completedTodayCount$ = this.executions$.pipe(map(execs => execs.filter(e => e.status === 'Passed').length));
204
+ this.failedTodayCount$ = this.executions$.pipe(map(execs => execs.filter(e => e.status === 'Failed' || e.status === 'Error').length));
184
205
  this.avgDurationToday$ = this.executions$.pipe(map(execs => {
185
- const today = new Date();
186
- today.setHours(0, 0, 0, 0);
187
- const todayExecs = execs.filter(e => e.startedAt >= today && e.completedAt);
188
- if (todayExecs.length === 0)
206
+ const completedExecs = execs.filter(e => e.completedAt || e.status !== 'Running');
207
+ if (completedExecs.length === 0)
189
208
  return 0;
190
- const totalDuration = todayExecs.reduce((sum, e) => sum + e.duration, 0);
191
- return totalDuration / todayExecs.length;
209
+ const totalDuration = completedExecs.reduce((sum, e) => sum + e.duration, 0);
210
+ return totalDuration / completedExecs.length;
192
211
  }));
193
212
  this.hasRunningTests$ = this.runningCount$.pipe(map(count => count > 0));
194
213
  }
@@ -199,6 +218,7 @@ export class TestingExecutionComponent {
199
218
  const progress = run.status === 'Running' ? Math.random() * 100 : 100;
200
219
  return {
201
220
  id: run.id,
221
+ testId: run.testId,
202
222
  testName: run.testName,
203
223
  suiteName: run.suiteName,
204
224
  status: run.status,
@@ -212,6 +232,7 @@ export class TestingExecutionComponent {
212
232
  }
213
233
  applyFilters(executions) {
214
234
  let filtered = [...executions];
235
+ // Apply status filter
215
236
  if (this.filters.status !== 'all') {
216
237
  if (this.filters.status === 'running') {
217
238
  filtered = filtered.filter(e => e.status === 'Running');
@@ -226,32 +247,51 @@ export class TestingExecutionComponent {
226
247
  filtered = filtered.filter(e => e.status === 'Passed');
227
248
  }
228
249
  }
229
- if (this.filters.timeRange !== 'all') {
230
- const now = new Date();
231
- let startDate = new Date();
232
- if (this.filters.timeRange === 'today') {
233
- startDate.setHours(0, 0, 0, 0);
234
- }
235
- else if (this.filters.timeRange === 'week') {
236
- startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
237
- }
238
- else if (this.filters.timeRange === 'month') {
239
- startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
240
- }
241
- filtered = filtered.filter(e => e.startedAt >= startDate);
242
- }
250
+ // Note: Time range filtering is now handled by the service via updateServiceDateRange()
251
+ // This ensures the KPIs and list use the same data set
252
+ // Apply search text filter
243
253
  if (this.filters.searchText) {
244
254
  const searchLower = this.filters.searchText.toLowerCase();
245
255
  filtered = filtered.filter(e => e.testName.toLowerCase().includes(searchLower) ||
246
256
  e.suiteName.toLowerCase().includes(searchLower));
247
257
  }
258
+ // Sort by most recent first
248
259
  filtered.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
249
260
  return filtered;
250
261
  }
251
262
  onFilterChange() {
263
+ // Only re-query server when time range changes - status/search filtering is client-side only
264
+ if (this.filters.timeRange !== this.previousTimeRange) {
265
+ this.previousTimeRange = this.filters.timeRange;
266
+ this.updateServiceDateRange();
267
+ }
268
+ // Trigger client-side filter update via observable
269
+ this.filterTrigger$.next();
252
270
  this.emitStateChange();
253
271
  this.cdr.markForCheck();
254
272
  }
273
+ updateServiceDateRange() {
274
+ const now = new Date();
275
+ let startDate;
276
+ switch (this.filters.timeRange) {
277
+ case 'today':
278
+ startDate = new Date(now);
279
+ startDate.setHours(0, 0, 0, 0);
280
+ break;
281
+ case 'week':
282
+ startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
283
+ break;
284
+ case 'month':
285
+ startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
286
+ break;
287
+ case 'all':
288
+ default:
289
+ // For "all time", use a very old date (e.g., 1 year ago)
290
+ startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
291
+ break;
292
+ }
293
+ this.instrumentationService.setDateRange(startDate, now);
294
+ }
255
295
  clearSearch() {
256
296
  this.filters.searchText = '';
257
297
  this.onFilterChange();
@@ -261,9 +301,14 @@ export class TestingExecutionComponent {
261
301
  this.instrumentationService.refresh();
262
302
  setTimeout(() => {
263
303
  this.isRefreshing = false;
304
+ this.lastUpdated = new Date();
264
305
  this.cdr.markForCheck();
265
306
  }, 1000);
266
307
  }
308
+ filterByStatus(status) {
309
+ this.filters.status = status;
310
+ this.onFilterChange();
311
+ }
267
312
  startNewTest() {
268
313
  this.activeDialogRef = this.dialogService.open({
269
314
  content: TestRunDialogComponent,
@@ -289,11 +334,44 @@ export class TestingExecutionComponent {
289
334
  viewDetails(execution) {
290
335
  SharedService.Instance.OpenEntityRecord('MJ: Test Runs', CompositeKey.FromID(execution.id));
291
336
  }
292
- cancelExecution(execution) {
293
- console.log('Cancel execution:', execution);
337
+ async cancelExecution(execution) {
338
+ // For now, show a notification - full cancel support requires server-side CancelTest mutation
339
+ // which we documented in the plan but haven't implemented yet
340
+ SharedService.Instance.CreateSimpleNotification(`Cancellation requested for "${execution.testName}". Full cancellation support coming soon.`, 'warning', 3000);
341
+ // Refresh after a delay to pick up any status changes
342
+ setTimeout(() => this.refresh(), 1500);
294
343
  }
295
- rerunTest(execution) {
296
- console.log('Re-run test:', execution);
344
+ async rerunTest(execution) {
345
+ if (!execution.testId) {
346
+ SharedService.Instance.CreateSimpleNotification('Cannot re-run: Test ID not available', 'error', 3000);
347
+ return;
348
+ }
349
+ // Open the test run dialog with the test pre-selected
350
+ this.activeDialogRef = this.dialogService.open({
351
+ content: TestRunDialogComponent,
352
+ width: 1000,
353
+ height: 750,
354
+ title: 'Re-run Test',
355
+ actions: []
356
+ });
357
+ // Pre-configure the dialog with the test
358
+ const dialogComponent = this.activeDialogRef.content.instance;
359
+ dialogComponent.runMode = 'test';
360
+ dialogComponent.selectedTestId = execution.testId;
361
+ this.activeDialogRef.result.pipe(takeUntil(this.destroy$)).subscribe({
362
+ next: (result) => {
363
+ if (result && typeof result === 'object' && 'testExecuted' in result && result.testExecuted) {
364
+ SharedService.Instance.CreateSimpleNotification(`Test "${execution.testName}" completed`, 'success', 3000);
365
+ this.refresh();
366
+ }
367
+ },
368
+ error: () => {
369
+ this.activeDialogRef = null;
370
+ },
371
+ complete: () => {
372
+ this.activeDialogRef = null;
373
+ }
374
+ });
297
375
  }
298
376
  formatDuration(milliseconds) {
299
377
  if (milliseconds < 1000)
@@ -304,227 +382,304 @@ export class TestingExecutionComponent {
304
382
  return `${minutes}m ${seconds % 60}s`;
305
383
  return `${seconds}s`;
306
384
  }
385
+ getTimeRangeLabel() {
386
+ switch (this.filters.timeRange) {
387
+ case 'today': return 'Today';
388
+ case 'week': return 'This Week';
389
+ case 'month': return 'This Month';
390
+ case 'all': return 'All Time';
391
+ default: return '';
392
+ }
393
+ }
307
394
  emitStateChange() {
308
395
  this.stateChange.emit({
309
396
  filters: this.filters
310
397
  });
311
398
  }
312
- static ɵfac = function TestingExecutionComponent_Factory(t) { return new (t || TestingExecutionComponent)(i0.ɵɵdirectiveInject(i1.TestingInstrumentationService), i0.ɵɵdirectiveInject(i2.DialogService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
313
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestingExecutionComponent, selectors: [["app-testing-execution"]], inputs: { initialState: "initialState" }, outputs: { stateChange: "stateChange" }, decls: 108, vars: 28, consts: [["kendoDialogContainer", "", 1, "testing-execution"], [1, "execution-header"], [1, "header-left"], [1, "fa-solid", "fa-play-circle"], ["class", "live-indicator", 4, "ngIf"], [1, "header-actions"], [1, "action-btn", "refresh", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], [1, "action-btn", "primary", 3, "click"], [1, "fa-solid", "fa-play"], [1, "execution-filters"], [1, "filter-group"], [3, "ngModelChange", "change", "ngModel"], ["value", "all"], ["value", "running"], ["value", "completed"], ["value", "failed"], ["value", "passed"], ["value", "today"], ["value", "week"], ["value", "month"], [1, "filter-group", "search"], [1, "search-input-wrapper"], [1, "fa-solid", "fa-search"], ["type", "text", "placeholder", "Search tests...", 3, "ngModelChange", "input", "ngModel"], ["class", "clear-btn", 3, "click", 4, "ngIf"], [1, "execution-summary"], [1, "summary-card"], [1, "summary-icon", "running"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "summary-content"], [1, "summary-value"], [1, "summary-label"], [1, "summary-icon", "completed"], [1, "fa-solid", "fa-check-circle"], [1, "summary-icon", "failed"], [1, "fa-solid", "fa-exclamation-circle"], [1, "summary-icon", "duration"], [1, "fa-solid", "fa-clock"], [1, "execution-content"], [1, "execution-list"], [1, "list-header"], [1, "header-cell", "test-name"], [1, "header-cell", "status"], [1, "header-cell", "score"], [1, "header-cell", "duration"], [1, "header-cell", "cost"], [1, "header-cell", "timestamp"], [1, "header-cell", "actions"], [1, "no-data"], [1, "execution-row", 3, "running"], [1, "live-indicator"], [1, "pulse"], [1, "text"], [1, "clear-btn", 3, "click"], [1, "fa-solid", "fa-times"], [1, "fa-solid", "fa-inbox"], [1, "execution-row"], [1, "cell", "test-name"], [1, "test-info"], [1, "name"], [1, "suite"], [1, "cell", "status"], [3, "status"], [1, "progress-bar"], [1, "cell", "score"], [3, "score", "showBar", "showIcon"], [1, "cell", "duration"], [1, "cell", "cost"], [3, "cost", "showIcon"], [1, "cell", "timestamp"], [1, "cell", "actions"], ["title", "View Details", 1, "icon-btn", 3, "click"], [1, "fa-solid", "fa-eye"], ["title", "Cancel", 1, "icon-btn", "danger"], ["title", "Re-run", 1, "icon-btn"], [1, "progress-fill"], ["title", "Cancel", 1, "icon-btn", "danger", 3, "click"], [1, "fa-solid", "fa-stop"], ["title", "Re-run", 1, "icon-btn", 3, "click"], [1, "fa-solid", "fa-redo"]], template: function TestingExecutionComponent_Template(rf, ctx) { if (rf & 1) {
314
- i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "h2");
315
- i0.ɵɵelement(4, "i", 3);
316
- i0.ɵɵtext(5, " Test Execution Monitor ");
399
+ static ɵfac = function TestingExecutionComponent_Factory(t) { return new (t || TestingExecutionComponent)(i0.ɵɵdirectiveInject(i1.TestingInstrumentationService), i0.ɵɵdirectiveInject(i2.DialogService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ViewContainerRef)); };
400
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestingExecutionComponent, selectors: [["app-testing-execution"]], inputs: { initialState: "initialState" }, outputs: { stateChange: "stateChange" }, decls: 118, vars: 41, consts: [["kendoDialogContainer", "", 1, "testing-execution"], [1, "execution-header"], [1, "header-left"], [1, "header-icon"], [1, "fa-solid", "fa-play-circle"], [1, "header-text"], [1, "header-meta"], [1, "last-updated"], [1, "fa-solid", "fa-clock"], [1, "live-indicator"], [1, "header-actions"], [1, "action-btn", "refresh-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-sync-alt"], [1, "action-btn", "primary-btn", 3, "click"], [1, "fa-solid", "fa-play"], [1, "filter-bar"], [1, "filter-chips"], [1, "filter-chip", 3, "click"], [1, "filter-chip", "running", 3, "click"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "filter-chip", "passed", 3, "click"], [1, "fa-solid", "fa-check"], [1, "filter-chip", "failed", 3, "click"], [1, "fa-solid", "fa-times"], [1, "filter-controls"], [1, "time-select"], [3, "ngModelChange", "change", "ngModel"], ["value", "today"], ["value", "week"], ["value", "month"], ["value", "all"], [1, "search-input"], [1, "fa-solid", "fa-search"], ["type", "text", "placeholder", "Search tests...", 3, "ngModelChange", "input", "ngModel"], [1, "clear-btn"], [1, "kpi-grid"], [1, "kpi-card", "running", "clickable", 3, "click"], [1, "kpi-icon"], [1, "kpi-content"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-arrow"], [1, "fa-solid", "fa-chevron-right"], [1, "kpi-card", "passed", "clickable", 3, "click"], [1, "fa-solid", "fa-check-circle"], [1, "kpi-card", "failed", "clickable", 3, "click"], [1, "fa-solid", "fa-exclamation-circle"], [1, "kpi-card", "duration"], [1, "execution-content"], [1, "execution-list"], [1, "list-header"], [1, "header-cell", "test-name"], [1, "header-cell", "status"], [1, "header-cell", "score"], [1, "header-cell", "duration"], [1, "header-cell", "cost"], [1, "header-cell", "timestamp"], [1, "header-cell", "actions"], [1, "loading-placeholder"], [1, "no-data"], [1, "execution-row", 3, "running"], [1, "pulse"], [1, "text"], [1, "clear-btn", 3, "click"], ["text", "Loading test executions..."], [1, "fa-solid", "fa-inbox"], [1, "action-btn", "primary", 3, "click"], [1, "execution-row"], [1, "cell", "test-name"], [1, "test-info"], [1, "name"], [1, "suite"], [1, "cell", "status"], [3, "status"], [1, "progress-bar"], [1, "cell", "score"], [3, "score", "showBar", "showIcon"], [1, "cell", "duration"], [1, "cell", "cost"], [3, "cost", "showIcon"], [1, "cell", "timestamp"], [1, "cell", "actions"], ["title", "View Details", 1, "icon-btn", 3, "click"], [1, "fa-solid", "fa-eye"], ["title", "Cancel", 1, "icon-btn", "danger"], ["title", "Re-run", 1, "icon-btn"], [1, "progress-fill"], ["title", "Cancel", 1, "icon-btn", "danger", 3, "click"], [1, "fa-solid", "fa-stop"], ["title", "Re-run", 1, "icon-btn", 3, "click"], [1, "fa-solid", "fa-redo"]], template: function TestingExecutionComponent_Template(rf, ctx) { if (rf & 1) {
401
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2)(3, "div", 3);
402
+ i0.ɵɵelement(4, "i", 4);
317
403
  i0.ɵɵelementEnd();
318
- i0.ɵɵtemplate(6, TestingExecutionComponent_div_6_Template, 4, 0, "div", 4);
319
- i0.ɵɵpipe(7, "async");
404
+ i0.ɵɵelementStart(5, "div", 5)(6, "h2");
405
+ i0.ɵɵtext(7, "Test Execution Monitor");
320
406
  i0.ɵɵelementEnd();
321
- i0.ɵɵelementStart(8, "div", 5)(9, "button", 6);
322
- i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_9_listener() { return ctx.refresh(); });
323
- i0.ɵɵelement(10, "i", 7);
324
- i0.ɵɵtext(11, " Refresh ");
407
+ i0.ɵɵelementStart(8, "div", 6)(9, "span", 7);
408
+ i0.ɵɵelement(10, "i", 8);
409
+ i0.ɵɵtext(11);
410
+ i0.ɵɵpipe(12, "date");
325
411
  i0.ɵɵelementEnd();
326
- i0.ɵɵelementStart(12, "button", 8);
327
- i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_12_listener() { return ctx.startNewTest(); });
328
- i0.ɵɵelement(13, "i", 9);
329
- i0.ɵɵtext(14, " Run Test ");
412
+ i0.ɵɵtemplate(13, TestingExecutionComponent_Conditional_13_Template, 4, 0, "div", 9);
413
+ i0.ɵɵpipe(14, "async");
330
414
  i0.ɵɵelementEnd()()();
331
- i0.ɵɵelementStart(15, "div", 10)(16, "div", 11)(17, "label");
332
- i0.ɵɵtext(18, "Status");
333
- i0.ɵɵelementEnd();
334
- i0.ɵɵelementStart(19, "select", 12);
335
- i0.ɵɵtwoWayListener("ngModelChange", function TestingExecutionComponent_Template_select_ngModelChange_19_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filters.status, $event) || (ctx.filters.status = $event); return $event; });
336
- i0.ɵɵlistener("change", function TestingExecutionComponent_Template_select_change_19_listener() { return ctx.onFilterChange(); });
337
- i0.ɵɵelementStart(20, "option", 13);
338
- i0.ɵɵtext(21, "All Statuses");
339
- i0.ɵɵelementEnd();
340
- i0.ɵɵelementStart(22, "option", 14);
341
- i0.ɵɵtext(23, "Running");
342
- i0.ɵɵelementEnd();
343
- i0.ɵɵelementStart(24, "option", 15);
344
- i0.ɵɵtext(25, "Completed");
415
+ i0.ɵɵelementStart(15, "div", 10)(16, "button", 11);
416
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_16_listener() { return ctx.refresh(); });
417
+ i0.ɵɵelement(17, "i", 12);
418
+ i0.ɵɵelementStart(18, "span");
419
+ i0.ɵɵtext(19, "Refresh");
420
+ i0.ɵɵelementEnd()();
421
+ i0.ɵɵelementStart(20, "button", 13);
422
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_20_listener() { return ctx.startNewTest(); });
423
+ i0.ɵɵelement(21, "i", 14);
424
+ i0.ɵɵelementStart(22, "span");
425
+ i0.ɵɵtext(23, "Run Test");
426
+ i0.ɵɵelementEnd()()()();
427
+ i0.ɵɵelementStart(24, "div", 15)(25, "div", 16)(26, "button", 17);
428
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_26_listener() { ctx.filters.status = "all"; return ctx.onFilterChange(); });
429
+ i0.ɵɵtext(27, " All ");
345
430
  i0.ɵɵelementEnd();
346
- i0.ɵɵelementStart(26, "option", 16);
347
- i0.ɵɵtext(27, "Failed");
431
+ i0.ɵɵelementStart(28, "button", 18);
432
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_28_listener() { ctx.filters.status = "running"; return ctx.onFilterChange(); });
433
+ i0.ɵɵelement(29, "i", 19);
434
+ i0.ɵɵtext(30, " Running ");
348
435
  i0.ɵɵelementEnd();
349
- i0.ɵɵelementStart(28, "option", 17);
350
- i0.ɵɵtext(29, "Passed");
351
- i0.ɵɵelementEnd()()();
352
- i0.ɵɵelementStart(30, "div", 11)(31, "label");
353
- i0.ɵɵtext(32, "Time Range");
436
+ i0.ɵɵelementStart(31, "button", 20);
437
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_31_listener() { ctx.filters.status = "passed"; return ctx.onFilterChange(); });
438
+ i0.ɵɵelement(32, "i", 21);
439
+ i0.ɵɵtext(33, " Passed ");
354
440
  i0.ɵɵelementEnd();
355
- i0.ɵɵelementStart(33, "select", 12);
356
- i0.ɵɵtwoWayListener("ngModelChange", function TestingExecutionComponent_Template_select_ngModelChange_33_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filters.timeRange, $event) || (ctx.filters.timeRange = $event); return $event; });
357
- i0.ɵɵlistener("change", function TestingExecutionComponent_Template_select_change_33_listener() { return ctx.onFilterChange(); });
358
- i0.ɵɵelementStart(34, "option", 18);
359
- i0.ɵɵtext(35, "Today");
441
+ i0.ɵɵelementStart(34, "button", 22);
442
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_button_click_34_listener() { ctx.filters.status = "failed"; return ctx.onFilterChange(); });
443
+ i0.ɵɵelement(35, "i", 23);
444
+ i0.ɵɵtext(36, " Failed ");
445
+ i0.ɵɵelementEnd()();
446
+ i0.ɵɵelementStart(37, "div", 24)(38, "div", 25)(39, "select", 26);
447
+ i0.ɵɵtwoWayListener("ngModelChange", function TestingExecutionComponent_Template_select_ngModelChange_39_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filters.timeRange, $event) || (ctx.filters.timeRange = $event); return $event; });
448
+ i0.ɵɵlistener("change", function TestingExecutionComponent_Template_select_change_39_listener() { return ctx.onFilterChange(); });
449
+ i0.ɵɵelementStart(40, "option", 27);
450
+ i0.ɵɵtext(41, "Today");
360
451
  i0.ɵɵelementEnd();
361
- i0.ɵɵelementStart(36, "option", 19);
362
- i0.ɵɵtext(37, "This Week");
452
+ i0.ɵɵelementStart(42, "option", 28);
453
+ i0.ɵɵtext(43, "This Week");
363
454
  i0.ɵɵelementEnd();
364
- i0.ɵɵelementStart(38, "option", 20);
365
- i0.ɵɵtext(39, "This Month");
455
+ i0.ɵɵelementStart(44, "option", 29);
456
+ i0.ɵɵtext(45, "This Month");
366
457
  i0.ɵɵelementEnd();
367
- i0.ɵɵelementStart(40, "option", 13);
368
- i0.ɵɵtext(41, "All Time");
458
+ i0.ɵɵelementStart(46, "option", 30);
459
+ i0.ɵɵtext(47, "All Time");
369
460
  i0.ɵɵelementEnd()()();
370
- i0.ɵɵelementStart(42, "div", 21)(43, "label");
371
- i0.ɵɵtext(44, "Search");
372
- i0.ɵɵelementEnd();
373
- i0.ɵɵelementStart(45, "div", 22);
374
- i0.ɵɵelement(46, "i", 23);
375
- i0.ɵɵelementStart(47, "input", 24);
376
- i0.ɵɵtwoWayListener("ngModelChange", function TestingExecutionComponent_Template_input_ngModelChange_47_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filters.searchText, $event) || (ctx.filters.searchText = $event); return $event; });
377
- i0.ɵɵlistener("input", function TestingExecutionComponent_Template_input_input_47_listener() { return ctx.onFilterChange(); });
461
+ i0.ɵɵelementStart(48, "div", 31);
462
+ i0.ɵɵelement(49, "i", 32);
463
+ i0.ɵɵelementStart(50, "input", 33);
464
+ i0.ɵɵtwoWayListener("ngModelChange", function TestingExecutionComponent_Template_input_ngModelChange_50_listener($event) { i0.ɵɵtwoWayBindingSet(ctx.filters.searchText, $event) || (ctx.filters.searchText = $event); return $event; });
465
+ i0.ɵɵlistener("input", function TestingExecutionComponent_Template_input_input_50_listener() { return ctx.onFilterChange(); });
378
466
  i0.ɵɵelementEnd();
379
- i0.ɵɵtemplate(48, TestingExecutionComponent_button_48_Template, 2, 0, "button", 25);
467
+ i0.ɵɵtemplate(51, TestingExecutionComponent_Conditional_51_Template, 2, 0, "button", 34);
380
468
  i0.ɵɵelementEnd()()();
381
- i0.ɵɵelementStart(49, "div", 26)(50, "div", 27)(51, "div", 28);
382
- i0.ɵɵelement(52, "i", 29);
469
+ i0.ɵɵelementStart(52, "div", 35)(53, "div", 36);
470
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_div_click_53_listener() { return ctx.filterByStatus("running"); });
471
+ i0.ɵɵelementStart(54, "div", 37);
472
+ i0.ɵɵelement(55, "i", 19);
383
473
  i0.ɵɵelementEnd();
384
- i0.ɵɵelementStart(53, "div", 30)(54, "div", 31);
385
- i0.ɵɵtext(55);
386
- i0.ɵɵpipe(56, "async");
474
+ i0.ɵɵelementStart(56, "div", 38)(57, "div", 39);
475
+ i0.ɵɵtext(58);
476
+ i0.ɵɵpipe(59, "async");
387
477
  i0.ɵɵelementEnd();
388
- i0.ɵɵelementStart(57, "div", 32);
389
- i0.ɵɵtext(58, "Running Now");
390
- i0.ɵɵelementEnd()()();
391
- i0.ɵɵelementStart(59, "div", 27)(60, "div", 33);
392
- i0.ɵɵelement(61, "i", 34);
478
+ i0.ɵɵelementStart(60, "div", 40);
479
+ i0.ɵɵtext(61, "Running Now");
480
+ i0.ɵɵelementEnd()();
481
+ i0.ɵɵelementStart(62, "div", 41);
482
+ i0.ɵɵelement(63, "i", 42);
483
+ i0.ɵɵelementEnd()();
484
+ i0.ɵɵelementStart(64, "div", 43);
485
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_div_click_64_listener() { return ctx.filterByStatus("passed"); });
486
+ i0.ɵɵelementStart(65, "div", 37);
487
+ i0.ɵɵelement(66, "i", 44);
393
488
  i0.ɵɵelementEnd();
394
- i0.ɵɵelementStart(62, "div", 30)(63, "div", 31);
395
- i0.ɵɵtext(64);
396
- i0.ɵɵpipe(65, "async");
489
+ i0.ɵɵelementStart(67, "div", 38)(68, "div", 39);
490
+ i0.ɵɵtext(69);
491
+ i0.ɵɵpipe(70, "async");
397
492
  i0.ɵɵelementEnd();
398
- i0.ɵɵelementStart(66, "div", 32);
399
- i0.ɵɵtext(67, "Completed Today");
400
- i0.ɵɵelementEnd()()();
401
- i0.ɵɵelementStart(68, "div", 27)(69, "div", 35);
402
- i0.ɵɵelement(70, "i", 36);
493
+ i0.ɵɵelementStart(71, "div", 40);
494
+ i0.ɵɵtext(72);
495
+ i0.ɵɵelementEnd()();
496
+ i0.ɵɵelementStart(73, "div", 41);
497
+ i0.ɵɵelement(74, "i", 42);
498
+ i0.ɵɵelementEnd()();
499
+ i0.ɵɵelementStart(75, "div", 45);
500
+ i0.ɵɵlistener("click", function TestingExecutionComponent_Template_div_click_75_listener() { return ctx.filterByStatus("failed"); });
501
+ i0.ɵɵelementStart(76, "div", 37);
502
+ i0.ɵɵelement(77, "i", 46);
403
503
  i0.ɵɵelementEnd();
404
- i0.ɵɵelementStart(71, "div", 30)(72, "div", 31);
405
- i0.ɵɵtext(73);
406
- i0.ɵɵpipe(74, "async");
504
+ i0.ɵɵelementStart(78, "div", 38)(79, "div", 39);
505
+ i0.ɵɵtext(80);
506
+ i0.ɵɵpipe(81, "async");
407
507
  i0.ɵɵelementEnd();
408
- i0.ɵɵelementStart(75, "div", 32);
409
- i0.ɵɵtext(76, "Failed Today");
410
- i0.ɵɵelementEnd()()();
411
- i0.ɵɵelementStart(77, "div", 27)(78, "div", 37);
412
- i0.ɵɵelement(79, "i", 38);
508
+ i0.ɵɵelementStart(82, "div", 40);
509
+ i0.ɵɵtext(83);
510
+ i0.ɵɵelementEnd()();
511
+ i0.ɵɵelementStart(84, "div", 41);
512
+ i0.ɵɵelement(85, "i", 42);
513
+ i0.ɵɵelementEnd()();
514
+ i0.ɵɵelementStart(86, "div", 47)(87, "div", 37);
515
+ i0.ɵɵelement(88, "i", 8);
413
516
  i0.ɵɵelementEnd();
414
- i0.ɵɵelementStart(80, "div", 30)(81, "div", 31);
415
- i0.ɵɵtext(82);
416
- i0.ɵɵpipe(83, "async");
517
+ i0.ɵɵelementStart(89, "div", 38)(90, "div", 39);
518
+ i0.ɵɵtext(91);
519
+ i0.ɵɵpipe(92, "async");
417
520
  i0.ɵɵelementEnd();
418
- i0.ɵɵelementStart(84, "div", 32);
419
- i0.ɵɵtext(85, "Avg Duration Today");
521
+ i0.ɵɵelementStart(93, "div", 40);
522
+ i0.ɵɵtext(94, "Avg Duration");
420
523
  i0.ɵɵelementEnd()()()();
421
- i0.ɵɵelementStart(86, "div", 39)(87, "div", 40)(88, "div", 41)(89, "div", 42);
422
- i0.ɵɵtext(90, "Test Name");
524
+ i0.ɵɵelementStart(95, "div", 48)(96, "div", 49)(97, "div", 50)(98, "div", 51);
525
+ i0.ɵɵtext(99, "Test Name");
423
526
  i0.ɵɵelementEnd();
424
- i0.ɵɵelementStart(91, "div", 43);
425
- i0.ɵɵtext(92, "Status");
527
+ i0.ɵɵelementStart(100, "div", 52);
528
+ i0.ɵɵtext(101, "Status");
426
529
  i0.ɵɵelementEnd();
427
- i0.ɵɵelementStart(93, "div", 44);
428
- i0.ɵɵtext(94, "Score");
530
+ i0.ɵɵelementStart(102, "div", 53);
531
+ i0.ɵɵtext(103, "Score");
429
532
  i0.ɵɵelementEnd();
430
- i0.ɵɵelementStart(95, "div", 45);
431
- i0.ɵɵtext(96, "Duration");
533
+ i0.ɵɵelementStart(104, "div", 54);
534
+ i0.ɵɵtext(105, "Duration");
432
535
  i0.ɵɵelementEnd();
433
- i0.ɵɵelementStart(97, "div", 46);
434
- i0.ɵɵtext(98, "Cost");
536
+ i0.ɵɵelementStart(106, "div", 55);
537
+ i0.ɵɵtext(107, "Cost");
435
538
  i0.ɵɵelementEnd();
436
- i0.ɵɵelementStart(99, "div", 47);
437
- i0.ɵɵtext(100, "Started At");
539
+ i0.ɵɵelementStart(108, "div", 56);
540
+ i0.ɵɵtext(109, "Started At");
438
541
  i0.ɵɵelementEnd();
439
- i0.ɵɵelementStart(101, "div", 48);
440
- i0.ɵɵtext(102, "Actions");
542
+ i0.ɵɵelementStart(110, "div", 57);
543
+ i0.ɵɵtext(111, "Actions");
441
544
  i0.ɵɵelementEnd()();
442
- i0.ɵɵtemplate(103, TestingExecutionComponent_Conditional_103_Template, 7, 0, "div", 49);
443
- i0.ɵɵpipe(104, "async");
444
- i0.ɵɵrepeaterCreate(105, TestingExecutionComponent_For_106_Template, 24, 17, "div", 50, _forTrack0);
445
- i0.ɵɵpipe(107, "async");
545
+ i0.ɵɵtemplate(112, TestingExecutionComponent_Conditional_112_Template, 2, 0, "div", 58);
546
+ i0.ɵɵpipe(113, "async");
547
+ i0.ɵɵtemplate(114, TestingExecutionComponent_Conditional_114_Template, 7, 0, "div", 59);
548
+ i0.ɵɵrepeaterCreate(115, TestingExecutionComponent_For_116_Template, 24, 17, "div", 60, _forTrack0);
549
+ i0.ɵɵpipe(117, "async");
446
550
  i0.ɵɵelementEnd()()();
447
551
  } if (rf & 2) {
448
552
  let tmp_11_0;
449
553
  let tmp_12_0;
450
- i0.ɵɵadvance(6);
451
- i0.ɵɵproperty("ngIf", i0.ɵɵpipeBind1(7, 13, ctx.hasRunningTests$));
554
+ let tmp_14_0;
555
+ let tmp_16_0;
556
+ let tmp_17_0;
557
+ let tmp_18_0;
558
+ i0.ɵɵadvance(11);
559
+ i0.ɵɵtextInterpolate1(" Updated ", i0.ɵɵpipeBind2(12, 23, ctx.lastUpdated, "shortTime"), " ");
560
+ i0.ɵɵadvance(2);
561
+ i0.ɵɵconditional(i0.ɵɵpipeBind1(14, 26, ctx.hasRunningTests$) ? 13 : -1);
452
562
  i0.ɵɵadvance(3);
453
563
  i0.ɵɵproperty("disabled", ctx.isRefreshing);
454
564
  i0.ɵɵadvance();
455
565
  i0.ɵɵclassProp("spinning", ctx.isRefreshing);
456
566
  i0.ɵɵadvance(9);
457
- i0.ɵɵtwoWayProperty("ngModel", ctx.filters.status);
458
- i0.ɵɵadvance(14);
567
+ i0.ɵɵclassProp("active", ctx.filters.status === "all");
568
+ i0.ɵɵadvance(2);
569
+ i0.ɵɵclassProp("active", ctx.filters.status === "running");
570
+ i0.ɵɵadvance(3);
571
+ i0.ɵɵclassProp("active", ctx.filters.status === "passed");
572
+ i0.ɵɵadvance(3);
573
+ i0.ɵɵclassProp("active", ctx.filters.status === "failed");
574
+ i0.ɵɵadvance(5);
459
575
  i0.ɵɵtwoWayProperty("ngModel", ctx.filters.timeRange);
460
- i0.ɵɵadvance(14);
576
+ i0.ɵɵadvance(11);
461
577
  i0.ɵɵtwoWayProperty("ngModel", ctx.filters.searchText);
462
578
  i0.ɵɵadvance();
463
- i0.ɵɵproperty("ngIf", ctx.filters.searchText);
579
+ i0.ɵɵconditional(ctx.filters.searchText ? 51 : -1);
464
580
  i0.ɵɵadvance(7);
465
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(56, 15, ctx.runningCount$) || 0);
466
- i0.ɵɵadvance(9);
467
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(65, 17, ctx.completedTodayCount$) || 0);
468
- i0.ɵɵadvance(9);
469
- i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(74, 19, ctx.failedTodayCount$) || 0);
470
- i0.ɵɵadvance(9);
471
- i0.ɵɵtextInterpolate(ctx.formatDuration(i0.ɵɵpipeBind1(83, 21, ctx.avgDurationToday$) || 0));
581
+ i0.ɵɵtextInterpolate((tmp_11_0 = i0.ɵɵpipeBind1(59, 28, ctx.runningCount$)) !== null && tmp_11_0 !== undefined ? tmp_11_0 : 0);
582
+ i0.ɵɵadvance(11);
583
+ i0.ɵɵtextInterpolate((tmp_12_0 = i0.ɵɵpipeBind1(70, 30, ctx.completedTodayCount$)) !== null && tmp_12_0 !== undefined ? tmp_12_0 : 0);
584
+ i0.ɵɵadvance(3);
585
+ i0.ɵɵtextInterpolate1("Passed ", ctx.getTimeRangeLabel(), "");
586
+ i0.ɵɵadvance(8);
587
+ i0.ɵɵtextInterpolate((tmp_14_0 = i0.ɵɵpipeBind1(81, 32, ctx.failedTodayCount$)) !== null && tmp_14_0 !== undefined ? tmp_14_0 : 0);
588
+ i0.ɵɵadvance(3);
589
+ i0.ɵɵtextInterpolate1("Failed ", ctx.getTimeRangeLabel(), "");
590
+ i0.ɵɵadvance(8);
591
+ i0.ɵɵtextInterpolate(ctx.formatDuration((tmp_16_0 = i0.ɵɵpipeBind1(92, 34, ctx.avgDurationToday$)) !== null && tmp_16_0 !== undefined ? tmp_16_0 : 0));
472
592
  i0.ɵɵadvance(21);
473
- i0.ɵɵconditional(((tmp_11_0 = i0.ɵɵpipeBind1(104, 23, ctx.filteredExecutions$)) == null ? null : tmp_11_0.length) === 0 ? 103 : -1);
474
- i0.ɵɵadvance(2);
475
- i0.ɵɵrepeater((tmp_12_0 = i0.ɵɵpipeBind1(107, 25, ctx.filteredExecutions$)) !== null && tmp_12_0 !== undefined ? tmp_12_0 : i0.ɵɵpureFunction0(27, _c0));
476
- } }, dependencies: [i3.NgIf, i4.NgSelectOption, i4.ɵNgSelectMultipleOption, i4.DefaultValueAccessor, i4.SelectControlValueAccessor, i4.NgControlStatus, i4.NgModel, i2.DialogContainerDirective, i5.TestStatusBadgeComponent, i5.ScoreIndicatorComponent, i5.CostDisplayComponent, i3.AsyncPipe, i3.DatePipe], styles: [".testing-execution[_ngcontent-%COMP%] {\n padding: 20px;\n height: 100%;\n overflow-y: auto;\n background: #f8f9fa;\n }\n\n .execution-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\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%] {\n display: flex;\n align-items: center;\n gap: 16px;\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 .live-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: #e3f2fd;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n color: #2196f3;\n }\n\n .pulse[_ngcontent-%COMP%] {\n width: 8px;\n height: 8px;\n background: #2196f3;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_pulse 2s infinite;\n }\n\n @keyframes _ngcontent-%COMP%_pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.2); }\n }\n\n .header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n }\n\n .action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .action-btn.refresh[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #ddd;\n color: #666;\n }\n\n .action-btn.refresh[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f5f5f5;\n }\n\n .action-btn.primary[_ngcontent-%COMP%] {\n background: #2196f3;\n color: white;\n }\n\n .action-btn.primary[_ngcontent-%COMP%]:hover {\n background: #1976d2;\n }\n\n .action-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .action-btn[_ngcontent-%COMP%] i.spinning[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_spin 1s linear infinite;\n }\n\n @keyframes _ngcontent-%COMP%_spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n .execution-filters[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n margin-bottom: 20px;\n background: white;\n padding: 16px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 150px;\n }\n\n .filter-group.search[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .filter-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n }\n\n .filter-group[_ngcontent-%COMP%] select[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n color: #333;\n background: white;\n }\n\n .search-input-wrapper[_ngcontent-%COMP%] {\n position: relative;\n display: flex;\n align-items: center;\n }\n\n .search-input-wrapper[_ngcontent-%COMP%] i.fa-search[_ngcontent-%COMP%] {\n position: absolute;\n left: 12px;\n color: #999;\n font-size: 12px;\n }\n\n .search-input-wrapper[_ngcontent-%COMP%] input[_ngcontent-%COMP%] {\n flex: 1;\n padding: 8px 40px 8px 36px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n }\n\n .clear-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n background: none;\n border: none;\n color: #999;\n cursor: pointer;\n padding: 4px;\n }\n\n .clear-btn[_ngcontent-%COMP%]:hover {\n color: #333;\n }\n\n .execution-summary[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 20px;\n }\n\n .summary-card[_ngcontent-%COMP%] {\n background: white;\n padding: 16px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .summary-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .summary-icon.running[_ngcontent-%COMP%] {\n background: #e3f2fd;\n color: #2196f3;\n }\n\n .summary-icon.completed[_ngcontent-%COMP%] {\n background: #e8f5e9;\n color: #4caf50;\n }\n\n .summary-icon.failed[_ngcontent-%COMP%] {\n background: #ffebee;\n color: #f44336;\n }\n\n .summary-icon.duration[_ngcontent-%COMP%] {\n background: #fff3e0;\n color: #ff9800;\n }\n\n .summary-content[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .summary-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: #333;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .summary-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n font-weight: 600;\n }\n\n .execution-content[_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 .execution-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n }\n\n .list-header[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 140px 100px 100px 100px 150px 100px;\n gap: 16px;\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 2px solid #e0e0e0;\n font-size: 11px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n }\n\n .execution-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 140px 100px 100px 100px 150px 100px;\n gap: 16px;\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n transition: background 0.2s ease;\n }\n\n .execution-row[_ngcontent-%COMP%]:hover {\n background: #f8f9fa;\n }\n\n .execution-row.running[_ngcontent-%COMP%] {\n background: #e3f2fd;\n }\n\n .execution-row.running[_ngcontent-%COMP%]:hover {\n background: #bbdefb;\n }\n\n .cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n font-size: 13px;\n color: #333;\n }\n\n .cell.test-name[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n\n .test-info[_ngcontent-%COMP%] .name[_ngcontent-%COMP%] {\n font-weight: 500;\n color: #333;\n }\n\n .test-info[_ngcontent-%COMP%] .suite[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #666;\n }\n\n .cell.status[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 6px;\n align-items: flex-start;\n }\n\n .progress-bar[_ngcontent-%COMP%] {\n width: 100%;\n height: 4px;\n background: #e0e0e0;\n border-radius: 2px;\n overflow: hidden;\n }\n\n .progress-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: #2196f3;\n transition: width 0.3s ease;\n }\n\n .cell.actions[_ngcontent-%COMP%] {\n gap: 8px;\n }\n\n .icon-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: #666;\n cursor: pointer;\n padding: 6px;\n border-radius: 4px;\n transition: all 0.2s ease;\n font-size: 14px;\n }\n\n .icon-btn[_ngcontent-%COMP%]:hover {\n background: #f0f0f0;\n color: #2196f3;\n }\n\n .icon-btn.danger[_ngcontent-%COMP%]:hover {\n background: #ffebee;\n color: #f44336;\n }\n\n .no-data[_ngcontent-%COMP%] {\n padding: 60px 20px;\n text-align: center;\n color: #999;\n }\n\n .no-data[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .no-data[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 14px;\n margin-bottom: 20px;\n }\n\n @media (max-width: 1200px) {\n .execution-filters[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n }\n\n .filter-group[_ngcontent-%COMP%] {\n min-width: 120px;\n }\n }"], changeDetection: 0 });
593
+ i0.ɵɵconditional(ctx.isLoading ? 112 : ((tmp_17_0 = i0.ɵɵpipeBind1(113, 36, ctx.filteredExecutions$)) == null ? null : tmp_17_0.length) === 0 ? 114 : -1);
594
+ i0.ɵɵadvance(3);
595
+ i0.ɵɵrepeater((tmp_18_0 = i0.ɵɵpipeBind1(117, 38, ctx.filteredExecutions$)) !== null && tmp_18_0 !== undefined ? tmp_18_0 : i0.ɵɵpureFunction0(40, _c0));
596
+ } }, dependencies: [i3.NgSelectOption, i3.ɵNgSelectMultipleOption, i3.DefaultValueAccessor, i3.SelectControlValueAccessor, i3.NgControlStatus, i3.NgModel, i2.DialogContainerDirective, i4.TestStatusBadgeComponent, i4.ScoreIndicatorComponent, i4.CostDisplayComponent, i5.LoadingComponent, i6.AsyncPipe, i6.DatePipe], styles: ["\n\n\n\n\n .testing-execution[_ngcontent-%COMP%] {\n padding: 24px;\n height: 100%;\n overflow-y: auto;\n background: linear-gradient(135deg, #f8fafc 0%, #eef2f7 100%);\n }\n\n \n\n .execution-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n padding: 24px 28px;\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(99, 102, 241, 0.25);\n }\n\n .header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .header-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 22px;\n color: white;\n }\n\n .header-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 20px;\n font-weight: 600;\n color: white;\n }\n\n .header-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .last-updated[_ngcontent-%COMP%] {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .last-updated[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .live-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n color: white;\n }\n\n .pulse[_ngcontent-%COMP%] {\n width: 8px;\n height: 8px;\n background: #22c55e;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_pulse 2s infinite;\n box-shadow: 0 0 8px #22c55e;\n }\n\n @keyframes _ngcontent-%COMP%_pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.3); }\n }\n\n .header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n }\n\n .action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: none;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .refresh-btn[_ngcontent-%COMP%] {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n border: 1px solid rgba(255, 255, 255, 0.25);\n }\n\n .refresh-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.25);\n transform: translateY(-1px);\n }\n\n .primary-btn[_ngcontent-%COMP%] {\n background: white;\n color: #6366f1;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n\n .primary-btn[_ngcontent-%COMP%]:hover {\n background: #f8f9ff;\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);\n }\n\n .action-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none !important;\n }\n\n .spinning[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_spin 1s linear infinite;\n }\n\n @keyframes _ngcontent-%COMP%_spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n \n\n .filter-bar[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 16px;\n margin-bottom: 20px;\n padding: 16px 20px;\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n }\n\n .filter-chips[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n }\n\n .filter-chip[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: #f1f5f9;\n border: 2px solid transparent;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: #64748b;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .filter-chip[_ngcontent-%COMP%]:hover {\n background: #e2e8f0;\n color: #475569;\n }\n\n .filter-chip.active[_ngcontent-%COMP%] {\n background: #6366f1;\n color: white;\n border-color: #6366f1;\n }\n\n .filter-chip.running.active[_ngcontent-%COMP%] {\n background: #3b82f6;\n border-color: #3b82f6;\n }\n\n .filter-chip.passed.active[_ngcontent-%COMP%] {\n background: #22c55e;\n border-color: #22c55e;\n }\n\n .filter-chip.failed.active[_ngcontent-%COMP%] {\n background: #ef4444;\n border-color: #ef4444;\n }\n\n .filter-chip[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n }\n\n .filter-controls[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .time-select[_ngcontent-%COMP%] select[_ngcontent-%COMP%] {\n padding: 10px 14px;\n border: 2px solid #e2e8f0;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 500;\n color: #475569;\n background: white;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .time-select[_ngcontent-%COMP%] select[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: #6366f1;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .search-input[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px;\n background: #f8fafc;\n border: 2px solid #e2e8f0;\n border-radius: 10px;\n min-width: 250px;\n transition: all 0.2s ease;\n }\n\n .search-input[_ngcontent-%COMP%]:focus-within {\n border-color: #6366f1;\n background: white;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .search-input[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #94a3b8;\n font-size: 14px;\n }\n\n .search-input[_ngcontent-%COMP%] input[_ngcontent-%COMP%] {\n flex: 1;\n border: none;\n background: transparent;\n outline: none;\n font-size: 13px;\n color: #334155;\n }\n\n .search-input[_ngcontent-%COMP%] input[_ngcontent-%COMP%]::placeholder {\n color: #94a3b8;\n }\n\n .clear-btn[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: #94a3b8;\n cursor: pointer;\n border-radius: 4px;\n transition: all 0.2s ease;\n }\n\n .clear-btn[_ngcontent-%COMP%]:hover {\n background: #e2e8f0;\n color: #64748b;\n }\n\n \n\n .kpi-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 16px;\n margin-bottom: 20px;\n }\n\n .kpi-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 20px;\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n overflow: hidden;\n }\n\n .kpi-card[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 4px;\n height: 100%;\n border-radius: 4px 0 0 4px;\n }\n\n .kpi-card.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n }\n\n .kpi-card.clickable[_ngcontent-%COMP%]:hover {\n transform: translateY(-3px);\n box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15);\n }\n\n .kpi-card.running[_ngcontent-%COMP%]::before { background: linear-gradient(180deg, #3b82f6, #60a5fa); }\n .kpi-card.passed[_ngcontent-%COMP%]::before { background: linear-gradient(180deg, #22c55e, #4ade80); }\n .kpi-card.failed[_ngcontent-%COMP%]::before { background: linear-gradient(180deg, #ef4444, #f87171); }\n .kpi-card.duration[_ngcontent-%COMP%]::before { background: linear-gradient(180deg, #8b5cf6, #a78bfa); }\n\n .kpi-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .kpi-card.running[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: rgba(59, 130, 246, 0.1);\n color: #3b82f6;\n }\n\n .kpi-card.passed[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: rgba(34, 197, 94, 0.1);\n color: #22c55e;\n }\n\n .kpi-card.failed[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: rgba(239, 68, 68, 0.1);\n color: #ef4444;\n }\n\n .kpi-card.duration[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: rgba(139, 92, 246, 0.1);\n color: #8b5cf6;\n }\n\n .kpi-content[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .kpi-value[_ngcontent-%COMP%] {\n font-size: 26px;\n font-weight: 700;\n color: #1e293b;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .kpi-label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-arrow[_ngcontent-%COMP%] {\n color: #cbd5e1;\n font-size: 14px;\n opacity: 0;\n transition: all 0.3s ease;\n }\n\n .kpi-card.clickable[_ngcontent-%COMP%]:hover .kpi-arrow[_ngcontent-%COMP%] {\n opacity: 1;\n color: #6366f1;\n transform: translateX(4px);\n }\n\n \n\n .execution-content[_ngcontent-%COMP%] {\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n overflow: hidden;\n }\n\n .execution-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n }\n\n \n\n .list-header[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 120px 100px 100px 140px 100px;\n gap: 20px;\n padding: 16px 24px;\n background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);\n border-bottom: 1px solid #e2e8f0;\n font-size: 11px;\n font-weight: 700;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .header-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n }\n\n \n\n .execution-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 120px 100px 100px 140px 100px;\n gap: 20px;\n padding: 18px 24px;\n border-bottom: 1px solid #f1f5f9;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .execution-row[_ngcontent-%COMP%]:last-child {\n border-bottom: none;\n }\n\n .execution-row[_ngcontent-%COMP%]:hover {\n background: linear-gradient(90deg, rgba(99, 102, 241, 0.03) 0%, rgba(139, 92, 246, 0.03) 100%);\n }\n\n .execution-row.running[_ngcontent-%COMP%] {\n background: linear-gradient(90deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.04) 100%);\n border-left: 3px solid #3b82f6;\n }\n\n .execution-row.running[_ngcontent-%COMP%]:hover {\n background: linear-gradient(90deg, rgba(59, 130, 246, 0.12) 0%, rgba(59, 130, 246, 0.06) 100%);\n }\n\n \n\n .cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n font-size: 13px;\n color: #334155;\n }\n\n .cell.test-name[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n\n .test-info[_ngcontent-%COMP%] .name[_ngcontent-%COMP%] {\n font-weight: 600;\n color: #1e293b;\n font-size: 14px;\n }\n\n .test-info[_ngcontent-%COMP%] .suite[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #64748b;\n }\n\n .cell.status[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 8px;\n align-items: flex-start;\n }\n\n .progress-bar[_ngcontent-%COMP%] {\n width: 100%;\n height: 4px;\n background: #e2e8f0;\n border-radius: 2px;\n overflow: hidden;\n }\n\n .progress-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: linear-gradient(90deg, #3b82f6, #60a5fa);\n border-radius: 2px;\n transition: width 0.3s ease;\n }\n\n .cell.actions[_ngcontent-%COMP%] {\n gap: 8px;\n justify-content: flex-end;\n }\n\n \n\n .icon-btn[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n color: #64748b;\n cursor: pointer;\n border-radius: 10px;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n font-size: 14px;\n }\n\n .icon-btn[_ngcontent-%COMP%]:hover {\n background: #6366f1;\n border-color: #6366f1;\n color: white;\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n }\n\n .icon-btn.danger[_ngcontent-%COMP%]:hover {\n background: #ef4444;\n border-color: #ef4444;\n box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);\n }\n\n \n\n .loading-placeholder[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 80px 40px;\n background: linear-gradient(180deg, #fafbff 0%, #f8fafc 100%);\n }\n\n \n\n .no-data[_ngcontent-%COMP%] {\n padding: 80px 40px;\n text-align: center;\n background: linear-gradient(180deg, #fafbff 0%, #f8fafc 100%);\n }\n\n .no-data[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 64px;\n color: #cbd5e1;\n margin-bottom: 20px;\n }\n\n .no-data[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 16px;\n color: #64748b;\n margin-bottom: 24px;\n }\n\n .no-data[_ngcontent-%COMP%] .action-btn[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n color: white;\n padding: 12px 24px;\n border-radius: 12px;\n font-weight: 600;\n box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);\n }\n\n .no-data[_ngcontent-%COMP%] .action-btn[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 24px rgba(99, 102, 241, 0.4);\n }\n\n \n\n @media (max-width: 1400px) {\n .kpi-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n @media (max-width: 1200px) {\n .filter-bar[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: stretch;\n }\n\n .filter-chips[_ngcontent-%COMP%] {\n justify-content: center;\n }\n\n .filter-controls[_ngcontent-%COMP%] {\n justify-content: center;\n }\n\n .list-header[_ngcontent-%COMP%], \n .execution-row[_ngcontent-%COMP%] {\n grid-template-columns: 1fr 100px 80px 100px;\n }\n\n .header-cell.cost[_ngcontent-%COMP%], \n .header-cell.timestamp[_ngcontent-%COMP%], \n .cell.cost[_ngcontent-%COMP%], \n .cell.timestamp[_ngcontent-%COMP%] {\n display: none;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .search-input[_ngcontent-%COMP%] {\n min-width: 200px;\n }\n }"], changeDetection: 0 });
477
597
  }
478
598
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestingExecutionComponent, [{
479
599
  type: Component,
480
600
  args: [{ selector: 'app-testing-execution', changeDetection: ChangeDetectionStrategy.OnPush, template: `
481
601
  <div class="testing-execution" kendoDialogContainer>
602
+ <!-- Premium Header with Gradient -->
482
603
  <div class="execution-header">
483
604
  <div class="header-left">
484
- <h2>
605
+ <div class="header-icon">
485
606
  <i class="fa-solid fa-play-circle"></i>
486
- Test Execution Monitor
487
- </h2>
488
- <div class="live-indicator" *ngIf="hasRunningTests$ | async">
489
- <span class="pulse"></span>
490
- <span class="text">Live</span>
607
+ </div>
608
+ <div class="header-text">
609
+ <h2>Test Execution Monitor</h2>
610
+ <div class="header-meta">
611
+ <span class="last-updated">
612
+ <i class="fa-solid fa-clock"></i>
613
+ Updated {{ lastUpdated | date:'shortTime' }}
614
+ </span>
615
+ @if (hasRunningTests$ | async) {
616
+ <div class="live-indicator">
617
+ <span class="pulse"></span>
618
+ <span class="text">Live</span>
619
+ </div>
620
+ }
621
+ </div>
491
622
  </div>
492
623
  </div>
493
624
  <div class="header-actions">
494
- <button class="action-btn refresh" (click)="refresh()" [disabled]="isRefreshing">
495
- <i class="fa-solid fa-refresh" [class.spinning]="isRefreshing"></i>
496
- Refresh
625
+ <button class="action-btn refresh-btn" (click)="refresh()" [disabled]="isRefreshing">
626
+ <i class="fa-solid fa-sync-alt" [class.spinning]="isRefreshing"></i>
627
+ <span>Refresh</span>
497
628
  </button>
498
- <button class="action-btn primary" (click)="startNewTest()">
629
+ <button class="action-btn primary-btn" (click)="startNewTest()">
499
630
  <i class="fa-solid fa-play"></i>
500
- Run Test
631
+ <span>Run Test</span>
501
632
  </button>
502
633
  </div>
503
634
  </div>
504
635
 
505
- <div class="execution-filters">
506
- <div class="filter-group">
507
- <label>Status</label>
508
- <select [(ngModel)]="filters.status" (change)="onFilterChange()">
509
- <option value="all">All Statuses</option>
510
- <option value="running">Running</option>
511
- <option value="completed">Completed</option>
512
- <option value="failed">Failed</option>
513
- <option value="passed">Passed</option>
514
- </select>
515
- </div>
516
- <div class="filter-group">
517
- <label>Time Range</label>
518
- <select [(ngModel)]="filters.timeRange" (change)="onFilterChange()">
519
- <option value="today">Today</option>
520
- <option value="week">This Week</option>
521
- <option value="month">This Month</option>
522
- <option value="all">All Time</option>
523
- </select>
636
+ <!-- Smart Filter Bar -->
637
+ <div class="filter-bar">
638
+ <div class="filter-chips">
639
+ <button
640
+ class="filter-chip"
641
+ [class.active]="filters.status === 'all'"
642
+ (click)="filters.status = 'all'; onFilterChange()"
643
+ >
644
+ All
645
+ </button>
646
+ <button
647
+ class="filter-chip running"
648
+ [class.active]="filters.status === 'running'"
649
+ (click)="filters.status = 'running'; onFilterChange()"
650
+ >
651
+ <i class="fa-solid fa-spinner fa-spin"></i>
652
+ Running
653
+ </button>
654
+ <button
655
+ class="filter-chip passed"
656
+ [class.active]="filters.status === 'passed'"
657
+ (click)="filters.status = 'passed'; onFilterChange()"
658
+ >
659
+ <i class="fa-solid fa-check"></i>
660
+ Passed
661
+ </button>
662
+ <button
663
+ class="filter-chip failed"
664
+ [class.active]="filters.status === 'failed'"
665
+ (click)="filters.status = 'failed'; onFilterChange()"
666
+ >
667
+ <i class="fa-solid fa-times"></i>
668
+ Failed
669
+ </button>
524
670
  </div>
525
- <div class="filter-group search">
526
- <label>Search</label>
527
- <div class="search-input-wrapper">
671
+
672
+ <div class="filter-controls">
673
+ <div class="time-select">
674
+ <select [(ngModel)]="filters.timeRange" (change)="onFilterChange()">
675
+ <option value="today">Today</option>
676
+ <option value="week">This Week</option>
677
+ <option value="month">This Month</option>
678
+ <option value="all">All Time</option>
679
+ </select>
680
+ </div>
681
+
682
+ <div class="search-input">
528
683
  <i class="fa-solid fa-search"></i>
529
684
  <input
530
685
  type="text"
@@ -532,52 +687,72 @@ export class TestingExecutionComponent {
532
687
  (input)="onFilterChange()"
533
688
  placeholder="Search tests..."
534
689
  />
535
- <button
536
- class="clear-btn"
537
- *ngIf="filters.searchText"
538
- (click)="clearSearch()"
539
- >
540
- <i class="fa-solid fa-times"></i>
541
- </button>
690
+ @if (filters.searchText) {
691
+ <button class="clear-btn" (click)="clearSearch()">
692
+ <i class="fa-solid fa-times"></i>
693
+ </button>
694
+ }
542
695
  </div>
543
696
  </div>
544
697
  </div>
545
698
 
546
- <div class="execution-summary">
547
- <div class="summary-card">
548
- <div class="summary-icon running">
699
+ <!-- KPI Cards (Actionable) -->
700
+ <div class="kpi-grid">
701
+ <div
702
+ class="kpi-card running clickable"
703
+ (click)="filterByStatus('running')"
704
+ >
705
+ <div class="kpi-icon">
549
706
  <i class="fa-solid fa-spinner fa-spin"></i>
550
707
  </div>
551
- <div class="summary-content">
552
- <div class="summary-value">{{ (runningCount$ | async) || 0 }}</div>
553
- <div class="summary-label">Running Now</div>
708
+ <div class="kpi-content">
709
+ <div class="kpi-value">{{ (runningCount$ | async) ?? 0 }}</div>
710
+ <div class="kpi-label">Running Now</div>
711
+ </div>
712
+ <div class="kpi-arrow">
713
+ <i class="fa-solid fa-chevron-right"></i>
554
714
  </div>
555
715
  </div>
556
- <div class="summary-card">
557
- <div class="summary-icon completed">
716
+
717
+ <div
718
+ class="kpi-card passed clickable"
719
+ (click)="filterByStatus('passed')"
720
+ >
721
+ <div class="kpi-icon">
558
722
  <i class="fa-solid fa-check-circle"></i>
559
723
  </div>
560
- <div class="summary-content">
561
- <div class="summary-value">{{ (completedTodayCount$ | async) || 0 }}</div>
562
- <div class="summary-label">Completed Today</div>
724
+ <div class="kpi-content">
725
+ <div class="kpi-value">{{ (completedTodayCount$ | async) ?? 0 }}</div>
726
+ <div class="kpi-label">Passed {{ getTimeRangeLabel() }}</div>
727
+ </div>
728
+ <div class="kpi-arrow">
729
+ <i class="fa-solid fa-chevron-right"></i>
563
730
  </div>
564
731
  </div>
565
- <div class="summary-card">
566
- <div class="summary-icon failed">
732
+
733
+ <div
734
+ class="kpi-card failed clickable"
735
+ (click)="filterByStatus('failed')"
736
+ >
737
+ <div class="kpi-icon">
567
738
  <i class="fa-solid fa-exclamation-circle"></i>
568
739
  </div>
569
- <div class="summary-content">
570
- <div class="summary-value">{{ (failedTodayCount$ | async) || 0 }}</div>
571
- <div class="summary-label">Failed Today</div>
740
+ <div class="kpi-content">
741
+ <div class="kpi-value">{{ (failedTodayCount$ | async) ?? 0 }}</div>
742
+ <div class="kpi-label">Failed {{ getTimeRangeLabel() }}</div>
743
+ </div>
744
+ <div class="kpi-arrow">
745
+ <i class="fa-solid fa-chevron-right"></i>
572
746
  </div>
573
747
  </div>
574
- <div class="summary-card">
575
- <div class="summary-icon duration">
748
+
749
+ <div class="kpi-card duration">
750
+ <div class="kpi-icon">
576
751
  <i class="fa-solid fa-clock"></i>
577
752
  </div>
578
- <div class="summary-content">
579
- <div class="summary-value">{{ formatDuration((avgDurationToday$ | async) || 0) }}</div>
580
- <div class="summary-label">Avg Duration Today</div>
753
+ <div class="kpi-content">
754
+ <div class="kpi-value">{{ formatDuration((avgDurationToday$ | async) ?? 0) }}</div>
755
+ <div class="kpi-label">Avg Duration</div>
581
756
  </div>
582
757
  </div>
583
758
  </div>
@@ -594,7 +769,11 @@ export class TestingExecutionComponent {
594
769
  <div class="header-cell actions">Actions</div>
595
770
  </div>
596
771
 
597
- @if ((filteredExecutions$ | async)?.length === 0) {
772
+ @if (isLoading) {
773
+ <div class="loading-placeholder">
774
+ <mj-loading text="Loading test executions..."></mj-loading>
775
+ </div>
776
+ } @else if ((filteredExecutions$ | async)?.length === 0) {
598
777
  <div class="no-data">
599
778
  <i class="fa-solid fa-inbox"></i>
600
779
  <p>No test executions found</p>
@@ -656,11 +835,11 @@ export class TestingExecutionComponent {
656
835
  </div>
657
836
  </div>
658
837
  </div>
659
- `, styles: ["\n .testing-execution {\n padding: 20px;\n height: 100%;\n overflow-y: auto;\n background: #f8f9fa;\n }\n\n .execution-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\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 {\n display: flex;\n align-items: center;\n gap: 16px;\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 .live-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: #e3f2fd;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n color: #2196f3;\n }\n\n .pulse {\n width: 8px;\n height: 8px;\n background: #2196f3;\n border-radius: 50%;\n animation: pulse 2s infinite;\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.2); }\n }\n\n .header-actions {\n display: flex;\n gap: 12px;\n }\n\n .action-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n border: none;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .action-btn.refresh {\n background: white;\n border: 1px solid #ddd;\n color: #666;\n }\n\n .action-btn.refresh:hover:not(:disabled) {\n background: #f5f5f5;\n }\n\n .action-btn.primary {\n background: #2196f3;\n color: white;\n }\n\n .action-btn.primary:hover {\n background: #1976d2;\n }\n\n .action-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .action-btn i.spinning {\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n .execution-filters {\n display: flex;\n gap: 16px;\n margin-bottom: 20px;\n background: white;\n padding: 16px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n }\n\n .filter-group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 150px;\n }\n\n .filter-group.search {\n flex: 1;\n }\n\n .filter-group label {\n font-size: 11px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n }\n\n .filter-group select {\n padding: 8px 12px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n color: #333;\n background: white;\n }\n\n .search-input-wrapper {\n position: relative;\n display: flex;\n align-items: center;\n }\n\n .search-input-wrapper i.fa-search {\n position: absolute;\n left: 12px;\n color: #999;\n font-size: 12px;\n }\n\n .search-input-wrapper input {\n flex: 1;\n padding: 8px 40px 8px 36px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 13px;\n }\n\n .clear-btn {\n position: absolute;\n right: 8px;\n background: none;\n border: none;\n color: #999;\n cursor: pointer;\n padding: 4px;\n }\n\n .clear-btn:hover {\n color: #333;\n }\n\n .execution-summary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 20px;\n }\n\n .summary-card {\n background: white;\n padding: 16px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .summary-icon {\n width: 48px;\n height: 48px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .summary-icon.running {\n background: #e3f2fd;\n color: #2196f3;\n }\n\n .summary-icon.completed {\n background: #e8f5e9;\n color: #4caf50;\n }\n\n .summary-icon.failed {\n background: #ffebee;\n color: #f44336;\n }\n\n .summary-icon.duration {\n background: #fff3e0;\n color: #ff9800;\n }\n\n .summary-content {\n flex: 1;\n }\n\n .summary-value {\n font-size: 24px;\n font-weight: 700;\n color: #333;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .summary-label {\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n font-weight: 600;\n }\n\n .execution-content {\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 .execution-list {\n display: flex;\n flex-direction: column;\n }\n\n .list-header {\n display: grid;\n grid-template-columns: 2fr 140px 100px 100px 100px 150px 100px;\n gap: 16px;\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 2px solid #e0e0e0;\n font-size: 11px;\n font-weight: 600;\n color: #666;\n text-transform: uppercase;\n }\n\n .execution-row {\n display: grid;\n grid-template-columns: 2fr 140px 100px 100px 100px 150px 100px;\n gap: 16px;\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n transition: background 0.2s ease;\n }\n\n .execution-row:hover {\n background: #f8f9fa;\n }\n\n .execution-row.running {\n background: #e3f2fd;\n }\n\n .execution-row.running:hover {\n background: #bbdefb;\n }\n\n .cell {\n display: flex;\n align-items: center;\n font-size: 13px;\n color: #333;\n }\n\n .cell.test-name {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n\n .test-info .name {\n font-weight: 500;\n color: #333;\n }\n\n .test-info .suite {\n font-size: 11px;\n color: #666;\n }\n\n .cell.status {\n flex-direction: column;\n gap: 6px;\n align-items: flex-start;\n }\n\n .progress-bar {\n width: 100%;\n height: 4px;\n background: #e0e0e0;\n border-radius: 2px;\n overflow: hidden;\n }\n\n .progress-fill {\n height: 100%;\n background: #2196f3;\n transition: width 0.3s ease;\n }\n\n .cell.actions {\n gap: 8px;\n }\n\n .icon-btn {\n background: none;\n border: none;\n color: #666;\n cursor: pointer;\n padding: 6px;\n border-radius: 4px;\n transition: all 0.2s ease;\n font-size: 14px;\n }\n\n .icon-btn:hover {\n background: #f0f0f0;\n color: #2196f3;\n }\n\n .icon-btn.danger:hover {\n background: #ffebee;\n color: #f44336;\n }\n\n .no-data {\n padding: 60px 20px;\n text-align: center;\n color: #999;\n }\n\n .no-data i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .no-data p {\n font-size: 14px;\n margin-bottom: 20px;\n }\n\n @media (max-width: 1200px) {\n .execution-filters {\n flex-wrap: wrap;\n }\n\n .filter-group {\n min-width: 120px;\n }\n }\n "] }]
660
- }], () => [{ type: i1.TestingInstrumentationService }, { type: i2.DialogService }, { type: i0.ChangeDetectorRef }], { initialState: [{
838
+ `, styles: ["\n /* ============================================\n Testing Execution - Premium Design System\n ============================================ */\n\n .testing-execution {\n padding: 24px;\n height: 100%;\n overflow-y: auto;\n background: linear-gradient(135deg, #f8fafc 0%, #eef2f7 100%);\n }\n\n /* Premium Header */\n .execution-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n padding: 24px 28px;\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(99, 102, 241, 0.25);\n }\n\n .header-left {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .header-icon {\n width: 48px;\n height: 48px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 22px;\n color: white;\n }\n\n .header-text h2 {\n margin: 0 0 4px 0;\n font-size: 20px;\n font-weight: 600;\n color: white;\n }\n\n .header-meta {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .last-updated {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .last-updated i {\n font-size: 11px;\n }\n\n .live-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n color: white;\n }\n\n .pulse {\n width: 8px;\n height: 8px;\n background: #22c55e;\n border-radius: 50%;\n animation: pulse 2s infinite;\n box-shadow: 0 0 8px #22c55e;\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(1.3); }\n }\n\n .header-actions {\n display: flex;\n gap: 12px;\n }\n\n .action-btn {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: none;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .refresh-btn {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n border: 1px solid rgba(255, 255, 255, 0.25);\n }\n\n .refresh-btn:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.25);\n transform: translateY(-1px);\n }\n\n .primary-btn {\n background: white;\n color: #6366f1;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n\n .primary-btn:hover {\n background: #f8f9ff;\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);\n }\n\n .action-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none !important;\n }\n\n .spinning {\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n /* Smart Filter Bar */\n .filter-bar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 16px;\n margin-bottom: 20px;\n padding: 16px 20px;\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n }\n\n .filter-chips {\n display: flex;\n gap: 8px;\n }\n\n .filter-chip {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: #f1f5f9;\n border: 2px solid transparent;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: #64748b;\n cursor: pointer;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .filter-chip:hover {\n background: #e2e8f0;\n color: #475569;\n }\n\n .filter-chip.active {\n background: #6366f1;\n color: white;\n border-color: #6366f1;\n }\n\n .filter-chip.running.active {\n background: #3b82f6;\n border-color: #3b82f6;\n }\n\n .filter-chip.passed.active {\n background: #22c55e;\n border-color: #22c55e;\n }\n\n .filter-chip.failed.active {\n background: #ef4444;\n border-color: #ef4444;\n }\n\n .filter-chip i {\n font-size: 11px;\n }\n\n .filter-controls {\n display: flex;\n gap: 12px;\n align-items: center;\n }\n\n .time-select select {\n padding: 10px 14px;\n border: 2px solid #e2e8f0;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 500;\n color: #475569;\n background: white;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .time-select select:focus {\n outline: none;\n border-color: #6366f1;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .search-input {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px;\n background: #f8fafc;\n border: 2px solid #e2e8f0;\n border-radius: 10px;\n min-width: 250px;\n transition: all 0.2s ease;\n }\n\n .search-input:focus-within {\n border-color: #6366f1;\n background: white;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .search-input i {\n color: #94a3b8;\n font-size: 14px;\n }\n\n .search-input input {\n flex: 1;\n border: none;\n background: transparent;\n outline: none;\n font-size: 13px;\n color: #334155;\n }\n\n .search-input input::placeholder {\n color: #94a3b8;\n }\n\n .clear-btn {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: #94a3b8;\n cursor: pointer;\n border-radius: 4px;\n transition: all 0.2s ease;\n }\n\n .clear-btn:hover {\n background: #e2e8f0;\n color: #64748b;\n }\n\n /* KPI Cards Grid */\n .kpi-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 16px;\n margin-bottom: 20px;\n }\n\n .kpi-card {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 20px;\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n overflow: hidden;\n }\n\n .kpi-card::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n width: 4px;\n height: 100%;\n border-radius: 4px 0 0 4px;\n }\n\n .kpi-card.clickable {\n cursor: pointer;\n }\n\n .kpi-card.clickable:hover {\n transform: translateY(-3px);\n box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15);\n }\n\n .kpi-card.running::before { background: linear-gradient(180deg, #3b82f6, #60a5fa); }\n .kpi-card.passed::before { background: linear-gradient(180deg, #22c55e, #4ade80); }\n .kpi-card.failed::before { background: linear-gradient(180deg, #ef4444, #f87171); }\n .kpi-card.duration::before { background: linear-gradient(180deg, #8b5cf6, #a78bfa); }\n\n .kpi-icon {\n width: 48px;\n height: 48px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 20px;\n }\n\n .kpi-card.running .kpi-icon {\n background: rgba(59, 130, 246, 0.1);\n color: #3b82f6;\n }\n\n .kpi-card.passed .kpi-icon {\n background: rgba(34, 197, 94, 0.1);\n color: #22c55e;\n }\n\n .kpi-card.failed .kpi-icon {\n background: rgba(239, 68, 68, 0.1);\n color: #ef4444;\n }\n\n .kpi-card.duration .kpi-icon {\n background: rgba(139, 92, 246, 0.1);\n color: #8b5cf6;\n }\n\n .kpi-content {\n flex: 1;\n }\n\n .kpi-value {\n font-size: 26px;\n font-weight: 700;\n color: #1e293b;\n line-height: 1;\n margin-bottom: 4px;\n }\n\n .kpi-label {\n font-size: 12px;\n font-weight: 500;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-arrow {\n color: #cbd5e1;\n font-size: 14px;\n opacity: 0;\n transition: all 0.3s ease;\n }\n\n .kpi-card.clickable:hover .kpi-arrow {\n opacity: 1;\n color: #6366f1;\n transform: translateX(4px);\n }\n\n /* Execution List Container */\n .execution-content {\n background: white;\n border-radius: 14px;\n box-shadow: 0 2px 12px rgba(99, 102, 241, 0.06);\n overflow: hidden;\n }\n\n .execution-list {\n display: flex;\n flex-direction: column;\n }\n\n /* List Header */\n .list-header {\n display: grid;\n grid-template-columns: 2fr 120px 100px 100px 140px 100px;\n gap: 20px;\n padding: 16px 24px;\n background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);\n border-bottom: 1px solid #e2e8f0;\n font-size: 11px;\n font-weight: 700;\n color: #64748b;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .header-cell {\n display: flex;\n align-items: center;\n }\n\n /* Execution Row */\n .execution-row {\n display: grid;\n grid-template-columns: 2fr 120px 100px 100px 140px 100px;\n gap: 20px;\n padding: 18px 24px;\n border-bottom: 1px solid #f1f5f9;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .execution-row:last-child {\n border-bottom: none;\n }\n\n .execution-row:hover {\n background: linear-gradient(90deg, rgba(99, 102, 241, 0.03) 0%, rgba(139, 92, 246, 0.03) 100%);\n }\n\n .execution-row.running {\n background: linear-gradient(90deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.04) 100%);\n border-left: 3px solid #3b82f6;\n }\n\n .execution-row.running:hover {\n background: linear-gradient(90deg, rgba(59, 130, 246, 0.12) 0%, rgba(59, 130, 246, 0.06) 100%);\n }\n\n /* Cells */\n .cell {\n display: flex;\n align-items: center;\n font-size: 13px;\n color: #334155;\n }\n\n .cell.test-name {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n\n .test-info .name {\n font-weight: 600;\n color: #1e293b;\n font-size: 14px;\n }\n\n .test-info .suite {\n font-size: 12px;\n color: #64748b;\n }\n\n .cell.status {\n flex-direction: column;\n gap: 8px;\n align-items: flex-start;\n }\n\n .progress-bar {\n width: 100%;\n height: 4px;\n background: #e2e8f0;\n border-radius: 2px;\n overflow: hidden;\n }\n\n .progress-fill {\n height: 100%;\n background: linear-gradient(90deg, #3b82f6, #60a5fa);\n border-radius: 2px;\n transition: width 0.3s ease;\n }\n\n .cell.actions {\n gap: 8px;\n justify-content: flex-end;\n }\n\n /* Action Buttons */\n .icon-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n color: #64748b;\n cursor: pointer;\n border-radius: 10px;\n transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n font-size: 14px;\n }\n\n .icon-btn:hover {\n background: #6366f1;\n border-color: #6366f1;\n color: white;\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n }\n\n .icon-btn.danger:hover {\n background: #ef4444;\n border-color: #ef4444;\n box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);\n }\n\n /* Loading State */\n .loading-placeholder {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 80px 40px;\n background: linear-gradient(180deg, #fafbff 0%, #f8fafc 100%);\n }\n\n /* Empty State */\n .no-data {\n padding: 80px 40px;\n text-align: center;\n background: linear-gradient(180deg, #fafbff 0%, #f8fafc 100%);\n }\n\n .no-data i {\n font-size: 64px;\n color: #cbd5e1;\n margin-bottom: 20px;\n }\n\n .no-data p {\n font-size: 16px;\n color: #64748b;\n margin-bottom: 24px;\n }\n\n .no-data .action-btn {\n background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n color: white;\n padding: 12px 24px;\n border-radius: 12px;\n font-weight: 600;\n box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3);\n }\n\n .no-data .action-btn:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 24px rgba(99, 102, 241, 0.4);\n }\n\n /* Responsive */\n @media (max-width: 1400px) {\n .kpi-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n }\n\n @media (max-width: 1200px) {\n .filter-bar {\n flex-direction: column;\n align-items: stretch;\n }\n\n .filter-chips {\n justify-content: center;\n }\n\n .filter-controls {\n justify-content: center;\n }\n\n .list-header,\n .execution-row {\n grid-template-columns: 1fr 100px 80px 100px;\n }\n\n .header-cell.cost,\n .header-cell.timestamp,\n .cell.cost,\n .cell.timestamp {\n display: none;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-grid {\n grid-template-columns: 1fr;\n }\n\n .search-input {\n min-width: 200px;\n }\n }\n "] }]
839
+ }], () => [{ type: i1.TestingInstrumentationService }, { type: i2.DialogService }, { type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }], { initialState: [{
661
840
  type: Input
662
841
  }], stateChange: [{
663
842
  type: Output
664
843
  }] }); })();
665
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestingExecutionComponent, { className: "TestingExecutionComponent", filePath: "src/Testing/components/testing-execution.component.ts", lineNumber: 608 }); })();
844
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestingExecutionComponent, { className: "TestingExecutionComponent", filePath: "src/Testing/components/testing-execution.component.ts", lineNumber: 897 }); })();
666
845
  //# sourceMappingURL=testing-execution.component.js.map