@memberjunction/ng-core-entity-forms 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.
- package/dist/lib/custom/Tests/entity-link-pill.component.d.ts +44 -0
- package/dist/lib/custom/Tests/entity-link-pill.component.d.ts.map +1 -0
- package/dist/lib/custom/Tests/entity-link-pill.component.js +124 -0
- package/dist/lib/custom/Tests/entity-link-pill.component.js.map +1 -0
- package/dist/lib/custom/Tests/test-form.component.d.ts +96 -9
- package/dist/lib/custom/Tests/test-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-form.component.js +1529 -277
- package/dist/lib/custom/Tests/test-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-run-form.component.d.ts +50 -9
- package/dist/lib/custom/Tests/test-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-run-form.component.js +1079 -426
- package/dist/lib/custom/Tests/test-run-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-suite-form.component.d.ts +228 -5
- package/dist/lib/custom/Tests/test-suite-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-suite-form.component.js +3309 -201
- package/dist/lib/custom/Tests/test-suite-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts +88 -3
- package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-suite-run-form.component.js +1976 -262
- package/dist/lib/custom/Tests/test-suite-run-form.component.js.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +9 -2
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +275 -244
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -1
- package/dist/lib/custom/custom-forms.module.d.ts +27 -26
- package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
- package/dist/lib/custom/custom-forms.module.js +9 -3
- package/dist/lib/custom/custom-forms.module.js.map +1 -1
- package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js +154 -122
- package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.js +75 -0
- package/dist/lib/generated/Entities/AIAgentModality/aiagentmodality.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.js +121 -0
- package/dist/lib/generated/Entities/AIArchitecture/aiarchitecture.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js +77 -41
- package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIModality/aimodality.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/AIModality/aimodality.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/AIModality/aimodality.form.component.js +167 -0
- package/dist/lib/generated/Entities/AIModality/aimodality.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/AIModel/aimodel.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js +160 -102
- package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.js +73 -0
- package/dist/lib/generated/Entities/AIModelArchitecture/aimodelarchitecture.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.js +89 -0
- package/dist/lib/generated/Entities/AIModelModality/aimodelmodality.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js +27 -13
- package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.js +39 -21
- package/dist/lib/generated/Entities/ConversationDetail/conversationdetail.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.d.ts +11 -0
- package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.js +95 -0
- package/dist/lib/generated/Entities/ConversationDetailAttachment/conversationdetailattachment.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/Entity/entity.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/Entity/entity.form.component.js +61 -43
- package/dist/lib/generated/Entities/Entity/entity.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/File/file.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/File/file.form.component.js +22 -4
- package/dist/lib/generated/Entities/File/file.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.js +40 -4
- package/dist/lib/generated/Entities/FileStorageProvider/filestorageprovider.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/Test/test.form.component.js +17 -15
- package/dist/lib/generated/Entities/Test/test.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/TestRun/testrun.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/TestRun/testrun.form.component.js +55 -43
- package/dist/lib/generated/Entities/TestRun/testrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.js +9 -15
- package/dist/lib/generated/Entities/TestRunFeedback/testrunfeedback.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.js +39 -19
- package/dist/lib/generated/Entities/TestSuite/testsuite.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.js +40 -16
- package/dist/lib/generated/Entities/TestSuiteRun/testsuiterun.form.component.js.map +1 -1
- package/dist/lib/generated/generated-forms.module.d.ts +145 -134
- package/dist/lib/generated/generated-forms.module.d.ts.map +1 -1
- package/dist/lib/generated/generated-forms.module.js +168 -87
- package/dist/lib/generated/generated-forms.module.js.map +1 -1
- package/package.json +28 -27
|
@@ -4,22 +4,47 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
|
7
|
+
import { Component, ChangeDetectionStrategy, HostListener, ViewChild } from '@angular/core';
|
|
8
|
+
import * as d3 from 'd3';
|
|
8
9
|
import { Subject } from 'rxjs';
|
|
9
|
-
import {
|
|
10
|
+
import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
11
|
+
import { CompositeKey, Metadata, RunView } from '@memberjunction/core';
|
|
12
|
+
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
10
13
|
import { BaseFormComponent } from '@memberjunction/ng-base-forms';
|
|
11
14
|
import { RegisterClass } from '@memberjunction/global';
|
|
12
15
|
import { SharedService } from '@memberjunction/ng-shared';
|
|
13
16
|
import { TestSuiteFormComponent } from '../../generated/Entities/TestSuite/testsuite.form.component';
|
|
17
|
+
import { TagsHelper } from '@memberjunction/ng-testing';
|
|
14
18
|
import * as i0 from "@angular/core";
|
|
15
19
|
import * as i1 from "@memberjunction/ng-shared";
|
|
16
20
|
import * as i2 from "@angular/router";
|
|
17
21
|
import * as i3 from "@memberjunction/ng-testing";
|
|
18
22
|
import * as i4 from "@angular/common";
|
|
19
|
-
import * as i5 from "@
|
|
20
|
-
import * as i6 from "@progress/kendo-angular-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
import * as i5 from "@angular/forms";
|
|
24
|
+
import * as i6 from "@progress/kendo-angular-dialog";
|
|
25
|
+
import * as i7 from "@progress/kendo-angular-buttons";
|
|
26
|
+
import * as i8 from "@memberjunction/ng-shared-generic";
|
|
27
|
+
const _c0 = ["chartContainer"];
|
|
28
|
+
const _c1 = () => [1, 2, 3, 4, 5];
|
|
29
|
+
const _c2 = () => [1, 2, 3];
|
|
30
|
+
const _c3 = (a0, a1) => ({ "trend-up": a0, "trend-down": a1 });
|
|
31
|
+
const _c4 = (a0, a1, a2) => ({ "fa-arrow-up": a0, "fa-arrow-down": a1, "fa-minus": a2 });
|
|
32
|
+
const _c5 = (a0, a1, a2) => ({ "high": a0, "medium": a1, "low": a2 });
|
|
33
|
+
const _c6 = (a0, a1) => ({ "positive": a0, "negative": a1 });
|
|
34
|
+
const _c7 = (a0, a1, a2) => ({ "fa-arrow-down": a0, "fa-arrow-up": a1, "fa-minus": a2 });
|
|
35
|
+
const _c8 = (a0, a1) => ({ "improved": a0, "regressed": a1 });
|
|
36
|
+
function TestSuiteFormComponentExtended_span_13_Template(rf, ctx) { if (rf & 1) {
|
|
37
|
+
i0.ɵɵelementStart(0, "span", 37);
|
|
38
|
+
i0.ɵɵelement(1, "i", 23);
|
|
39
|
+
i0.ɵɵtext(2);
|
|
40
|
+
i0.ɵɵelementEnd();
|
|
41
|
+
} if (rf & 2) {
|
|
42
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
43
|
+
i0.ɵɵadvance(2);
|
|
44
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.suiteTests.length, " tests ");
|
|
45
|
+
} }
|
|
46
|
+
function TestSuiteFormComponentExtended_div_25_Template(rf, ctx) { if (rf & 1) {
|
|
47
|
+
i0.ɵɵelementStart(0, "div", 38)(1, "p");
|
|
23
48
|
i0.ɵɵtext(2);
|
|
24
49
|
i0.ɵɵelementEnd()();
|
|
25
50
|
} if (rf & 2) {
|
|
@@ -27,8 +52,8 @@ function TestSuiteFormComponentExtended_div_15_Template(rf, ctx) { if (rf & 1) {
|
|
|
27
52
|
i0.ɵɵadvance(2);
|
|
28
53
|
i0.ɵɵtextInterpolate(ctx_r0.record.Description);
|
|
29
54
|
} }
|
|
30
|
-
function
|
|
31
|
-
i0.ɵɵelementStart(0, "span",
|
|
55
|
+
function TestSuiteFormComponentExtended_span_34_Template(rf, ctx) { if (rf & 1) {
|
|
56
|
+
i0.ɵɵelementStart(0, "span", 39);
|
|
32
57
|
i0.ɵɵtext(1);
|
|
33
58
|
i0.ɵɵelementEnd();
|
|
34
59
|
} if (rf & 2) {
|
|
@@ -36,8 +61,8 @@ function TestSuiteFormComponentExtended_span_24_Template(rf, ctx) { if (rf & 1)
|
|
|
36
61
|
i0.ɵɵadvance();
|
|
37
62
|
i0.ɵɵtextInterpolate(ctx_r0.suiteTests.length);
|
|
38
63
|
} }
|
|
39
|
-
function
|
|
40
|
-
i0.ɵɵelementStart(0, "span",
|
|
64
|
+
function TestSuiteFormComponentExtended_span_38_Template(rf, ctx) { if (rf & 1) {
|
|
65
|
+
i0.ɵɵelementStart(0, "span", 39);
|
|
41
66
|
i0.ɵɵtext(1);
|
|
42
67
|
i0.ɵɵelementEnd();
|
|
43
68
|
} if (rf & 2) {
|
|
@@ -45,229 +70,1814 @@ function TestSuiteFormComponentExtended_span_28_Template(rf, ctx) { if (rf & 1)
|
|
|
45
70
|
i0.ɵɵadvance();
|
|
46
71
|
i0.ɵɵtextInterpolate(ctx_r0.suiteRuns.length);
|
|
47
72
|
} }
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
i0.ɵɵ
|
|
73
|
+
function TestSuiteFormComponentExtended_div_46_Template(rf, ctx) { if (rf & 1) {
|
|
74
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
75
|
+
i0.ɵɵelementStart(0, "div", 40)(1, "div", 41)(2, "h3");
|
|
76
|
+
i0.ɵɵelement(3, "i", 42);
|
|
77
|
+
i0.ɵɵtext(4, " Suite Information");
|
|
78
|
+
i0.ɵɵelementEnd();
|
|
79
|
+
i0.ɵɵelementStart(5, "div", 43)(6, "div", 44)(7, "div", 45);
|
|
80
|
+
i0.ɵɵtext(8, "Name");
|
|
81
|
+
i0.ɵɵelementEnd();
|
|
82
|
+
i0.ɵɵelementStart(9, "div", 46);
|
|
83
|
+
i0.ɵɵtext(10);
|
|
84
|
+
i0.ɵɵelementEnd()();
|
|
85
|
+
i0.ɵɵelementStart(11, "div", 44)(12, "div", 45);
|
|
86
|
+
i0.ɵɵtext(13, "Status");
|
|
87
|
+
i0.ɵɵelementEnd();
|
|
88
|
+
i0.ɵɵelementStart(14, "div", 46)(15, "span", 47);
|
|
89
|
+
i0.ɵɵtext(16);
|
|
90
|
+
i0.ɵɵelementEnd()()();
|
|
91
|
+
i0.ɵɵelementStart(17, "div", 44)(18, "div", 45);
|
|
92
|
+
i0.ɵɵtext(19, "Created");
|
|
93
|
+
i0.ɵɵelementEnd();
|
|
94
|
+
i0.ɵɵelementStart(20, "div", 46);
|
|
95
|
+
i0.ɵɵtext(21);
|
|
96
|
+
i0.ɵɵpipe(22, "date");
|
|
97
|
+
i0.ɵɵelementEnd()();
|
|
98
|
+
i0.ɵɵelementStart(23, "div", 44)(24, "div", 45);
|
|
99
|
+
i0.ɵɵtext(25, "Updated");
|
|
100
|
+
i0.ɵɵelementEnd();
|
|
101
|
+
i0.ɵɵelementStart(26, "div", 46);
|
|
102
|
+
i0.ɵɵtext(27);
|
|
103
|
+
i0.ɵɵpipe(28, "date");
|
|
104
|
+
i0.ɵɵelementEnd()();
|
|
105
|
+
i0.ɵɵelementStart(29, "div", 44)(30, "div", 45);
|
|
106
|
+
i0.ɵɵtext(31, "Max Execution Time");
|
|
107
|
+
i0.ɵɵelementEnd();
|
|
108
|
+
i0.ɵɵelementStart(32, "div", 46);
|
|
109
|
+
i0.ɵɵtext(33);
|
|
110
|
+
i0.ɵɵelementEnd()()()();
|
|
111
|
+
i0.ɵɵelementStart(34, "div", 48)(35, "h3");
|
|
112
|
+
i0.ɵɵelement(36, "i", 49);
|
|
113
|
+
i0.ɵɵtext(37, " Execution Settings");
|
|
114
|
+
i0.ɵɵelementEnd();
|
|
115
|
+
i0.ɵɵelementStart(38, "div", 50)(39, "div", 51)(40, "label");
|
|
116
|
+
i0.ɵɵtext(41, "Max Execution Time (ms)");
|
|
117
|
+
i0.ɵɵelementEnd();
|
|
118
|
+
i0.ɵɵelementStart(42, "input", 52);
|
|
119
|
+
i0.ɵɵtwoWayListener("ngModelChange", function TestSuiteFormComponentExtended_div_46_Template_input_ngModelChange_42_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r0.record.MaxExecutionTimeMS, $event) || (ctx_r0.record.MaxExecutionTimeMS = $event); return i0.ɵɵresetView($event); });
|
|
120
|
+
i0.ɵɵelementEnd();
|
|
121
|
+
i0.ɵɵelementStart(43, "span", 53);
|
|
122
|
+
i0.ɵɵtext(44, "Default timeout for tests in this suite");
|
|
123
|
+
i0.ɵɵelementEnd()()()()();
|
|
124
|
+
} if (rf & 2) {
|
|
125
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
126
|
+
i0.ɵɵadvance(10);
|
|
127
|
+
i0.ɵɵtextInterpolate(ctx_r0.record.Name);
|
|
128
|
+
i0.ɵɵadvance(5);
|
|
129
|
+
i0.ɵɵproperty("ngClass", ctx_r0.getStatusClass());
|
|
130
|
+
i0.ɵɵadvance();
|
|
131
|
+
i0.ɵɵtextInterpolate(ctx_r0.record.Status);
|
|
132
|
+
i0.ɵɵadvance(5);
|
|
133
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(22, 7, ctx_r0.record.__mj_CreatedAt, "medium"));
|
|
134
|
+
i0.ɵɵadvance(6);
|
|
135
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(28, 10, ctx_r0.record.__mj_UpdatedAt, "medium"));
|
|
136
|
+
i0.ɵɵadvance(6);
|
|
137
|
+
i0.ɵɵtextInterpolate(ctx_r0.formatTimeout(ctx_r0.record.MaxExecutionTimeMS));
|
|
138
|
+
i0.ɵɵadvance(9);
|
|
139
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r0.record.MaxExecutionTimeMS);
|
|
140
|
+
} }
|
|
141
|
+
function TestSuiteFormComponentExtended_div_47_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
142
|
+
i0.ɵɵelementStart(0, "div", 61);
|
|
143
|
+
i0.ɵɵelement(1, "div", 62)(2, "div", 63);
|
|
144
|
+
i0.ɵɵelementStart(3, "div", 64);
|
|
145
|
+
i0.ɵɵelement(4, "div", 65)(5, "div", 66);
|
|
146
|
+
i0.ɵɵelementEnd()();
|
|
147
|
+
} }
|
|
148
|
+
function TestSuiteFormComponentExtended_div_47_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
149
|
+
i0.ɵɵelementStart(0, "div", 58)(1, "div", 59);
|
|
150
|
+
i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_47_div_1_div_2_Template, 6, 0, "div", 60);
|
|
151
|
+
i0.ɵɵelementEnd()();
|
|
152
|
+
} if (rf & 2) {
|
|
153
|
+
i0.ɵɵadvance(2);
|
|
154
|
+
i0.ɵɵproperty("ngForOf", i0.ɵɵpureFunction0(1, _c1));
|
|
155
|
+
} }
|
|
156
|
+
function TestSuiteFormComponentExtended_div_47_div_2_div_1_span_9_Template(rf, ctx) { if (rf & 1) {
|
|
157
|
+
i0.ɵɵelementStart(0, "span");
|
|
158
|
+
i0.ɵɵelement(1, "i", 42);
|
|
159
|
+
i0.ɵɵtext(2);
|
|
160
|
+
i0.ɵɵelementEnd();
|
|
161
|
+
} if (rf & 2) {
|
|
162
|
+
const test_r4 = i0.ɵɵnextContext().$implicit;
|
|
163
|
+
i0.ɵɵadvance(2);
|
|
164
|
+
i0.ɵɵtextInterpolate1(" ", test_r4.Status, "");
|
|
165
|
+
} }
|
|
166
|
+
function TestSuiteFormComponentExtended_div_47_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
167
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
168
|
+
i0.ɵɵelementStart(0, "div", 69);
|
|
169
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_47_div_2_div_1_Template_div_click_0_listener() { const test_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openTest(test_r4.TestID)); });
|
|
170
|
+
i0.ɵɵelementStart(1, "div", 70);
|
|
171
|
+
i0.ɵɵtext(2);
|
|
172
|
+
i0.ɵɵelementEnd();
|
|
173
|
+
i0.ɵɵelementStart(3, "div", 71);
|
|
174
|
+
i0.ɵɵelement(4, "i", 23);
|
|
175
|
+
i0.ɵɵelementEnd();
|
|
176
|
+
i0.ɵɵelementStart(5, "div", 72)(6, "div", 73);
|
|
177
|
+
i0.ɵɵtext(7);
|
|
178
|
+
i0.ɵɵelementEnd();
|
|
179
|
+
i0.ɵɵelementStart(8, "div", 74);
|
|
180
|
+
i0.ɵɵtemplate(9, TestSuiteFormComponentExtended_div_47_div_2_div_1_span_9_Template, 3, 1, "span", 75);
|
|
181
|
+
i0.ɵɵelementEnd()();
|
|
182
|
+
i0.ɵɵelement(10, "i", 76);
|
|
183
|
+
i0.ɵɵelementEnd();
|
|
184
|
+
} if (rf & 2) {
|
|
185
|
+
const test_r4 = ctx.$implicit;
|
|
186
|
+
i0.ɵɵadvance(2);
|
|
187
|
+
i0.ɵɵtextInterpolate(test_r4.Sequence);
|
|
188
|
+
i0.ɵɵadvance(5);
|
|
189
|
+
i0.ɵɵtextInterpolate(test_r4.Test);
|
|
190
|
+
i0.ɵɵadvance(2);
|
|
191
|
+
i0.ɵɵproperty("ngIf", test_r4.Status);
|
|
192
|
+
} }
|
|
193
|
+
function TestSuiteFormComponentExtended_div_47_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
194
|
+
i0.ɵɵelementStart(0, "div", 67);
|
|
195
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_47_div_2_div_1_Template, 11, 3, "div", 68);
|
|
196
|
+
i0.ɵɵelementEnd();
|
|
197
|
+
} if (rf & 2) {
|
|
198
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
199
|
+
i0.ɵɵadvance();
|
|
200
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.suiteTests);
|
|
201
|
+
} }
|
|
202
|
+
function TestSuiteFormComponentExtended_div_47_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
203
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
204
|
+
i0.ɵɵelement(2, "i", 23);
|
|
205
|
+
i0.ɵɵelementEnd();
|
|
206
|
+
i0.ɵɵelementStart(3, "h4");
|
|
207
|
+
i0.ɵɵtext(4, "No Tests in Suite");
|
|
208
|
+
i0.ɵɵelementEnd();
|
|
209
|
+
i0.ɵɵelementStart(5, "p");
|
|
210
|
+
i0.ɵɵtext(6, "Add tests to this suite to start running them together.");
|
|
211
|
+
i0.ɵɵelementEnd()();
|
|
212
|
+
} }
|
|
213
|
+
function TestSuiteFormComponentExtended_div_47_Template(rf, ctx) { if (rf & 1) {
|
|
214
|
+
i0.ɵɵelementStart(0, "div", 54);
|
|
215
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_47_div_1_Template, 3, 2, "div", 55)(2, TestSuiteFormComponentExtended_div_47_div_2_Template, 2, 1, "div", 56)(3, TestSuiteFormComponentExtended_div_47_div_3_Template, 7, 0, "div", 57);
|
|
216
|
+
i0.ɵɵelementEnd();
|
|
217
|
+
} if (rf & 2) {
|
|
218
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
219
|
+
i0.ɵɵadvance();
|
|
220
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingTests);
|
|
221
|
+
i0.ɵɵadvance();
|
|
222
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingTests && ctx_r0.suiteTests.length > 0);
|
|
223
|
+
i0.ɵɵadvance();
|
|
224
|
+
i0.ɵɵproperty("ngIf", ctx_r0.testsLoaded && !ctx_r0.loadingTests && ctx_r0.suiteTests.length === 0);
|
|
225
|
+
} }
|
|
226
|
+
function TestSuiteFormComponentExtended_div_48_div_1_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
227
|
+
i0.ɵɵelementStart(0, "div", 61);
|
|
228
|
+
i0.ɵɵelement(1, "div", 63);
|
|
229
|
+
i0.ɵɵelementStart(2, "div", 64);
|
|
230
|
+
i0.ɵɵelement(3, "div", 65)(4, "div", 66);
|
|
231
|
+
i0.ɵɵelementEnd()();
|
|
232
|
+
} }
|
|
233
|
+
function TestSuiteFormComponentExtended_div_48_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
234
|
+
i0.ɵɵelementStart(0, "div", 58)(1, "div", 59);
|
|
235
|
+
i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_48_div_1_div_2_Template, 5, 0, "div", 60);
|
|
236
|
+
i0.ɵɵelementEnd()();
|
|
237
|
+
} if (rf & 2) {
|
|
238
|
+
i0.ɵɵadvance(2);
|
|
239
|
+
i0.ɵɵproperty("ngForOf", i0.ɵɵpureFunction0(1, _c2));
|
|
240
|
+
} }
|
|
241
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_13_Template(rf, ctx) { if (rf & 1) {
|
|
242
|
+
i0.ɵɵelementStart(0, "span");
|
|
243
|
+
i0.ɵɵelement(1, "i", 97);
|
|
244
|
+
i0.ɵɵtext(2);
|
|
245
|
+
i0.ɵɵelementEnd();
|
|
246
|
+
} if (rf & 2) {
|
|
247
|
+
const run_r6 = i0.ɵɵnextContext().$implicit;
|
|
248
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
249
|
+
i0.ɵɵadvance(2);
|
|
250
|
+
i0.ɵɵtextInterpolate3(" ", run_r6.PassedTests, "/", run_r6.TotalTests, " (", ctx_r0.getPassRate(run_r6).toFixed(0), "%) ");
|
|
251
|
+
} }
|
|
252
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_15_Template(rf, ctx) { if (rf & 1) {
|
|
253
|
+
i0.ɵɵelementStart(0, "span", 98);
|
|
254
|
+
i0.ɵɵelement(1, "i", 85);
|
|
255
|
+
i0.ɵɵelementEnd();
|
|
256
|
+
} if (rf & 2) {
|
|
257
|
+
const run_r6 = i0.ɵɵnextContext().$implicit;
|
|
258
|
+
i0.ɵɵclassMap("status-" + run_r6.Status.toLowerCase());
|
|
259
|
+
i0.ɵɵadvance();
|
|
260
|
+
i0.ɵɵclassProp("fa-circle-check", run_r6.Status === "Completed")("fa-circle-xmark", run_r6.Status === "Failed")("fa-spinner", run_r6.Status === "Running")("fa-clock", run_r6.Status === "Pending")("fa-ban", run_r6.Status === "Cancelled");
|
|
261
|
+
} }
|
|
262
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_16_Template(rf, ctx) { if (rf & 1) {
|
|
263
|
+
i0.ɵɵelementStart(0, "span", 99);
|
|
264
|
+
i0.ɵɵelement(1, "i", 100);
|
|
265
|
+
i0.ɵɵelementStart(2, "span", 101);
|
|
266
|
+
i0.ɵɵelement(3, "i", 102);
|
|
267
|
+
i0.ɵɵelementEnd()();
|
|
268
|
+
} }
|
|
269
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_span_17_Template(rf, ctx) { if (rf & 1) {
|
|
270
|
+
i0.ɵɵelementStart(0, "span", 103);
|
|
271
|
+
i0.ɵɵelement(1, "i", 104);
|
|
272
|
+
i0.ɵɵelementStart(2, "span");
|
|
273
|
+
i0.ɵɵtext(3);
|
|
274
|
+
i0.ɵɵelementEnd()();
|
|
275
|
+
} if (rf & 2) {
|
|
276
|
+
const run_r6 = i0.ɵɵnextContext().$implicit;
|
|
277
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
278
|
+
i0.ɵɵclassProp("high", ctx_r0.getPassRate(run_r6) >= 80)("medium", ctx_r0.getPassRate(run_r6) >= 50 && ctx_r0.getPassRate(run_r6) < 80)("low", ctx_r0.getPassRate(run_r6) < 50);
|
|
279
|
+
i0.ɵɵadvance(3);
|
|
280
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r6).toFixed(0), "%");
|
|
281
|
+
} }
|
|
282
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
283
|
+
i0.ɵɵelementStart(0, "span", 107);
|
|
284
|
+
i0.ɵɵtext(1);
|
|
285
|
+
i0.ɵɵelementEnd();
|
|
286
|
+
} if (rf & 2) {
|
|
287
|
+
const tag_r7 = ctx.$implicit;
|
|
288
|
+
i0.ɵɵadvance();
|
|
289
|
+
i0.ɵɵtextInterpolate(tag_r7);
|
|
290
|
+
} }
|
|
291
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_Template(rf, ctx) { if (rf & 1) {
|
|
292
|
+
i0.ɵɵelementStart(0, "div", 105);
|
|
293
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_span_1_Template, 2, 1, "span", 106);
|
|
294
|
+
i0.ɵɵelementEnd();
|
|
295
|
+
} if (rf & 2) {
|
|
296
|
+
const run_r6 = i0.ɵɵnextContext().$implicit;
|
|
297
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
298
|
+
i0.ɵɵadvance();
|
|
299
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r6));
|
|
300
|
+
} }
|
|
301
|
+
function TestSuiteFormComponentExtended_div_48_div_2_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
302
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
303
|
+
i0.ɵɵelementStart(0, "div", 83);
|
|
304
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_48_div_2_div_1_Template_div_click_0_listener() { const run_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r6.ID)); });
|
|
305
|
+
i0.ɵɵelementStart(1, "div", 84);
|
|
306
|
+
i0.ɵɵelement(2, "i", 85);
|
|
307
|
+
i0.ɵɵelementEnd();
|
|
308
|
+
i0.ɵɵelementStart(3, "div", 86)(4, "div", 87)(5, "span", 88);
|
|
309
|
+
i0.ɵɵtext(6);
|
|
310
|
+
i0.ɵɵelementEnd();
|
|
311
|
+
i0.ɵɵelementStart(7, "span", 89);
|
|
312
|
+
i0.ɵɵtext(8);
|
|
313
|
+
i0.ɵɵelementEnd()();
|
|
314
|
+
i0.ɵɵelementStart(9, "div", 90)(10, "span");
|
|
315
|
+
i0.ɵɵelement(11, "i", 91);
|
|
316
|
+
i0.ɵɵtext(12);
|
|
317
|
+
i0.ɵɵelementEnd();
|
|
318
|
+
i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_13_Template, 3, 3, "span", 75);
|
|
319
|
+
i0.ɵɵelementEnd();
|
|
320
|
+
i0.ɵɵelementStart(14, "div", 92);
|
|
321
|
+
i0.ɵɵtemplate(15, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_15_Template, 2, 12, "span", 93)(16, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_16_Template, 4, 0, "span", 94)(17, TestSuiteFormComponentExtended_div_48_div_2_div_1_span_17_Template, 4, 7, "span", 95);
|
|
322
|
+
i0.ɵɵelementEnd();
|
|
323
|
+
i0.ɵɵtemplate(18, TestSuiteFormComponentExtended_div_48_div_2_div_1_div_18_Template, 2, 1, "div", 96);
|
|
324
|
+
i0.ɵɵelementEnd();
|
|
325
|
+
i0.ɵɵelement(19, "i", 76);
|
|
326
|
+
i0.ɵɵelementEnd();
|
|
327
|
+
} if (rf & 2) {
|
|
328
|
+
const run_r6 = ctx.$implicit;
|
|
329
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
330
|
+
i0.ɵɵadvance();
|
|
331
|
+
i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r6.Status));
|
|
332
|
+
i0.ɵɵadvance();
|
|
333
|
+
i0.ɵɵclassProp("fa-check", run_r6.Status === "Completed")("fa-times", run_r6.Status === "Failed")("fa-spinner", run_r6.Status === "Running")("fa-clock", run_r6.Status === "Pending")("fa-ban", run_r6.Status === "Cancelled");
|
|
334
|
+
i0.ɵɵadvance(4);
|
|
335
|
+
i0.ɵɵtextInterpolate1("Run #", run_r6.ID.substring(0, 8), "");
|
|
336
|
+
i0.ɵɵadvance();
|
|
337
|
+
i0.ɵɵstyleProp("color", ctx_r0.getRunStatusColor(run_r6.Status));
|
|
338
|
+
i0.ɵɵadvance();
|
|
339
|
+
i0.ɵɵtextInterpolate(run_r6.Status);
|
|
340
|
+
i0.ɵɵadvance(4);
|
|
341
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.getRelativeTime(run_r6.StartedAt), "");
|
|
342
|
+
i0.ɵɵadvance();
|
|
343
|
+
i0.ɵɵproperty("ngIf", run_r6.TotalTests);
|
|
344
|
+
i0.ɵɵadvance(2);
|
|
345
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
|
|
346
|
+
i0.ɵɵadvance();
|
|
347
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman);
|
|
348
|
+
i0.ɵɵadvance();
|
|
349
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && run_r6.TotalTests);
|
|
350
|
+
i0.ɵɵadvance();
|
|
351
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r6).length > 0);
|
|
352
|
+
} }
|
|
353
|
+
function TestSuiteFormComponentExtended_div_48_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
354
|
+
i0.ɵɵelementStart(0, "div", 81);
|
|
355
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_2_div_1_Template, 20, 22, "div", 82);
|
|
356
|
+
i0.ɵɵelementEnd();
|
|
357
|
+
} if (rf & 2) {
|
|
358
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
359
|
+
i0.ɵɵadvance();
|
|
360
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
|
|
361
|
+
} }
|
|
362
|
+
function TestSuiteFormComponentExtended_div_48_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
363
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
364
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
365
|
+
i0.ɵɵelement(2, "i", 108);
|
|
366
|
+
i0.ɵɵelementEnd();
|
|
367
|
+
i0.ɵɵelementStart(3, "h4");
|
|
368
|
+
i0.ɵɵtext(4, "No Suite Runs Yet");
|
|
369
|
+
i0.ɵɵelementEnd();
|
|
370
|
+
i0.ɵɵelementStart(5, "p");
|
|
371
|
+
i0.ɵɵtext(6, "Run this suite to see execution history and results here.");
|
|
372
|
+
i0.ɵɵelementEnd();
|
|
373
|
+
i0.ɵɵelementStart(7, "button", 15);
|
|
374
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_48_div_3_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r8); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
|
|
375
|
+
i0.ɵɵelement(8, "i", 16);
|
|
376
|
+
i0.ɵɵtext(9, " Run Suite Now ");
|
|
377
|
+
i0.ɵɵelementEnd()();
|
|
378
|
+
} }
|
|
379
|
+
function TestSuiteFormComponentExtended_div_48_Template(rf, ctx) { if (rf & 1) {
|
|
380
|
+
i0.ɵɵelementStart(0, "div", 79);
|
|
381
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_48_div_1_Template, 3, 2, "div", 55)(2, TestSuiteFormComponentExtended_div_48_div_2_Template, 2, 1, "div", 80)(3, TestSuiteFormComponentExtended_div_48_div_3_Template, 10, 0, "div", 57);
|
|
382
|
+
i0.ɵɵelementEnd();
|
|
383
|
+
} if (rf & 2) {
|
|
384
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
385
|
+
i0.ɵɵadvance();
|
|
386
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingRuns);
|
|
387
|
+
i0.ɵɵadvance();
|
|
388
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
|
|
389
|
+
i0.ɵɵadvance();
|
|
390
|
+
i0.ɵɵproperty("ngIf", ctx_r0.runsLoaded && !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length === 0);
|
|
391
|
+
} }
|
|
392
|
+
function TestSuiteFormComponentExtended_div_49_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
393
|
+
i0.ɵɵelementStart(0, "div", 58);
|
|
394
|
+
i0.ɵɵelement(1, "mj-loading", 110);
|
|
395
|
+
i0.ɵɵelementEnd();
|
|
396
|
+
} }
|
|
397
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
398
|
+
i0.ɵɵelementStart(0, "span");
|
|
399
|
+
i0.ɵɵtext(1);
|
|
400
|
+
i0.ɵɵelementEnd();
|
|
401
|
+
} if (rf & 2) {
|
|
402
|
+
const ctx_r0 = i0.ɵɵnextContext(4);
|
|
403
|
+
i0.ɵɵadvance();
|
|
404
|
+
i0.ɵɵtextInterpolate1(" \u00B7 ", ctx_r0.selectedTags.length, " tags");
|
|
405
|
+
} }
|
|
406
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_Template(rf, ctx) { if (rf & 1) {
|
|
407
|
+
i0.ɵɵelementStart(0, "span", 123);
|
|
408
|
+
i0.ɵɵtext(1);
|
|
409
|
+
i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_span_2_Template, 2, 1, "span", 75);
|
|
410
|
+
i0.ɵɵelementEnd();
|
|
411
|
+
} if (rf & 2) {
|
|
412
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
413
|
+
i0.ɵɵadvance();
|
|
414
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.analyticsTimeRange === "all" ? "All Time" : ctx_r0.analyticsTimeRange, " ");
|
|
415
|
+
i0.ɵɵadvance();
|
|
416
|
+
i0.ɵɵproperty("ngIf", ctx_r0.selectedTags.length > 0);
|
|
417
|
+
} }
|
|
418
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_span_3_Template(rf, ctx) { if (rf & 1) {
|
|
419
|
+
i0.ɵɵelementStart(0, "span", 133);
|
|
420
|
+
i0.ɵɵtext(1);
|
|
421
|
+
i0.ɵɵelementEnd();
|
|
422
|
+
} if (rf & 2) {
|
|
423
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
424
|
+
i0.ɵɵadvance();
|
|
425
|
+
i0.ɵɵtextInterpolate1("(", ctx_r0.selectedTags.length, " selected)");
|
|
426
|
+
} }
|
|
427
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_i_1_Template(rf, ctx) { if (rf & 1) {
|
|
428
|
+
i0.ɵɵelement(0, "i", 136);
|
|
429
|
+
} }
|
|
430
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template(rf, ctx) { if (rf & 1) {
|
|
431
|
+
const _r12 = i0.ɵɵgetCurrentView();
|
|
432
|
+
i0.ɵɵelementStart(0, "button", 134);
|
|
433
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template_button_click_0_listener() { const tag_r13 = i0.ɵɵrestoreView(_r12).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.toggleTagFilter(tag_r13)); });
|
|
434
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_i_1_Template, 1, 0, "i", 135);
|
|
435
|
+
i0.ɵɵtext(2);
|
|
436
|
+
i0.ɵɵelementEnd();
|
|
437
|
+
} if (rf & 2) {
|
|
438
|
+
const tag_r13 = ctx.$implicit;
|
|
439
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
440
|
+
i0.ɵɵclassProp("active", ctx_r0.isTagSelected(tag_r13));
|
|
441
|
+
i0.ɵɵadvance();
|
|
442
|
+
i0.ɵɵproperty("ngIf", ctx_r0.isTagSelected(tag_r13));
|
|
443
|
+
i0.ɵɵadvance();
|
|
444
|
+
i0.ɵɵtextInterpolate1(" ", tag_r13, " ");
|
|
445
|
+
} }
|
|
446
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template(rf, ctx) { if (rf & 1) {
|
|
447
|
+
const _r11 = i0.ɵɵgetCurrentView();
|
|
448
|
+
i0.ɵɵelementStart(0, "div", 125)(1, "label");
|
|
449
|
+
i0.ɵɵtext(2, "Filter by Tag ");
|
|
450
|
+
i0.ɵɵtemplate(3, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_span_3_Template, 2, 1, "span", 129);
|
|
451
|
+
i0.ɵɵelementEnd();
|
|
452
|
+
i0.ɵɵelementStart(4, "div", 130)(5, "button", 131);
|
|
453
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r11); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleTagFilter(null)); });
|
|
454
|
+
i0.ɵɵelement(6, "i", 6);
|
|
455
|
+
i0.ɵɵtext(7, " All Tags ");
|
|
456
|
+
i0.ɵɵelementEnd();
|
|
457
|
+
i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_button_8_Template, 3, 4, "button", 132);
|
|
458
|
+
i0.ɵɵelementEnd()();
|
|
459
|
+
} if (rf & 2) {
|
|
460
|
+
const ctx_r0 = i0.ɵɵnextContext(4);
|
|
461
|
+
i0.ɵɵadvance(3);
|
|
462
|
+
i0.ɵɵproperty("ngIf", ctx_r0.selectedTags.length > 0);
|
|
463
|
+
i0.ɵɵadvance(2);
|
|
464
|
+
i0.ɵɵclassProp("active", ctx_r0.selectedTags.length === 0);
|
|
465
|
+
i0.ɵɵadvance(3);
|
|
466
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.uniqueTags);
|
|
467
|
+
} }
|
|
468
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template(rf, ctx) { if (rf & 1) {
|
|
469
|
+
const _r10 = i0.ɵɵgetCurrentView();
|
|
470
|
+
i0.ɵɵelementStart(0, "div", 124)(1, "div", 125)(2, "label");
|
|
471
|
+
i0.ɵɵtext(3, "Time Range");
|
|
472
|
+
i0.ɵɵelementEnd();
|
|
473
|
+
i0.ɵɵelementStart(4, "div", 126)(5, "button", 127);
|
|
474
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("7d")); });
|
|
475
|
+
i0.ɵɵtext(6, "7 Days");
|
|
476
|
+
i0.ɵɵelementEnd();
|
|
477
|
+
i0.ɵɵelementStart(7, "button", 127);
|
|
478
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("30d")); });
|
|
479
|
+
i0.ɵɵtext(8, "30 Days");
|
|
480
|
+
i0.ɵɵelementEnd();
|
|
481
|
+
i0.ɵɵelementStart(9, "button", 127);
|
|
482
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("90d")); });
|
|
483
|
+
i0.ɵɵtext(10, "90 Days");
|
|
484
|
+
i0.ɵɵelementEnd();
|
|
485
|
+
i0.ɵɵelementStart(11, "button", 127);
|
|
486
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r10); const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.setTimeRange("all")); });
|
|
487
|
+
i0.ɵɵtext(12, "All Time");
|
|
488
|
+
i0.ɵɵelementEnd()()();
|
|
489
|
+
i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_div_13_Template, 9, 4, "div", 128);
|
|
490
|
+
i0.ɵɵelementEnd();
|
|
491
|
+
} if (rf & 2) {
|
|
492
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
493
|
+
i0.ɵɵadvance(5);
|
|
494
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "7d");
|
|
495
|
+
i0.ɵɵadvance(2);
|
|
496
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "30d");
|
|
497
|
+
i0.ɵɵadvance(2);
|
|
498
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "90d");
|
|
499
|
+
i0.ɵɵadvance(2);
|
|
500
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsTimeRange === "all");
|
|
501
|
+
i0.ɵɵadvance(2);
|
|
502
|
+
i0.ɵɵproperty("ngIf", ctx_r0.uniqueTags.length > 0);
|
|
503
|
+
} }
|
|
504
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_20_Template(rf, ctx) { if (rf & 1) {
|
|
505
|
+
i0.ɵɵelementStart(0, "span", 161);
|
|
506
|
+
i0.ɵɵtext(1);
|
|
507
|
+
i0.ɵɵelementEnd();
|
|
508
|
+
} if (rf & 2) {
|
|
509
|
+
const tag_r16 = ctx.$implicit;
|
|
510
|
+
i0.ɵɵadvance();
|
|
511
|
+
i0.ɵɵtextInterpolate(tag_r16);
|
|
512
|
+
} }
|
|
513
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_21_Template(rf, ctx) { if (rf & 1) {
|
|
514
|
+
i0.ɵɵelementStart(0, "span", 162);
|
|
515
|
+
i0.ɵɵtext(1);
|
|
516
|
+
i0.ɵɵelementEnd();
|
|
517
|
+
} if (rf & 2) {
|
|
518
|
+
const dp_r15 = i0.ɵɵnextContext().$implicit;
|
|
519
|
+
i0.ɵɵadvance();
|
|
520
|
+
i0.ɵɵtextInterpolate1("+", dp_r15.tags.length - 2, "");
|
|
521
|
+
} }
|
|
522
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template(rf, ctx) { if (rf & 1) {
|
|
523
|
+
const _r14 = i0.ɵɵgetCurrentView();
|
|
524
|
+
i0.ɵɵelementStart(0, "tr", 154);
|
|
525
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template_tr_click_0_listener() { const dp_r15 = i0.ɵɵrestoreView(_r14).$implicit; const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.openSuiteRun(dp_r15.runId)); });
|
|
526
|
+
i0.ɵɵelementStart(1, "td");
|
|
527
|
+
i0.ɵɵtext(2);
|
|
528
|
+
i0.ɵɵpipe(3, "date");
|
|
529
|
+
i0.ɵɵelementEnd();
|
|
530
|
+
i0.ɵɵelementStart(4, "td")(5, "span", 155);
|
|
531
|
+
i0.ɵɵtext(6);
|
|
532
|
+
i0.ɵɵelementEnd()();
|
|
533
|
+
i0.ɵɵelementStart(7, "td")(8, "div", 156);
|
|
534
|
+
i0.ɵɵelement(9, "div", 157);
|
|
535
|
+
i0.ɵɵelementStart(10, "span");
|
|
536
|
+
i0.ɵɵtext(11);
|
|
537
|
+
i0.ɵɵelementEnd()()();
|
|
538
|
+
i0.ɵɵelementStart(12, "td");
|
|
539
|
+
i0.ɵɵtext(13);
|
|
540
|
+
i0.ɵɵelementEnd();
|
|
541
|
+
i0.ɵɵelementStart(14, "td");
|
|
542
|
+
i0.ɵɵtext(15);
|
|
543
|
+
i0.ɵɵelementEnd();
|
|
544
|
+
i0.ɵɵelementStart(16, "td");
|
|
545
|
+
i0.ɵɵtext(17);
|
|
546
|
+
i0.ɵɵelementEnd();
|
|
547
|
+
i0.ɵɵelementStart(18, "td")(19, "div", 158);
|
|
548
|
+
i0.ɵɵtemplate(20, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_20_Template, 2, 1, "span", 159)(21, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_span_21_Template, 2, 1, "span", 160);
|
|
549
|
+
i0.ɵɵelementEnd()()();
|
|
550
|
+
} if (rf & 2) {
|
|
551
|
+
const dp_r15 = ctx.$implicit;
|
|
552
|
+
const ctx_r0 = i0.ɵɵnextContext(4);
|
|
553
|
+
i0.ɵɵadvance(2);
|
|
554
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(3, 13, dp_r15.date, "short"));
|
|
555
|
+
i0.ɵɵadvance(3);
|
|
556
|
+
i0.ɵɵproperty("ngClass", "status-" + dp_r15.status.toLowerCase());
|
|
557
|
+
i0.ɵɵadvance();
|
|
558
|
+
i0.ɵɵtextInterpolate(dp_r15.status);
|
|
559
|
+
i0.ɵɵadvance(3);
|
|
560
|
+
i0.ɵɵstyleProp("width", dp_r15.passRate, "%");
|
|
561
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(16, _c5, dp_r15.passRate >= 80, dp_r15.passRate >= 50 && dp_r15.passRate < 80, dp_r15.passRate < 50));
|
|
562
|
+
i0.ɵɵadvance(2);
|
|
563
|
+
i0.ɵɵtextInterpolate1("", dp_r15.passRate.toFixed(0), "%");
|
|
564
|
+
i0.ɵɵadvance(2);
|
|
565
|
+
i0.ɵɵtextInterpolate2("", dp_r15.passedTests, "/", dp_r15.totalTests, "");
|
|
566
|
+
i0.ɵɵadvance(2);
|
|
567
|
+
i0.ɵɵtextInterpolate(ctx_r0.formatDuration(dp_r15.duration));
|
|
568
|
+
i0.ɵɵadvance(2);
|
|
569
|
+
i0.ɵɵtextInterpolate(ctx_r0.formatCost(dp_r15.cost));
|
|
570
|
+
i0.ɵɵadvance(3);
|
|
571
|
+
i0.ɵɵproperty("ngForOf", dp_r15.tags.slice(0, 2));
|
|
572
|
+
i0.ɵɵadvance();
|
|
573
|
+
i0.ɵɵproperty("ngIf", dp_r15.tags.length > 2);
|
|
574
|
+
} }
|
|
575
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_div_61_Template(rf, ctx) { if (rf & 1) {
|
|
576
|
+
i0.ɵɵelementStart(0, "div", 163)(1, "p");
|
|
577
|
+
i0.ɵɵtext(2, "No runs match the current filters.");
|
|
578
|
+
i0.ɵɵelementEnd()();
|
|
579
|
+
} }
|
|
580
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_Template(rf, ctx) { if (rf & 1) {
|
|
581
|
+
i0.ɵɵelementContainerStart(0);
|
|
582
|
+
i0.ɵɵelementStart(1, "div", 137)(2, "div", 138)(3, "div", 139);
|
|
583
|
+
i0.ɵɵelement(4, "i", 108);
|
|
584
|
+
i0.ɵɵelementEnd();
|
|
585
|
+
i0.ɵɵelementStart(5, "div", 140)(6, "div", 141);
|
|
586
|
+
i0.ɵɵtext(7);
|
|
587
|
+
i0.ɵɵelementEnd();
|
|
588
|
+
i0.ɵɵelementStart(8, "div", 142);
|
|
589
|
+
i0.ɵɵtext(9, "Total Runs");
|
|
590
|
+
i0.ɵɵelementEnd()()();
|
|
591
|
+
i0.ɵɵelementStart(10, "div", 138)(11, "div", 143);
|
|
592
|
+
i0.ɵɵelement(12, "i", 97);
|
|
593
|
+
i0.ɵɵelementEnd();
|
|
594
|
+
i0.ɵɵelementStart(13, "div", 140)(14, "div", 141);
|
|
595
|
+
i0.ɵɵtext(15);
|
|
596
|
+
i0.ɵɵelementEnd();
|
|
597
|
+
i0.ɵɵelementStart(16, "div", 142);
|
|
598
|
+
i0.ɵɵtext(17, "Avg Pass Rate");
|
|
599
|
+
i0.ɵɵelementEnd();
|
|
600
|
+
i0.ɵɵelementStart(18, "div", 144);
|
|
601
|
+
i0.ɵɵelement(19, "i", 10);
|
|
602
|
+
i0.ɵɵtext(20);
|
|
603
|
+
i0.ɵɵelementEnd()()();
|
|
604
|
+
i0.ɵɵelementStart(21, "div", 138)(22, "div", 145);
|
|
605
|
+
i0.ɵɵelement(23, "i", 102);
|
|
606
|
+
i0.ɵɵelementEnd();
|
|
607
|
+
i0.ɵɵelementStart(24, "div", 140)(25, "div", 141);
|
|
608
|
+
i0.ɵɵtext(26);
|
|
609
|
+
i0.ɵɵelementEnd();
|
|
610
|
+
i0.ɵɵelementStart(27, "div", 142);
|
|
611
|
+
i0.ɵɵtext(28, "Avg Duration");
|
|
612
|
+
i0.ɵɵelementEnd()()();
|
|
613
|
+
i0.ɵɵelementStart(29, "div", 138)(30, "div", 146);
|
|
614
|
+
i0.ɵɵelement(31, "i", 147);
|
|
615
|
+
i0.ɵɵelementEnd();
|
|
616
|
+
i0.ɵɵelementStart(32, "div", 140)(33, "div", 141);
|
|
617
|
+
i0.ɵɵtext(34);
|
|
618
|
+
i0.ɵɵelementEnd();
|
|
619
|
+
i0.ɵɵelementStart(35, "div", 142);
|
|
620
|
+
i0.ɵɵtext(36, "Total Cost");
|
|
621
|
+
i0.ɵɵelementEnd()()()();
|
|
622
|
+
i0.ɵɵelementStart(37, "div", 148)(38, "h3");
|
|
623
|
+
i0.ɵɵelement(39, "i", 149);
|
|
624
|
+
i0.ɵɵtext(40, " Run History");
|
|
625
|
+
i0.ɵɵelementEnd();
|
|
626
|
+
i0.ɵɵelementStart(41, "div", 150)(42, "table", 151)(43, "thead")(44, "tr")(45, "th");
|
|
627
|
+
i0.ɵɵtext(46, "Date");
|
|
628
|
+
i0.ɵɵelementEnd();
|
|
629
|
+
i0.ɵɵelementStart(47, "th");
|
|
630
|
+
i0.ɵɵtext(48, "Status");
|
|
631
|
+
i0.ɵɵelementEnd();
|
|
632
|
+
i0.ɵɵelementStart(49, "th");
|
|
633
|
+
i0.ɵɵtext(50, "Pass Rate");
|
|
634
|
+
i0.ɵɵelementEnd();
|
|
635
|
+
i0.ɵɵelementStart(51, "th");
|
|
636
|
+
i0.ɵɵtext(52, "Tests");
|
|
637
|
+
i0.ɵɵelementEnd();
|
|
638
|
+
i0.ɵɵelementStart(53, "th");
|
|
639
|
+
i0.ɵɵtext(54, "Duration");
|
|
640
|
+
i0.ɵɵelementEnd();
|
|
641
|
+
i0.ɵɵelementStart(55, "th");
|
|
642
|
+
i0.ɵɵtext(56, "Cost");
|
|
643
|
+
i0.ɵɵelementEnd();
|
|
644
|
+
i0.ɵɵelementStart(57, "th");
|
|
645
|
+
i0.ɵɵtext(58, "Tags");
|
|
646
|
+
i0.ɵɵelementEnd()()();
|
|
647
|
+
i0.ɵɵelementStart(59, "tbody");
|
|
648
|
+
i0.ɵɵtemplate(60, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_tr_60_Template, 22, 20, "tr", 152);
|
|
649
|
+
i0.ɵɵelementEnd()()();
|
|
650
|
+
i0.ɵɵtemplate(61, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_div_61_Template, 3, 0, "div", 153);
|
|
651
|
+
i0.ɵɵelementEnd();
|
|
652
|
+
i0.ɵɵelementContainerEnd();
|
|
653
|
+
} if (rf & 2) {
|
|
654
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
655
|
+
i0.ɵɵadvance(7);
|
|
656
|
+
i0.ɵɵtextInterpolate(ctx_r0.getTotalRuns());
|
|
657
|
+
i0.ɵɵadvance(8);
|
|
658
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getAveragePassRate().toFixed(1), "%");
|
|
659
|
+
i0.ɵɵadvance(3);
|
|
660
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(9, _c3, ctx_r0.getPassRateTrend().direction === "up", ctx_r0.getPassRateTrend().direction === "down"));
|
|
661
|
+
i0.ɵɵadvance();
|
|
662
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(12, _c4, ctx_r0.getPassRateTrend().direction === "up", ctx_r0.getPassRateTrend().direction === "down", ctx_r0.getPassRateTrend().direction === "stable"));
|
|
663
|
+
i0.ɵɵadvance();
|
|
664
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.getPassRateTrend().value.toFixed(1), "% ");
|
|
665
|
+
i0.ɵɵadvance(6);
|
|
666
|
+
i0.ɵɵtextInterpolate(ctx_r0.formatDuration(ctx_r0.getAverageDuration()));
|
|
667
|
+
i0.ɵɵadvance(8);
|
|
668
|
+
i0.ɵɵtextInterpolate(ctx_r0.formatCost(ctx_r0.getTotalCost()));
|
|
669
|
+
i0.ɵɵadvance(26);
|
|
670
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.getFilteredAnalyticsData());
|
|
671
|
+
i0.ɵɵadvance();
|
|
672
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getFilteredAnalyticsData().length === 0);
|
|
673
|
+
} }
|
|
674
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
675
|
+
i0.ɵɵelementStart(0, "div", 58);
|
|
676
|
+
i0.ɵɵelement(1, "mj-loading", 165);
|
|
677
|
+
i0.ɵɵelementEnd();
|
|
678
|
+
} }
|
|
679
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template(rf, ctx) { if (rf & 1) {
|
|
680
|
+
const _r18 = i0.ɵɵgetCurrentView();
|
|
681
|
+
i0.ɵɵelementStart(0, "button", 188);
|
|
682
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.clearMatrixFilter()); });
|
|
683
|
+
i0.ɵɵelement(1, "i", 189);
|
|
684
|
+
i0.ɵɵelementEnd();
|
|
685
|
+
} }
|
|
686
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
687
|
+
i0.ɵɵelementStart(0, "span", 198);
|
|
688
|
+
i0.ɵɵtext(1);
|
|
689
|
+
i0.ɵɵelementEnd();
|
|
690
|
+
} if (rf & 2) {
|
|
691
|
+
const tag_r21 = ctx.$implicit;
|
|
692
|
+
i0.ɵɵadvance();
|
|
693
|
+
i0.ɵɵtextInterpolate(tag_r21);
|
|
694
|
+
} }
|
|
695
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
696
|
+
i0.ɵɵelementStart(0, "span", 199);
|
|
697
|
+
i0.ɵɵtext(1);
|
|
698
|
+
i0.ɵɵelementEnd();
|
|
699
|
+
} if (rf & 2) {
|
|
700
|
+
const run_r20 = i0.ɵɵnextContext(2).$implicit;
|
|
701
|
+
i0.ɵɵadvance();
|
|
702
|
+
i0.ɵɵtextInterpolate1("+", run_r20.tags.length - 2, "");
|
|
703
|
+
} }
|
|
704
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
705
|
+
i0.ɵɵelementStart(0, "div", 195);
|
|
706
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_1_Template, 2, 1, "span", 196)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_span_2_Template, 2, 1, "span", 197);
|
|
707
|
+
i0.ɵɵelementEnd();
|
|
708
|
+
} if (rf & 2) {
|
|
709
|
+
const run_r20 = i0.ɵɵnextContext().$implicit;
|
|
710
|
+
i0.ɵɵadvance();
|
|
711
|
+
i0.ɵɵproperty("ngForOf", run_r20.tags.slice(0, 2));
|
|
712
|
+
i0.ɵɵadvance();
|
|
713
|
+
i0.ɵɵproperty("ngIf", run_r20.tags.length > 2);
|
|
714
|
+
} }
|
|
715
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template(rf, ctx) { if (rf & 1) {
|
|
716
|
+
const _r19 = i0.ɵɵgetCurrentView();
|
|
717
|
+
i0.ɵɵelementStart(0, "th", 190);
|
|
718
|
+
i0.ɵɵpipe(1, "date");
|
|
719
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template_th_click_0_listener() { const run_r20 = i0.ɵɵrestoreView(_r19).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r20.runId)); });
|
|
720
|
+
i0.ɵɵelementStart(2, "div", 191);
|
|
721
|
+
i0.ɵɵtemplate(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_div_3_Template, 3, 2, "div", 192);
|
|
722
|
+
i0.ɵɵelementStart(4, "div", 193);
|
|
723
|
+
i0.ɵɵtext(5);
|
|
724
|
+
i0.ɵɵelementEnd();
|
|
725
|
+
i0.ɵɵelementStart(6, "div", 194);
|
|
726
|
+
i0.ɵɵtext(7);
|
|
727
|
+
i0.ɵɵelementEnd()()();
|
|
728
|
+
} if (rf & 2) {
|
|
729
|
+
const run_r20 = ctx.$implicit;
|
|
730
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
731
|
+
i0.ɵɵproperty("title", "Click to view suite run - " + i0.ɵɵpipeBind2(1, 5, run_r20.date, "medium"));
|
|
732
|
+
i0.ɵɵadvance(3);
|
|
733
|
+
i0.ɵɵproperty("ngIf", run_r20.tags.length > 0);
|
|
734
|
+
i0.ɵɵadvance(2);
|
|
735
|
+
i0.ɵɵtextInterpolate(ctx_r0.getRelativeTime(run_r20.date));
|
|
736
|
+
i0.ɵɵadvance();
|
|
737
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(8, _c5, run_r20.passRate >= 80, run_r20.passRate >= 50 && run_r20.passRate < 80, run_r20.passRate < 50));
|
|
738
|
+
i0.ɵɵadvance();
|
|
739
|
+
i0.ɵɵtextInterpolate1(" ", run_r20.passRate.toFixed(0), "% ");
|
|
740
|
+
} }
|
|
741
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
742
|
+
i0.ɵɵelementStart(0, "span", 213);
|
|
743
|
+
i0.ɵɵelement(1, "i", 85);
|
|
744
|
+
i0.ɵɵelementEnd();
|
|
745
|
+
} if (rf & 2) {
|
|
746
|
+
const result_r26 = i0.ɵɵnextContext().ngIf;
|
|
747
|
+
const ctx_r0 = i0.ɵɵnextContext(7);
|
|
748
|
+
i0.ɵɵclassProp("cell-skipped-status", result_r26.status === "Skipped" || result_r26.status === "Pending");
|
|
749
|
+
i0.ɵɵproperty("ngClass", "status-" + result_r26.status.toLowerCase())("title", ctx_r0.getStatusTooltip(result_r26.status));
|
|
750
|
+
i0.ɵɵadvance();
|
|
751
|
+
i0.ɵɵclassProp("fa-check", result_r26.status === "Passed")("fa-times", result_r26.status === "Failed")("fa-exclamation", result_r26.status === "Error")("fa-hourglass-end", result_r26.status === "Timeout")("fa-forward", result_r26.status === "Skipped")("fa-spinner", result_r26.status === "Running")("fa-clock", result_r26.status === "Pending");
|
|
752
|
+
} }
|
|
753
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_3_Template(rf, ctx) { if (rf & 1) {
|
|
754
|
+
i0.ɵɵelementStart(0, "span", 214);
|
|
755
|
+
i0.ɵɵelement(1, "i", 215);
|
|
756
|
+
i0.ɵɵelementEnd();
|
|
757
|
+
} }
|
|
758
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_4_Template(rf, ctx) { if (rf & 1) {
|
|
759
|
+
i0.ɵɵelementStart(0, "span", 216);
|
|
760
|
+
i0.ɵɵelement(1, "i", 100);
|
|
761
|
+
i0.ɵɵelementStart(2, "span", 217);
|
|
762
|
+
i0.ɵɵtext(3);
|
|
763
|
+
i0.ɵɵelementEnd()();
|
|
764
|
+
} if (rf & 2) {
|
|
765
|
+
const result_r26 = i0.ɵɵnextContext().ngIf;
|
|
766
|
+
const ctx_r0 = i0.ɵɵnextContext(7);
|
|
767
|
+
i0.ɵɵclassProp("rating-low", result_r26.humanRating <= 4)("rating-medium", result_r26.humanRating >= 5 && result_r26.humanRating <= 6)("rating-good", result_r26.humanRating >= 7 && result_r26.humanRating <= 8)("rating-excellent", result_r26.humanRating >= 9);
|
|
768
|
+
i0.ɵɵproperty("title", ctx_r0.getHumanTooltip(result_r26.humanRating, result_r26.humanComments));
|
|
769
|
+
i0.ɵɵadvance(3);
|
|
770
|
+
i0.ɵɵtextInterpolate(result_r26.humanRating);
|
|
771
|
+
} }
|
|
772
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_5_Template(rf, ctx) { if (rf & 1) {
|
|
773
|
+
i0.ɵɵelementStart(0, "span", 218);
|
|
774
|
+
i0.ɵɵelement(1, "i", 104);
|
|
775
|
+
i0.ɵɵelementStart(2, "span", 219);
|
|
776
|
+
i0.ɵɵtext(3);
|
|
777
|
+
i0.ɵɵelementEnd()();
|
|
778
|
+
} if (rf & 2) {
|
|
779
|
+
const result_r26 = i0.ɵɵnextContext().ngIf;
|
|
780
|
+
i0.ɵɵclassProp("score-low", result_r26.score < 0.5)("score-medium", result_r26.score >= 0.5 && result_r26.score < 0.7)("score-good", result_r26.score >= 0.7 && result_r26.score < 0.85)("score-excellent", result_r26.score >= 0.85);
|
|
781
|
+
i0.ɵɵproperty("title", "Auto Score: " + (result_r26.score * 100).toFixed(0) + "% automated evaluation");
|
|
782
|
+
i0.ɵɵadvance(3);
|
|
783
|
+
i0.ɵɵtextInterpolate((result_r26.score * 100).toFixed(0));
|
|
784
|
+
} }
|
|
785
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_6_Template(rf, ctx) { if (rf & 1) {
|
|
786
|
+
i0.ɵɵelementStart(0, "span", 220);
|
|
787
|
+
i0.ɵɵelement(1, "i", 104);
|
|
788
|
+
i0.ɵɵelementEnd();
|
|
789
|
+
} }
|
|
790
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_Template(rf, ctx) { if (rf & 1) {
|
|
791
|
+
i0.ɵɵelementContainerStart(0);
|
|
792
|
+
i0.ɵɵelementStart(1, "div", 207);
|
|
793
|
+
i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_2_Template, 2, 18, "span", 208)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_3_Template, 2, 0, "span", 209)(4, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_4_Template, 4, 10, "span", 210)(5, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_5_Template, 4, 10, "span", 211)(6, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_span_6_Template, 2, 0, "span", 212);
|
|
794
|
+
i0.ɵɵelementEnd();
|
|
795
|
+
i0.ɵɵelementContainerEnd();
|
|
796
|
+
} if (rf & 2) {
|
|
797
|
+
const result_r26 = ctx.ngIf;
|
|
798
|
+
const ctx_r0 = i0.ɵɵnextContext(7);
|
|
799
|
+
i0.ɵɵadvance(2);
|
|
800
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
|
|
801
|
+
i0.ɵɵadvance();
|
|
802
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman && !result_r26.humanRating);
|
|
803
|
+
i0.ɵɵadvance();
|
|
804
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman && result_r26.humanRating);
|
|
805
|
+
i0.ɵɵadvance();
|
|
806
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && result_r26.score != null);
|
|
807
|
+
i0.ɵɵadvance();
|
|
808
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto && result_r26.score == null);
|
|
809
|
+
} }
|
|
810
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
811
|
+
i0.ɵɵelementStart(0, "span", 221);
|
|
812
|
+
i0.ɵɵelement(1, "i", 222);
|
|
813
|
+
i0.ɵɵelementEnd();
|
|
814
|
+
} }
|
|
815
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template(rf, ctx) { if (rf & 1) {
|
|
816
|
+
const _r24 = i0.ɵɵgetCurrentView();
|
|
817
|
+
i0.ɵɵelementStart(0, "td", 205);
|
|
818
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template_td_click_0_listener($event) { const run_r25 = i0.ɵɵrestoreView(_r24).$implicit; const test_r23 = i0.ɵɵnextContext().$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.onMatrixCellClick(ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId), $event)); });
|
|
819
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_ng_container_1_Template, 7, 5, "ng-container", 75)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_span_2_Template, 2, 0, "span", 206);
|
|
820
|
+
i0.ɵɵelementEnd();
|
|
821
|
+
} if (rf & 2) {
|
|
822
|
+
let tmp_11_0;
|
|
823
|
+
const run_r25 = ctx.$implicit;
|
|
824
|
+
const test_r23 = i0.ɵɵnextContext().$implicit;
|
|
825
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
826
|
+
i0.ɵɵclassProp("clickable", ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId))("cell-not-run", !ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
|
|
827
|
+
i0.ɵɵproperty("ngClass", ctx_r0.getMatrixCellClass(ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId)))("title", ((tmp_11_0 = ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId)) == null ? null : tmp_11_0.status) + " - Click to view test run" || "Not Run");
|
|
828
|
+
i0.ɵɵadvance();
|
|
829
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
|
|
830
|
+
i0.ɵɵadvance();
|
|
831
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.getTestResultForRun(run_r25.runId, test_r23.testId));
|
|
832
|
+
} }
|
|
833
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template(rf, ctx) { if (rf & 1) {
|
|
834
|
+
const _r22 = i0.ɵɵgetCurrentView();
|
|
835
|
+
i0.ɵɵelementStart(0, "tr", 200);
|
|
836
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template_tr_click_0_listener() { const test_r23 = i0.ɵɵrestoreView(_r22).$implicit; const ctx_r0 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r0.selectMatrixRow(test_r23.testId)); });
|
|
837
|
+
i0.ɵɵelementStart(1, "td", 201);
|
|
838
|
+
i0.ɵɵtext(2);
|
|
839
|
+
i0.ɵɵelementEnd();
|
|
840
|
+
i0.ɵɵelementStart(3, "td", 202)(4, "span", 203);
|
|
841
|
+
i0.ɵɵtext(5);
|
|
842
|
+
i0.ɵɵelementEnd()();
|
|
843
|
+
i0.ɵɵtemplate(6, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_td_6_Template, 3, 8, "td", 204);
|
|
844
|
+
i0.ɵɵelement(7, "td", 187);
|
|
845
|
+
i0.ɵɵelementEnd();
|
|
846
|
+
} if (rf & 2) {
|
|
847
|
+
const test_r23 = ctx.$implicit;
|
|
848
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
849
|
+
i0.ɵɵclassProp("row-selected", ctx_r0.selectedMatrixTestId === test_r23.testId);
|
|
850
|
+
i0.ɵɵadvance(2);
|
|
851
|
+
i0.ɵɵtextInterpolate(test_r23.sequence);
|
|
852
|
+
i0.ɵɵadvance(2);
|
|
853
|
+
i0.ɵɵproperty("title", test_r23.testName);
|
|
854
|
+
i0.ɵɵadvance();
|
|
855
|
+
i0.ɵɵtextInterpolate(test_r23.testName);
|
|
856
|
+
i0.ɵɵadvance();
|
|
857
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
|
|
858
|
+
} }
|
|
859
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_2_Template(rf, ctx) { if (rf & 1) {
|
|
860
|
+
i0.ɵɵelementStart(0, "span", 228)(1, "span", 229);
|
|
861
|
+
i0.ɵɵtext(2);
|
|
862
|
+
i0.ɵɵelementEnd()();
|
|
863
|
+
} if (rf & 2) {
|
|
864
|
+
const run_r27 = i0.ɵɵnextContext().$implicit;
|
|
865
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
866
|
+
i0.ɵɵadvance(2);
|
|
867
|
+
i0.ɵɵtextInterpolate2("", ctx_r0.getRunPassedCount(run_r27), "/", ctx_r0.getRunTotalCount(run_r27), "");
|
|
868
|
+
} }
|
|
869
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
870
|
+
i0.ɵɵelementStart(0, "span", 233);
|
|
871
|
+
i0.ɵɵtext(1);
|
|
872
|
+
i0.ɵɵelementEnd();
|
|
873
|
+
} if (rf & 2) {
|
|
874
|
+
let tmp_8_0;
|
|
875
|
+
const run_r27 = i0.ɵɵnextContext(2).$implicit;
|
|
876
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
877
|
+
i0.ɵɵadvance();
|
|
878
|
+
i0.ɵɵtextInterpolate((tmp_8_0 = ctx_r0.getRunHumanAvg(run_r27)) == null ? null : tmp_8_0.toFixed(1));
|
|
879
|
+
} }
|
|
880
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_Template(rf, ctx) { if (rf & 1) {
|
|
881
|
+
i0.ɵɵelementStart(0, "span", 230);
|
|
882
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_span_1_Template, 2, 1, "span", 231);
|
|
883
|
+
i0.ɵɵelementStart(2, "span", 232);
|
|
884
|
+
i0.ɵɵtext(3);
|
|
885
|
+
i0.ɵɵelementEnd()();
|
|
886
|
+
} if (rf & 2) {
|
|
887
|
+
const run_r27 = i0.ɵɵnextContext().$implicit;
|
|
888
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
889
|
+
i0.ɵɵadvance();
|
|
890
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getRunHumanAvg(run_r27) != null);
|
|
891
|
+
i0.ɵɵadvance(2);
|
|
892
|
+
i0.ɵɵtextInterpolate1("(", ctx_r0.getRunHumanCount(run_r27), ")");
|
|
893
|
+
} }
|
|
894
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
895
|
+
i0.ɵɵelementStart(0, "span", 233);
|
|
896
|
+
i0.ɵɵtext(1);
|
|
897
|
+
i0.ɵɵelementEnd();
|
|
898
|
+
} if (rf & 2) {
|
|
899
|
+
const run_r27 = i0.ɵɵnextContext(2).$implicit;
|
|
900
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
901
|
+
i0.ɵɵadvance();
|
|
902
|
+
i0.ɵɵtextInterpolate1("", (ctx_r0.getRunAutoAvg(run_r27) * 100).toFixed(0), "%");
|
|
903
|
+
} }
|
|
904
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_Template(rf, ctx) { if (rf & 1) {
|
|
905
|
+
i0.ɵɵelementStart(0, "span", 234);
|
|
906
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_span_1_Template, 2, 1, "span", 231);
|
|
907
|
+
i0.ɵɵelementStart(2, "span", 232);
|
|
908
|
+
i0.ɵɵtext(3);
|
|
909
|
+
i0.ɵɵelementEnd()();
|
|
910
|
+
} if (rf & 2) {
|
|
911
|
+
const run_r27 = i0.ɵɵnextContext().$implicit;
|
|
912
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
913
|
+
i0.ɵɵadvance();
|
|
914
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getRunAutoAvg(run_r27) != null);
|
|
915
|
+
i0.ɵɵadvance(2);
|
|
916
|
+
i0.ɵɵtextInterpolate1("(", ctx_r0.getRunAutoCount(run_r27), ")");
|
|
917
|
+
} }
|
|
918
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_Template(rf, ctx) { if (rf & 1) {
|
|
919
|
+
i0.ɵɵelementStart(0, "td", 223)(1, "div", 224);
|
|
920
|
+
i0.ɵɵtemplate(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_2_Template, 3, 2, "span", 225)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_3_Template, 4, 2, "span", 226)(4, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_span_4_Template, 4, 2, "span", 227);
|
|
921
|
+
i0.ɵɵelementEnd()();
|
|
922
|
+
} if (rf & 2) {
|
|
923
|
+
const ctx_r0 = i0.ɵɵnextContext(5);
|
|
924
|
+
i0.ɵɵadvance(2);
|
|
925
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showExecution);
|
|
926
|
+
i0.ɵɵadvance();
|
|
927
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showHuman);
|
|
928
|
+
i0.ɵɵadvance();
|
|
929
|
+
i0.ɵɵproperty("ngIf", ctx_r0.evalPreferences.showAuto);
|
|
930
|
+
} }
|
|
931
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
932
|
+
const _r17 = i0.ɵɵgetCurrentView();
|
|
933
|
+
i0.ɵɵelementStart(0, "div", 166)(1, "div", 167)(2, "h3");
|
|
934
|
+
i0.ɵɵelement(3, "i", 115);
|
|
935
|
+
i0.ɵɵtext(4, " Test Results Matrix");
|
|
936
|
+
i0.ɵɵelementEnd();
|
|
937
|
+
i0.ɵɵelementStart(5, "div", 168)(6, "div", 169);
|
|
938
|
+
i0.ɵɵelement(7, "i", 170);
|
|
939
|
+
i0.ɵɵelementStart(8, "input", 171);
|
|
940
|
+
i0.ɵɵlistener("input", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_input_input_8_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.onMatrixFilterInput($event)); });
|
|
941
|
+
i0.ɵɵelementEnd();
|
|
942
|
+
i0.ɵɵtemplate(9, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_button_9_Template, 2, 0, "button", 172);
|
|
943
|
+
i0.ɵɵelementEnd();
|
|
944
|
+
i0.ɵɵelementStart(10, "span", 173);
|
|
945
|
+
i0.ɵɵtext(11);
|
|
946
|
+
i0.ɵɵelementEnd();
|
|
947
|
+
i0.ɵɵelementStart(12, "button", 174);
|
|
948
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.exportMatrixToCSV()); });
|
|
949
|
+
i0.ɵɵelement(13, "i", 175);
|
|
950
|
+
i0.ɵɵtext(14, " Export ");
|
|
951
|
+
i0.ɵɵelementEnd()()();
|
|
952
|
+
i0.ɵɵelementStart(15, "div", 176)(16, "table", 177)(17, "thead")(18, "tr")(19, "th", 178);
|
|
953
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_th_click_19_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleMatrixSort("sequence")); });
|
|
954
|
+
i0.ɵɵtext(20, " # ");
|
|
955
|
+
i0.ɵɵelement(21, "i", 10);
|
|
956
|
+
i0.ɵɵelementEnd();
|
|
957
|
+
i0.ɵɵelementStart(22, "th", 179);
|
|
958
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template_th_click_22_listener() { i0.ɵɵrestoreView(_r17); const ctx_r0 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r0.toggleMatrixSort("name")); });
|
|
959
|
+
i0.ɵɵtext(23, " Test ");
|
|
960
|
+
i0.ɵɵelement(24, "i", 10);
|
|
961
|
+
i0.ɵɵelementEnd();
|
|
962
|
+
i0.ɵɵtemplate(25, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_th_25_Template, 8, 12, "th", 180);
|
|
963
|
+
i0.ɵɵelement(26, "th", 181);
|
|
964
|
+
i0.ɵɵelementEnd()();
|
|
965
|
+
i0.ɵɵelementStart(27, "tbody");
|
|
966
|
+
i0.ɵɵtemplate(28, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_tr_28_Template, 8, 6, "tr", 182);
|
|
967
|
+
i0.ɵɵelementEnd();
|
|
968
|
+
i0.ɵɵelementStart(29, "tfoot")(30, "tr", 183);
|
|
969
|
+
i0.ɵɵelement(31, "td", 184);
|
|
970
|
+
i0.ɵɵelementStart(32, "td", 185)(33, "strong");
|
|
971
|
+
i0.ɵɵtext(34, "Totals");
|
|
972
|
+
i0.ɵɵelementEnd()();
|
|
973
|
+
i0.ɵɵtemplate(35, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_td_35_Template, 5, 3, "td", 186);
|
|
974
|
+
i0.ɵɵelement(36, "td", 187);
|
|
975
|
+
i0.ɵɵelementEnd()()()()();
|
|
976
|
+
} if (rf & 2) {
|
|
977
|
+
const ctx_r0 = i0.ɵɵnextContext(4);
|
|
978
|
+
i0.ɵɵadvance(8);
|
|
979
|
+
i0.ɵɵproperty("value", ctx_r0.matrixTestFilter);
|
|
980
|
+
i0.ɵɵadvance();
|
|
981
|
+
i0.ɵɵproperty("ngIf", ctx_r0.matrixTestFilter);
|
|
982
|
+
i0.ɵɵadvance(2);
|
|
983
|
+
i0.ɵɵtextInterpolate2("", ctx_r0.matrixData.length, " runs \u00B7 ", ctx_r0.getUniqueTestsFromMatrix().length, " tests");
|
|
984
|
+
i0.ɵɵadvance();
|
|
985
|
+
i0.ɵɵproperty("disabled", ctx_r0.matrixData.length === 0);
|
|
986
|
+
i0.ɵɵadvance(9);
|
|
987
|
+
i0.ɵɵproperty("ngClass", ctx_r0.matrixSortBy === "sequence" ? ctx_r0.matrixSortAsc ? "fa-sort-up" : "fa-sort-down" : "fa-sort");
|
|
988
|
+
i0.ɵɵadvance(3);
|
|
989
|
+
i0.ɵɵproperty("ngClass", ctx_r0.matrixSortBy === "name" ? ctx_r0.matrixSortAsc ? "fa-sort-up" : "fa-sort-down" : "fa-sort");
|
|
990
|
+
i0.ɵɵadvance();
|
|
991
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
|
|
992
|
+
i0.ɵɵadvance(3);
|
|
993
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.getUniqueTestsFromMatrix());
|
|
994
|
+
i0.ɵɵadvance(7);
|
|
995
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.matrixData);
|
|
996
|
+
} }
|
|
997
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
998
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
999
|
+
i0.ɵɵelement(2, "i", 115);
|
|
1000
|
+
i0.ɵɵelementEnd();
|
|
1001
|
+
i0.ɵɵelementStart(3, "h4");
|
|
1002
|
+
i0.ɵɵtext(4, "No Matrix Data");
|
|
1003
|
+
i0.ɵɵelementEnd();
|
|
1004
|
+
i0.ɵɵelementStart(5, "p");
|
|
1005
|
+
i0.ɵɵtext(6, "No suite runs match the current filters.");
|
|
1006
|
+
i0.ɵɵelementEnd()();
|
|
1007
|
+
} }
|
|
1008
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_Template(rf, ctx) { if (rf & 1) {
|
|
1009
|
+
i0.ɵɵelementContainerStart(0);
|
|
1010
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_2_Template, 37, 10, "div", 164)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_div_3_Template, 7, 0, "div", 57);
|
|
1011
|
+
i0.ɵɵelementContainerEnd();
|
|
1012
|
+
} if (rf & 2) {
|
|
1013
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1014
|
+
i0.ɵɵadvance();
|
|
1015
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingMatrix);
|
|
1016
|
+
i0.ɵɵadvance();
|
|
1017
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length > 0);
|
|
1018
|
+
i0.ɵɵadvance();
|
|
1019
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length === 0);
|
|
1020
|
+
} }
|
|
1021
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
1022
|
+
i0.ɵɵelementStart(0, "div", 58);
|
|
1023
|
+
i0.ɵɵelement(1, "mj-loading", 236);
|
|
1024
|
+
i0.ɵɵelementEnd();
|
|
1025
|
+
} }
|
|
1026
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
1027
|
+
i0.ɵɵelementStart(0, "div", 237)(1, "div", 238)(2, "h3");
|
|
1028
|
+
i0.ɵɵelement(3, "i", 116);
|
|
1029
|
+
i0.ɵɵtext(4, " Test Results Flow");
|
|
1030
|
+
i0.ɵɵelementEnd();
|
|
1031
|
+
i0.ɵɵelementStart(5, "div", 239)(6, "span", 240);
|
|
1032
|
+
i0.ɵɵelement(7, "i", 136);
|
|
1033
|
+
i0.ɵɵtext(8, " Passed");
|
|
1034
|
+
i0.ɵɵelementEnd();
|
|
1035
|
+
i0.ɵɵelementStart(9, "span", 241);
|
|
1036
|
+
i0.ɵɵelement(10, "i", 189);
|
|
1037
|
+
i0.ɵɵtext(11, " Failed");
|
|
1038
|
+
i0.ɵɵelementEnd();
|
|
1039
|
+
i0.ɵɵelementStart(12, "span", 242);
|
|
1040
|
+
i0.ɵɵelement(13, "i", 243);
|
|
1041
|
+
i0.ɵɵtext(14, " Error");
|
|
1042
|
+
i0.ɵɵelementEnd();
|
|
1043
|
+
i0.ɵɵelementStart(15, "span", 244);
|
|
1044
|
+
i0.ɵɵelement(16, "i", 245);
|
|
1045
|
+
i0.ɵɵtext(17, " Skipped");
|
|
1046
|
+
i0.ɵɵelementEnd()()();
|
|
1047
|
+
i0.ɵɵelementStart(18, "div", 246);
|
|
1048
|
+
i0.ɵɵelement(19, "div", 247, 0);
|
|
1049
|
+
i0.ɵɵelementEnd();
|
|
1050
|
+
i0.ɵɵelementStart(21, "div", 248);
|
|
1051
|
+
i0.ɵɵelement(22, "i", 42);
|
|
1052
|
+
i0.ɵɵtext(23);
|
|
1053
|
+
i0.ɵɵelementEnd()();
|
|
1054
|
+
} if (rf & 2) {
|
|
1055
|
+
const ctx_r0 = i0.ɵɵnextContext(4);
|
|
1056
|
+
i0.ɵɵadvance(23);
|
|
1057
|
+
i0.ɵɵtextInterpolate1(" Interactive visualization showing test results across ", ctx_r0.matrixData.length, " runs. Hover over elements for details, click nodes to navigate. ");
|
|
1058
|
+
} }
|
|
1059
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
1060
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
1061
|
+
i0.ɵɵelement(2, "i", 116);
|
|
1062
|
+
i0.ɵɵelementEnd();
|
|
1063
|
+
i0.ɵɵelementStart(3, "h4");
|
|
1064
|
+
i0.ɵɵtext(4, "No Chart Data");
|
|
1065
|
+
i0.ɵɵelementEnd();
|
|
1066
|
+
i0.ɵɵelementStart(5, "p");
|
|
1067
|
+
i0.ɵɵtext(6, "No suite runs match the current filters.");
|
|
1068
|
+
i0.ɵɵelementEnd()();
|
|
1069
|
+
} }
|
|
1070
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_Template(rf, ctx) { if (rf & 1) {
|
|
1071
|
+
i0.ɵɵelementContainerStart(0);
|
|
1072
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_2_Template, 24, 1, "div", 235)(3, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_div_3_Template, 7, 0, "div", 57);
|
|
1073
|
+
i0.ɵɵelementContainerEnd();
|
|
1074
|
+
} if (rf & 2) {
|
|
1075
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1076
|
+
i0.ɵɵadvance();
|
|
1077
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingMatrix);
|
|
1078
|
+
i0.ɵɵadvance();
|
|
1079
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length > 0);
|
|
1080
|
+
i0.ɵɵadvance();
|
|
1081
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingMatrix && ctx_r0.matrixLoaded && ctx_r0.matrixData.length === 0);
|
|
1082
|
+
} }
|
|
1083
|
+
function TestSuiteFormComponentExtended_div_49_ng_container_2_Template(rf, ctx) { if (rf & 1) {
|
|
1084
|
+
const _r9 = i0.ɵɵgetCurrentView();
|
|
1085
|
+
i0.ɵɵelementContainerStart(0);
|
|
1086
|
+
i0.ɵɵelementStart(1, "div", 111)(2, "div", 112)(3, "button", 113);
|
|
1087
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("summary")); });
|
|
1088
|
+
i0.ɵɵelement(4, "i", 114);
|
|
1089
|
+
i0.ɵɵelementStart(5, "span");
|
|
1090
|
+
i0.ɵɵtext(6, "Summary");
|
|
1091
|
+
i0.ɵɵelementEnd()();
|
|
1092
|
+
i0.ɵɵelementStart(7, "button", 113);
|
|
1093
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("matrix")); });
|
|
1094
|
+
i0.ɵɵelement(8, "i", 115);
|
|
1095
|
+
i0.ɵɵelementStart(9, "span");
|
|
1096
|
+
i0.ɵɵtext(10, "Matrix");
|
|
1097
|
+
i0.ɵɵelementEnd()();
|
|
1098
|
+
i0.ɵɵelementStart(11, "button", 113);
|
|
1099
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.setAnalyticsView("chart")); });
|
|
1100
|
+
i0.ɵɵelement(12, "i", 116);
|
|
1101
|
+
i0.ɵɵelementStart(13, "span");
|
|
1102
|
+
i0.ɵɵtext(14, "Chart");
|
|
1103
|
+
i0.ɵɵelementEnd()()()();
|
|
1104
|
+
i0.ɵɵelementStart(15, "div", 117)(16, "div", 118);
|
|
1105
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_ng_container_2_Template_div_click_16_listener() { i0.ɵɵrestoreView(_r9); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.toggleFilters()); });
|
|
1106
|
+
i0.ɵɵelementStart(17, "span", 119);
|
|
1107
|
+
i0.ɵɵelement(18, "i", 120);
|
|
1108
|
+
i0.ɵɵtext(19, " Filters ");
|
|
1109
|
+
i0.ɵɵtemplate(20, TestSuiteFormComponentExtended_div_49_ng_container_2_span_20_Template, 3, 2, "span", 121);
|
|
1110
|
+
i0.ɵɵelementEnd();
|
|
1111
|
+
i0.ɵɵelement(21, "i", 10);
|
|
1112
|
+
i0.ɵɵelementEnd();
|
|
1113
|
+
i0.ɵɵtemplate(22, TestSuiteFormComponentExtended_div_49_ng_container_2_div_22_Template, 14, 9, "div", 122);
|
|
1114
|
+
i0.ɵɵelementEnd();
|
|
1115
|
+
i0.ɵɵtemplate(23, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_23_Template, 62, 16, "ng-container", 75)(24, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_24_Template, 4, 3, "ng-container", 75)(25, TestSuiteFormComponentExtended_div_49_ng_container_2_ng_container_25_Template, 4, 3, "ng-container", 75);
|
|
1116
|
+
i0.ɵɵelementContainerEnd();
|
|
1117
|
+
} if (rf & 2) {
|
|
1118
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1119
|
+
i0.ɵɵadvance(3);
|
|
1120
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsView === "summary");
|
|
1121
|
+
i0.ɵɵadvance(4);
|
|
1122
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsView === "matrix");
|
|
1123
|
+
i0.ɵɵadvance(4);
|
|
1124
|
+
i0.ɵɵclassProp("active", ctx_r0.analyticsView === "chart");
|
|
1125
|
+
i0.ɵɵadvance(4);
|
|
1126
|
+
i0.ɵɵclassProp("collapsed", ctx_r0.filtersCollapsed);
|
|
1127
|
+
i0.ɵɵadvance(5);
|
|
1128
|
+
i0.ɵɵproperty("ngIf", ctx_r0.filtersCollapsed);
|
|
1129
|
+
i0.ɵɵadvance();
|
|
1130
|
+
i0.ɵɵproperty("ngClass", ctx_r0.filtersCollapsed ? "fa-chevron-down" : "fa-chevron-up");
|
|
1131
|
+
i0.ɵɵadvance();
|
|
1132
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.filtersCollapsed);
|
|
1133
|
+
i0.ɵɵadvance();
|
|
1134
|
+
i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "summary");
|
|
1135
|
+
i0.ɵɵadvance();
|
|
1136
|
+
i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "matrix");
|
|
1137
|
+
i0.ɵɵadvance();
|
|
1138
|
+
i0.ɵɵproperty("ngIf", ctx_r0.analyticsView === "chart");
|
|
1139
|
+
} }
|
|
1140
|
+
function TestSuiteFormComponentExtended_div_49_div_3_Template(rf, ctx) { if (rf & 1) {
|
|
1141
|
+
const _r28 = i0.ɵɵgetCurrentView();
|
|
1142
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
1143
|
+
i0.ɵɵelement(2, "i", 26);
|
|
1144
|
+
i0.ɵɵelementEnd();
|
|
1145
|
+
i0.ɵɵelementStart(3, "h4");
|
|
1146
|
+
i0.ɵɵtext(4, "No Analytics Data");
|
|
1147
|
+
i0.ɵɵelementEnd();
|
|
1148
|
+
i0.ɵɵelementStart(5, "p");
|
|
1149
|
+
i0.ɵɵtext(6, "Run this suite to start collecting analytics data.");
|
|
1150
|
+
i0.ɵɵelementEnd();
|
|
1151
|
+
i0.ɵɵelementStart(7, "button", 15);
|
|
1152
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_49_div_3_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r28); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
|
|
1153
|
+
i0.ɵɵelement(8, "i", 16);
|
|
1154
|
+
i0.ɵɵtext(9, " Run Suite Now ");
|
|
1155
|
+
i0.ɵɵelementEnd()();
|
|
1156
|
+
} }
|
|
1157
|
+
function TestSuiteFormComponentExtended_div_49_Template(rf, ctx) { if (rf & 1) {
|
|
1158
|
+
i0.ɵɵelementStart(0, "div", 109);
|
|
1159
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_49_div_1_Template, 2, 0, "div", 55)(2, TestSuiteFormComponentExtended_div_49_ng_container_2_Template, 26, 14, "ng-container", 75)(3, TestSuiteFormComponentExtended_div_49_div_3_Template, 10, 0, "div", 57);
|
|
1160
|
+
i0.ɵɵelementEnd();
|
|
1161
|
+
} if (rf & 2) {
|
|
1162
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
1163
|
+
i0.ɵɵadvance();
|
|
1164
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingAnalytics);
|
|
1165
|
+
i0.ɵɵadvance();
|
|
1166
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingAnalytics && ctx_r0.analyticsLoaded);
|
|
1167
|
+
i0.ɵɵadvance();
|
|
1168
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingAnalytics && ctx_r0.analyticsLoaded && ctx_r0.analyticsData.length === 0);
|
|
1169
|
+
} }
|
|
1170
|
+
function TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
1171
|
+
i0.ɵɵelementStart(0, "span", 268);
|
|
1172
|
+
i0.ɵɵtext(1);
|
|
1173
|
+
i0.ɵɵelementEnd();
|
|
1174
|
+
} if (rf & 2) {
|
|
1175
|
+
const tag_r31 = ctx.$implicit;
|
|
1176
|
+
i0.ɵɵadvance();
|
|
1177
|
+
i0.ɵɵtextInterpolate(tag_r31);
|
|
1178
|
+
} }
|
|
1179
|
+
function TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_Template(rf, ctx) { if (rf & 1) {
|
|
1180
|
+
i0.ɵɵelementStart(0, "div", 266);
|
|
1181
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_span_1_Template, 2, 1, "span", 267);
|
|
1182
|
+
i0.ɵɵelementEnd();
|
|
1183
|
+
} if (rf & 2) {
|
|
1184
|
+
const run_r30 = i0.ɵɵnextContext().$implicit;
|
|
1185
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1186
|
+
i0.ɵɵadvance();
|
|
1187
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r30).slice(0, 2));
|
|
1188
|
+
} }
|
|
1189
|
+
function TestSuiteFormComponentExtended_div_50_div_5_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
1190
|
+
const _r29 = i0.ɵɵgetCurrentView();
|
|
1191
|
+
i0.ɵɵelementStart(0, "div", 260);
|
|
1192
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_5_div_1_Template_div_click_0_listener() { const run_r30 = i0.ɵɵrestoreView(_r29).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.selectCompareRunA(run_r30)); });
|
|
1193
|
+
i0.ɵɵelement(1, "div", 261);
|
|
1194
|
+
i0.ɵɵelementStart(2, "div", 262)(3, "div", 263);
|
|
1195
|
+
i0.ɵɵtext(4);
|
|
1196
|
+
i0.ɵɵpipe(5, "date");
|
|
1197
|
+
i0.ɵɵelementEnd();
|
|
1198
|
+
i0.ɵɵelementStart(6, "div", 264);
|
|
1199
|
+
i0.ɵɵtext(7);
|
|
1200
|
+
i0.ɵɵelementEnd()();
|
|
1201
|
+
i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_50_div_5_div_1_div_8_Template, 2, 1, "div", 265);
|
|
1202
|
+
i0.ɵɵelementEnd();
|
|
1203
|
+
} if (rf & 2) {
|
|
1204
|
+
const run_r30 = ctx.$implicit;
|
|
1205
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1206
|
+
i0.ɵɵclassProp("selected", (ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) === run_r30.ID);
|
|
1207
|
+
i0.ɵɵadvance();
|
|
1208
|
+
i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r30.Status));
|
|
1209
|
+
i0.ɵɵadvance(3);
|
|
1210
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(5, 7, run_r30.StartedAt, "short"));
|
|
1211
|
+
i0.ɵɵadvance(3);
|
|
1212
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r30).toFixed(0), "% pass");
|
|
1213
|
+
i0.ɵɵadvance();
|
|
1214
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r30).length > 0);
|
|
1215
|
+
} }
|
|
1216
|
+
function TestSuiteFormComponentExtended_div_50_div_5_Template(rf, ctx) { if (rf & 1) {
|
|
1217
|
+
i0.ɵɵelementStart(0, "div", 258);
|
|
1218
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_5_div_1_Template, 9, 10, "div", 259);
|
|
1219
|
+
i0.ɵɵelementEnd();
|
|
1220
|
+
} if (rf & 2) {
|
|
1221
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1222
|
+
i0.ɵɵadvance();
|
|
1223
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
|
|
1224
|
+
} }
|
|
1225
|
+
function TestSuiteFormComponentExtended_div_50_div_6_Template(rf, ctx) { if (rf & 1) {
|
|
1226
|
+
const _r32 = i0.ɵɵgetCurrentView();
|
|
1227
|
+
i0.ɵɵelementStart(0, "div", 269)(1, "div", 270)(2, "span", 271);
|
|
1228
|
+
i0.ɵɵtext(3, "Selected:");
|
|
1229
|
+
i0.ɵɵelementEnd();
|
|
1230
|
+
i0.ɵɵelementStart(4, "button", 272);
|
|
1231
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_6_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r32); const ctx_r0 = i0.ɵɵnextContext(2); ctx_r0.compareRunA = null; return i0.ɵɵresetView(ctx_r0.compareResults = []); });
|
|
1232
|
+
i0.ɵɵtext(5, "Clear");
|
|
1233
|
+
i0.ɵɵelementEnd()();
|
|
1234
|
+
i0.ɵɵelementStart(6, "div", 273)(7, "span");
|
|
1235
|
+
i0.ɵɵtext(8);
|
|
1236
|
+
i0.ɵɵpipe(9, "date");
|
|
1237
|
+
i0.ɵɵelementEnd();
|
|
1238
|
+
i0.ɵɵelementStart(10, "span", 274);
|
|
1239
|
+
i0.ɵɵtext(11);
|
|
1240
|
+
i0.ɵɵelementEnd()()();
|
|
1241
|
+
} if (rf & 2) {
|
|
1242
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1243
|
+
i0.ɵɵadvance(8);
|
|
1244
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(9, 2, ctx_r0.compareRunA.StartedAt, "medium"));
|
|
1245
|
+
i0.ɵɵadvance(3);
|
|
1246
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(ctx_r0.compareRunA).toFixed(1), "%");
|
|
1247
|
+
} }
|
|
1248
|
+
function TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_span_1_Template(rf, ctx) { if (rf & 1) {
|
|
1249
|
+
i0.ɵɵelementStart(0, "span", 268);
|
|
1250
|
+
i0.ɵɵtext(1);
|
|
1251
|
+
i0.ɵɵelementEnd();
|
|
1252
|
+
} if (rf & 2) {
|
|
1253
|
+
const tag_r35 = ctx.$implicit;
|
|
1254
|
+
i0.ɵɵadvance();
|
|
1255
|
+
i0.ɵɵtextInterpolate(tag_r35);
|
|
1256
|
+
} }
|
|
1257
|
+
function TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_Template(rf, ctx) { if (rf & 1) {
|
|
1258
|
+
i0.ɵɵelementStart(0, "div", 266);
|
|
1259
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_span_1_Template, 2, 1, "span", 267);
|
|
1260
|
+
i0.ɵɵelementEnd();
|
|
1261
|
+
} if (rf & 2) {
|
|
1262
|
+
const run_r34 = i0.ɵɵnextContext().$implicit;
|
|
1263
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1264
|
+
i0.ɵɵadvance();
|
|
1265
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.getRunTags(run_r34).slice(0, 2));
|
|
1266
|
+
} }
|
|
1267
|
+
function TestSuiteFormComponentExtended_div_50_div_12_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
1268
|
+
const _r33 = i0.ɵɵgetCurrentView();
|
|
1269
|
+
i0.ɵɵelementStart(0, "div", 260);
|
|
1270
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_12_div_1_Template_div_click_0_listener() { const run_r34 = i0.ɵɵrestoreView(_r33).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView((ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) !== run_r34.ID && ctx_r0.selectCompareRunB(run_r34)); });
|
|
1271
|
+
i0.ɵɵelement(1, "div", 261);
|
|
1272
|
+
i0.ɵɵelementStart(2, "div", 262)(3, "div", 263);
|
|
1273
|
+
i0.ɵɵtext(4);
|
|
1274
|
+
i0.ɵɵpipe(5, "date");
|
|
1275
|
+
i0.ɵɵelementEnd();
|
|
1276
|
+
i0.ɵɵelementStart(6, "div", 264);
|
|
1277
|
+
i0.ɵɵtext(7);
|
|
1278
|
+
i0.ɵɵelementEnd()();
|
|
1279
|
+
i0.ɵɵtemplate(8, TestSuiteFormComponentExtended_div_50_div_12_div_1_div_8_Template, 2, 1, "div", 265);
|
|
1280
|
+
i0.ɵɵelementEnd();
|
|
1281
|
+
} if (rf & 2) {
|
|
1282
|
+
const run_r34 = ctx.$implicit;
|
|
1283
|
+
const ctx_r0 = i0.ɵɵnextContext(3);
|
|
1284
|
+
i0.ɵɵclassProp("selected", (ctx_r0.compareRunB == null ? null : ctx_r0.compareRunB.ID) === run_r34.ID)("disabled", (ctx_r0.compareRunA == null ? null : ctx_r0.compareRunA.ID) === run_r34.ID);
|
|
1285
|
+
i0.ɵɵadvance();
|
|
1286
|
+
i0.ɵɵstyleProp("background-color", ctx_r0.getRunStatusColor(run_r34.Status));
|
|
1287
|
+
i0.ɵɵadvance(3);
|
|
1288
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(5, 9, run_r34.StartedAt, "short"));
|
|
1289
|
+
i0.ɵɵadvance(3);
|
|
1290
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(run_r34).toFixed(0), "% pass");
|
|
1291
|
+
i0.ɵɵadvance();
|
|
1292
|
+
i0.ɵɵproperty("ngIf", ctx_r0.getRunTags(run_r34).length > 0);
|
|
1293
|
+
} }
|
|
1294
|
+
function TestSuiteFormComponentExtended_div_50_div_12_Template(rf, ctx) { if (rf & 1) {
|
|
1295
|
+
i0.ɵɵelementStart(0, "div", 258);
|
|
1296
|
+
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_50_div_12_div_1_Template, 9, 12, "div", 275);
|
|
1297
|
+
i0.ɵɵelementEnd();
|
|
1298
|
+
} if (rf & 2) {
|
|
1299
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1300
|
+
i0.ɵɵadvance();
|
|
1301
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.suiteRuns);
|
|
1302
|
+
} }
|
|
1303
|
+
function TestSuiteFormComponentExtended_div_50_div_13_Template(rf, ctx) { if (rf & 1) {
|
|
1304
|
+
const _r36 = i0.ɵɵgetCurrentView();
|
|
1305
|
+
i0.ɵɵelementStart(0, "div", 269)(1, "div", 270)(2, "span", 271);
|
|
1306
|
+
i0.ɵɵtext(3, "Selected:");
|
|
1307
|
+
i0.ɵɵelementEnd();
|
|
1308
|
+
i0.ɵɵelementStart(4, "button", 272);
|
|
1309
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_13_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r36); const ctx_r0 = i0.ɵɵnextContext(2); ctx_r0.compareRunB = null; return i0.ɵɵresetView(ctx_r0.compareResults = []); });
|
|
1310
|
+
i0.ɵɵtext(5, "Clear");
|
|
1311
|
+
i0.ɵɵelementEnd()();
|
|
1312
|
+
i0.ɵɵelementStart(6, "div", 273)(7, "span");
|
|
1313
|
+
i0.ɵɵtext(8);
|
|
1314
|
+
i0.ɵɵpipe(9, "date");
|
|
1315
|
+
i0.ɵɵelementEnd();
|
|
1316
|
+
i0.ɵɵelementStart(10, "span", 274);
|
|
1317
|
+
i0.ɵɵtext(11);
|
|
1318
|
+
i0.ɵɵelementEnd()()();
|
|
1319
|
+
} if (rf & 2) {
|
|
1320
|
+
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1321
|
+
i0.ɵɵadvance(8);
|
|
1322
|
+
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(9, 2, ctx_r0.compareRunB.StartedAt, "medium"));
|
|
1323
|
+
i0.ɵɵadvance(3);
|
|
1324
|
+
i0.ɵɵtextInterpolate1("", ctx_r0.getPassRate(ctx_r0.compareRunB).toFixed(1), "%");
|
|
1325
|
+
} }
|
|
1326
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_4_Template(rf, ctx) { if (rf & 1) {
|
|
1327
|
+
i0.ɵɵelementStart(0, "span", 155);
|
|
1328
|
+
i0.ɵɵtext(1);
|
|
1329
|
+
i0.ɵɵelementEnd();
|
|
1330
|
+
} if (rf & 2) {
|
|
1331
|
+
const result_r37 = i0.ɵɵnextContext().$implicit;
|
|
1332
|
+
i0.ɵɵproperty("ngClass", "status-" + result_r37.runA.status.toLowerCase());
|
|
1333
|
+
i0.ɵɵadvance();
|
|
1334
|
+
i0.ɵɵtextInterpolate(result_r37.runA.status);
|
|
1335
|
+
} }
|
|
1336
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_5_Template(rf, ctx) { if (rf & 1) {
|
|
1337
|
+
i0.ɵɵelementStart(0, "span", 297);
|
|
1338
|
+
i0.ɵɵtext(1, "N/A");
|
|
1339
|
+
i0.ɵɵelementEnd();
|
|
1340
|
+
} }
|
|
1341
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_7_Template(rf, ctx) { if (rf & 1) {
|
|
1342
|
+
i0.ɵɵelementStart(0, "span", 155);
|
|
1343
|
+
i0.ɵɵtext(1);
|
|
1344
|
+
i0.ɵɵelementEnd();
|
|
1345
|
+
} if (rf & 2) {
|
|
1346
|
+
const result_r37 = i0.ɵɵnextContext().$implicit;
|
|
1347
|
+
i0.ɵɵproperty("ngClass", "status-" + result_r37.runB.status.toLowerCase());
|
|
1348
|
+
i0.ɵɵadvance();
|
|
1349
|
+
i0.ɵɵtextInterpolate(result_r37.runB.status);
|
|
1350
|
+
} }
|
|
1351
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_8_Template(rf, ctx) { if (rf & 1) {
|
|
1352
|
+
i0.ɵɵelementStart(0, "span", 297);
|
|
1353
|
+
i0.ɵɵtext(1, "N/A");
|
|
1354
|
+
i0.ɵɵelementEnd();
|
|
1355
|
+
} }
|
|
1356
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_10_Template(rf, ctx) { if (rf & 1) {
|
|
1357
|
+
i0.ɵɵelementStart(0, "span", 289);
|
|
1358
|
+
i0.ɵɵtext(1);
|
|
1359
|
+
i0.ɵɵelementEnd();
|
|
1360
|
+
} if (rf & 2) {
|
|
1361
|
+
const result_r37 = i0.ɵɵnextContext().$implicit;
|
|
1362
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(3, _c6, result_r37.scoreDiff > 0, result_r37.scoreDiff < 0));
|
|
1363
|
+
i0.ɵɵadvance();
|
|
1364
|
+
i0.ɵɵtextInterpolate2(" ", result_r37.scoreDiff > 0 ? "+" : "", "", (result_r37.scoreDiff * 100).toFixed(1), "% ");
|
|
1365
|
+
} }
|
|
1366
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_11_Template(rf, ctx) { if (rf & 1) {
|
|
1367
|
+
i0.ɵɵelementStart(0, "span", 298);
|
|
1368
|
+
i0.ɵɵtext(1, "-");
|
|
1369
|
+
i0.ɵɵelementEnd();
|
|
1370
|
+
} }
|
|
1371
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_13_Template(rf, ctx) { if (rf & 1) {
|
|
1372
|
+
i0.ɵɵelementStart(0, "span", 289);
|
|
1373
|
+
i0.ɵɵtext(1);
|
|
1374
|
+
i0.ɵɵelementEnd();
|
|
1375
|
+
} if (rf & 2) {
|
|
1376
|
+
const result_r37 = i0.ɵɵnextContext().$implicit;
|
|
1377
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(3, _c6, result_r37.durationDiff < 0, result_r37.durationDiff > 0));
|
|
1378
|
+
i0.ɵɵadvance();
|
|
1379
|
+
i0.ɵɵtextInterpolate2(" ", result_r37.durationDiff > 0 ? "+" : "", "", result_r37.durationDiff.toFixed(1), "s ");
|
|
1380
|
+
} }
|
|
1381
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_14_Template(rf, ctx) { if (rf & 1) {
|
|
1382
|
+
i0.ɵɵelementStart(0, "span", 298);
|
|
1383
|
+
i0.ɵɵtext(1, "-");
|
|
1384
|
+
i0.ɵɵelementEnd();
|
|
1385
|
+
} }
|
|
1386
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_16_Template(rf, ctx) { if (rf & 1) {
|
|
1387
|
+
i0.ɵɵelementStart(0, "span", 299);
|
|
1388
|
+
i0.ɵɵelement(1, "i", 300);
|
|
1389
|
+
i0.ɵɵtext(2, " Fixed ");
|
|
1390
|
+
i0.ɵɵelementEnd();
|
|
1391
|
+
} }
|
|
1392
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_17_Template(rf, ctx) { if (rf & 1) {
|
|
1393
|
+
i0.ɵɵelementStart(0, "span", 301);
|
|
1394
|
+
i0.ɵɵelement(1, "i", 302);
|
|
1395
|
+
i0.ɵɵtext(2, " Broke ");
|
|
1396
|
+
i0.ɵɵelementEnd();
|
|
1397
|
+
} }
|
|
1398
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_18_Template(rf, ctx) { if (rf & 1) {
|
|
1399
|
+
i0.ɵɵelementStart(0, "span", 303);
|
|
1400
|
+
i0.ɵɵelement(1, "i", 222);
|
|
1401
|
+
i0.ɵɵelementEnd();
|
|
1402
|
+
} }
|
|
1403
|
+
function TestSuiteFormComponentExtended_div_50_div_14_tr_45_Template(rf, ctx) { if (rf & 1) {
|
|
1404
|
+
i0.ɵɵelementStart(0, "tr", 289)(1, "td", 202);
|
|
1405
|
+
i0.ɵɵtext(2);
|
|
1406
|
+
i0.ɵɵelementEnd();
|
|
1407
|
+
i0.ɵɵelementStart(3, "td");
|
|
1408
|
+
i0.ɵɵtemplate(4, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_4_Template, 2, 2, "span", 290)(5, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_5_Template, 2, 0, "span", 291);
|
|
1409
|
+
i0.ɵɵelementEnd();
|
|
1410
|
+
i0.ɵɵelementStart(6, "td");
|
|
1411
|
+
i0.ɵɵtemplate(7, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_7_Template, 2, 2, "span", 290)(8, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_8_Template, 2, 0, "span", 291);
|
|
1412
|
+
i0.ɵɵelementEnd();
|
|
1413
|
+
i0.ɵɵelementStart(9, "td");
|
|
1414
|
+
i0.ɵɵtemplate(10, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_10_Template, 2, 6, "span", 292)(11, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_11_Template, 2, 0, "span", 293);
|
|
1415
|
+
i0.ɵɵelementEnd();
|
|
1416
|
+
i0.ɵɵelementStart(12, "td");
|
|
1417
|
+
i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_13_Template, 2, 6, "span", 292)(14, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_14_Template, 2, 0, "span", 293);
|
|
1418
|
+
i0.ɵɵelementEnd();
|
|
1419
|
+
i0.ɵɵelementStart(15, "td");
|
|
1420
|
+
i0.ɵɵtemplate(16, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_16_Template, 3, 0, "span", 294)(17, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_17_Template, 3, 0, "span", 295)(18, TestSuiteFormComponentExtended_div_50_div_14_tr_45_span_18_Template, 2, 0, "span", 296);
|
|
1421
|
+
i0.ɵɵelementEnd()();
|
|
1422
|
+
} if (rf & 2) {
|
|
1423
|
+
const result_r37 = ctx.$implicit;
|
|
1424
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(13, _c8, result_r37.runA && result_r37.runB && result_r37.runA.status !== "Passed" && result_r37.runB.status === "Passed", result_r37.runA && result_r37.runB && result_r37.runA.status === "Passed" && result_r37.runB.status !== "Passed"));
|
|
1425
|
+
i0.ɵɵadvance(2);
|
|
1426
|
+
i0.ɵɵtextInterpolate(result_r37.testName);
|
|
1427
|
+
i0.ɵɵadvance(2);
|
|
1428
|
+
i0.ɵɵproperty("ngIf", result_r37.runA);
|
|
1429
|
+
i0.ɵɵadvance();
|
|
1430
|
+
i0.ɵɵproperty("ngIf", !result_r37.runA);
|
|
1431
|
+
i0.ɵɵadvance(2);
|
|
1432
|
+
i0.ɵɵproperty("ngIf", result_r37.runB);
|
|
1433
|
+
i0.ɵɵadvance();
|
|
1434
|
+
i0.ɵɵproperty("ngIf", !result_r37.runB);
|
|
1435
|
+
i0.ɵɵadvance(2);
|
|
1436
|
+
i0.ɵɵproperty("ngIf", result_r37.scoreDiff != null);
|
|
1437
|
+
i0.ɵɵadvance();
|
|
1438
|
+
i0.ɵɵproperty("ngIf", result_r37.scoreDiff == null);
|
|
1439
|
+
i0.ɵɵadvance(2);
|
|
1440
|
+
i0.ɵɵproperty("ngIf", result_r37.durationDiff != null);
|
|
1441
|
+
i0.ɵɵadvance();
|
|
1442
|
+
i0.ɵɵproperty("ngIf", result_r37.durationDiff == null);
|
|
1443
|
+
i0.ɵɵadvance(2);
|
|
1444
|
+
i0.ɵɵproperty("ngIf", result_r37.runA && result_r37.runB && result_r37.runA.status !== "Passed" && result_r37.runB.status === "Passed");
|
|
1445
|
+
i0.ɵɵadvance();
|
|
1446
|
+
i0.ɵɵproperty("ngIf", result_r37.runA && result_r37.runB && result_r37.runA.status === "Passed" && result_r37.runB.status !== "Passed");
|
|
1447
|
+
i0.ɵɵadvance();
|
|
1448
|
+
i0.ɵɵproperty("ngIf", !result_r37.statusChanged);
|
|
1449
|
+
} }
|
|
1450
|
+
function TestSuiteFormComponentExtended_div_50_div_14_Template(rf, ctx) { if (rf & 1) {
|
|
1451
|
+
i0.ɵɵelementStart(0, "div", 276)(1, "div", 277)(2, "div", 278)(3, "div", 279);
|
|
1452
|
+
i0.ɵɵtext(4, "Pass Rate Change");
|
|
51
1453
|
i0.ɵɵelementEnd();
|
|
52
|
-
i0.ɵɵelementStart(
|
|
53
|
-
i0.ɵɵ
|
|
54
|
-
i0.ɵɵ
|
|
55
|
-
i0.ɵɵelementStart(8, "div", 30);
|
|
56
|
-
i0.ɵɵtext(9);
|
|
1454
|
+
i0.ɵɵelementStart(5, "div", 280);
|
|
1455
|
+
i0.ɵɵelement(6, "i", 10);
|
|
1456
|
+
i0.ɵɵtext(7);
|
|
57
1457
|
i0.ɵɵelementEnd()();
|
|
58
|
-
i0.ɵɵelementStart(
|
|
59
|
-
i0.ɵɵtext(
|
|
1458
|
+
i0.ɵɵelementStart(8, "div", 278)(9, "div", 279);
|
|
1459
|
+
i0.ɵɵtext(10, "Duration Change");
|
|
60
1460
|
i0.ɵɵelementEnd();
|
|
61
|
-
i0.ɵɵelementStart(
|
|
62
|
-
i0.ɵɵ
|
|
1461
|
+
i0.ɵɵelementStart(11, "div", 280);
|
|
1462
|
+
i0.ɵɵelement(12, "i", 10);
|
|
1463
|
+
i0.ɵɵtext(13);
|
|
63
1464
|
i0.ɵɵelementEnd()();
|
|
64
|
-
i0.ɵɵelementStart(
|
|
65
|
-
i0.ɵɵtext(
|
|
1465
|
+
i0.ɵɵelementStart(14, "div", 281)(15, "div", 279);
|
|
1466
|
+
i0.ɵɵtext(16, "Improved");
|
|
66
1467
|
i0.ɵɵelementEnd();
|
|
67
|
-
i0.ɵɵelementStart(
|
|
68
|
-
i0.ɵɵtext(
|
|
69
|
-
i0.ɵɵpipe(20, "date");
|
|
1468
|
+
i0.ɵɵelementStart(17, "div", 282);
|
|
1469
|
+
i0.ɵɵtext(18);
|
|
70
1470
|
i0.ɵɵelementEnd()();
|
|
71
|
-
i0.ɵɵelementStart(
|
|
72
|
-
i0.ɵɵtext(
|
|
1471
|
+
i0.ɵɵelementStart(19, "div", 283)(20, "div", 279);
|
|
1472
|
+
i0.ɵɵtext(21, "Regressed");
|
|
73
1473
|
i0.ɵɵelementEnd();
|
|
74
|
-
i0.ɵɵelementStart(
|
|
75
|
-
i0.ɵɵtext(
|
|
76
|
-
i0.ɵɵ
|
|
77
|
-
i0.ɵɵ
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
i0.ɵɵadvance(9);
|
|
81
|
-
i0.ɵɵtextInterpolate(ctx_r0.record.Name);
|
|
82
|
-
i0.ɵɵadvance(5);
|
|
83
|
-
i0.ɵɵtextInterpolate(ctx_r0.record.Status);
|
|
84
|
-
i0.ɵɵadvance(5);
|
|
85
|
-
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(20, 4, ctx_r0.record.__mj_CreatedAt, "medium"));
|
|
86
|
-
i0.ɵɵadvance(6);
|
|
87
|
-
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(26, 7, ctx_r0.record.__mj_UpdatedAt, "medium"));
|
|
88
|
-
} }
|
|
89
|
-
function TestSuiteFormComponentExtended_div_31_div_1_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
90
|
-
const _r2 = i0.ɵɵgetCurrentView();
|
|
91
|
-
i0.ɵɵelementStart(0, "div", 36);
|
|
92
|
-
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_31_div_1_div_1_Template_div_click_0_listener() { const test_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openTest(test_r3.TestID)); });
|
|
93
|
-
i0.ɵɵelementStart(1, "div", 37);
|
|
94
|
-
i0.ɵɵtext(2);
|
|
1474
|
+
i0.ɵɵelementStart(22, "div", 282);
|
|
1475
|
+
i0.ɵɵtext(23);
|
|
1476
|
+
i0.ɵɵelementEnd()()();
|
|
1477
|
+
i0.ɵɵelementStart(24, "div", 284)(25, "h3");
|
|
1478
|
+
i0.ɵɵelement(26, "i", 285);
|
|
1479
|
+
i0.ɵɵtext(27, " Test-by-Test Comparison");
|
|
95
1480
|
i0.ɵɵelementEnd();
|
|
96
|
-
i0.ɵɵelementStart(
|
|
97
|
-
i0.ɵɵ
|
|
1481
|
+
i0.ɵɵelementStart(28, "div", 286)(29, "table", 287)(30, "thead")(31, "tr")(32, "th");
|
|
1482
|
+
i0.ɵɵtext(33, "Test");
|
|
98
1483
|
i0.ɵɵelementEnd();
|
|
99
|
-
i0.ɵɵelementStart(
|
|
100
|
-
i0.ɵɵtext(
|
|
1484
|
+
i0.ɵɵelementStart(34, "th");
|
|
1485
|
+
i0.ɵɵtext(35, "Run A Status");
|
|
101
1486
|
i0.ɵɵelementEnd();
|
|
102
|
-
i0.ɵɵelementStart(
|
|
103
|
-
i0.ɵɵtext(
|
|
104
|
-
i0.ɵɵelementEnd()();
|
|
105
|
-
i0.ɵɵelement(10, "i", 42);
|
|
1487
|
+
i0.ɵɵelementStart(36, "th");
|
|
1488
|
+
i0.ɵɵtext(37, "Run B Status");
|
|
106
1489
|
i0.ɵɵelementEnd();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
i0.ɵɵ
|
|
110
|
-
i0.ɵɵ
|
|
111
|
-
i0.ɵɵ
|
|
112
|
-
i0.ɵɵtextInterpolate(test_r3.Test);
|
|
113
|
-
i0.ɵɵadvance(2);
|
|
114
|
-
i0.ɵɵtextInterpolate(test_r3.Status);
|
|
115
|
-
} }
|
|
116
|
-
function TestSuiteFormComponentExtended_div_31_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
117
|
-
i0.ɵɵelementStart(0, "div", 34);
|
|
118
|
-
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_31_div_1_div_1_Template, 11, 3, "div", 35);
|
|
1490
|
+
i0.ɵɵelementStart(38, "th");
|
|
1491
|
+
i0.ɵɵtext(39, "Score Diff");
|
|
1492
|
+
i0.ɵɵelementEnd();
|
|
1493
|
+
i0.ɵɵelementStart(40, "th");
|
|
1494
|
+
i0.ɵɵtext(41, "Duration Diff");
|
|
119
1495
|
i0.ɵɵelementEnd();
|
|
1496
|
+
i0.ɵɵelementStart(42, "th");
|
|
1497
|
+
i0.ɵɵtext(43, "Change");
|
|
1498
|
+
i0.ɵɵelementEnd()()();
|
|
1499
|
+
i0.ɵɵelementStart(44, "tbody");
|
|
1500
|
+
i0.ɵɵtemplate(45, TestSuiteFormComponentExtended_div_50_div_14_tr_45_Template, 19, 16, "tr", 288);
|
|
1501
|
+
i0.ɵɵelementEnd()()()()();
|
|
120
1502
|
} if (rf & 2) {
|
|
1503
|
+
let tmp_4_0;
|
|
121
1504
|
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
1505
|
+
i0.ɵɵadvance(5);
|
|
1506
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(10, _c6, ctx_r0.getComparePassRateDiff() > 0, ctx_r0.getComparePassRateDiff() < 0));
|
|
122
1507
|
i0.ɵɵadvance();
|
|
123
|
-
i0.ɵɵproperty("
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
i0.ɵɵ
|
|
127
|
-
i0.ɵɵ
|
|
128
|
-
i0.ɵɵelementStart(2, "p");
|
|
129
|
-
i0.ɵɵtext(3, "No tests in this suite");
|
|
130
|
-
i0.ɵɵelementEnd()();
|
|
131
|
-
} }
|
|
132
|
-
function TestSuiteFormComponentExtended_div_31_Template(rf, ctx) { if (rf & 1) {
|
|
133
|
-
i0.ɵɵelementStart(0, "div", 31);
|
|
134
|
-
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_31_div_1_Template, 2, 1, "div", 32)(2, TestSuiteFormComponentExtended_div_31_div_2_Template, 4, 0, "div", 33);
|
|
135
|
-
i0.ɵɵelementEnd();
|
|
136
|
-
} if (rf & 2) {
|
|
137
|
-
const ctx_r0 = i0.ɵɵnextContext();
|
|
1508
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(13, _c4, ctx_r0.getComparePassRateDiff() > 0, ctx_r0.getComparePassRateDiff() < 0, ctx_r0.getComparePassRateDiff() === 0));
|
|
1509
|
+
i0.ɵɵadvance();
|
|
1510
|
+
i0.ɵɵtextInterpolate2(" ", ctx_r0.getComparePassRateDiff() > 0 ? "+" : "", "", (tmp_4_0 = ctx_r0.getComparePassRateDiff()) == null ? null : tmp_4_0.toFixed(1), "% ");
|
|
1511
|
+
i0.ɵɵadvance(4);
|
|
1512
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction2(17, _c6, ctx_r0.getCompareDurationDiff() < 0, ctx_r0.getCompareDurationDiff() > 0));
|
|
138
1513
|
i0.ɵɵadvance();
|
|
139
|
-
i0.ɵɵproperty("
|
|
1514
|
+
i0.ɵɵproperty("ngClass", i0.ɵɵpureFunction3(20, _c7, ctx_r0.getCompareDurationDiff() < 0, ctx_r0.getCompareDurationDiff() > 0, ctx_r0.getCompareDurationDiff() === 0));
|
|
140
1515
|
i0.ɵɵadvance();
|
|
141
|
-
i0.ɵɵ
|
|
1516
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.formatDuration(ctx_r0.getAbsCompareDurationDiff()), " ");
|
|
1517
|
+
i0.ɵɵadvance(5);
|
|
1518
|
+
i0.ɵɵtextInterpolate(ctx_r0.getCompareImprovedCount());
|
|
1519
|
+
i0.ɵɵadvance(5);
|
|
1520
|
+
i0.ɵɵtextInterpolate(ctx_r0.getCompareRegressedCount());
|
|
1521
|
+
i0.ɵɵadvance(22);
|
|
1522
|
+
i0.ɵɵproperty("ngForOf", ctx_r0.compareResults);
|
|
142
1523
|
} }
|
|
143
|
-
function
|
|
144
|
-
i0.ɵɵelementStart(0, "
|
|
145
|
-
i0.ɵɵ
|
|
1524
|
+
function TestSuiteFormComponentExtended_div_50_div_15_Template(rf, ctx) { if (rf & 1) {
|
|
1525
|
+
i0.ɵɵelementStart(0, "div", 58);
|
|
1526
|
+
i0.ɵɵelement(1, "mj-loading", 304);
|
|
146
1527
|
i0.ɵɵelementEnd();
|
|
147
|
-
} if (rf & 2) {
|
|
148
|
-
const run_r5 = i0.ɵɵnextContext().$implicit;
|
|
149
|
-
i0.ɵɵadvance();
|
|
150
|
-
i0.ɵɵtextInterpolate2("", run_r5.PassedTests, "/", run_r5.TotalTests, " passed");
|
|
151
1528
|
} }
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
i0.ɵɵ
|
|
155
|
-
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_32_div_1_div_1_Template_div_click_0_listener() { const run_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r0 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r0.openSuiteRun(run_r5.ID)); });
|
|
156
|
-
i0.ɵɵelementStart(1, "div", 50);
|
|
157
|
-
i0.ɵɵelement(2, "i", 51);
|
|
1529
|
+
function TestSuiteFormComponentExtended_div_50_div_16_Template(rf, ctx) { if (rf & 1) {
|
|
1530
|
+
i0.ɵɵelementStart(0, "div", 305)(1, "div", 306);
|
|
1531
|
+
i0.ɵɵelement(2, "i", 27);
|
|
158
1532
|
i0.ɵɵelementEnd();
|
|
159
|
-
i0.ɵɵelementStart(3, "
|
|
160
|
-
i0.ɵɵtext(
|
|
1533
|
+
i0.ɵɵelementStart(3, "h4");
|
|
1534
|
+
i0.ɵɵtext(4, "Select Two Runs to Compare");
|
|
161
1535
|
i0.ɵɵelementEnd();
|
|
162
|
-
i0.ɵɵelementStart(
|
|
163
|
-
i0.ɵɵtext(
|
|
1536
|
+
i0.ɵɵelementStart(5, "p");
|
|
1537
|
+
i0.ɵɵtext(6, "Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison.");
|
|
164
1538
|
i0.ɵɵelementEnd()();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
i0.ɵɵ
|
|
1539
|
+
} }
|
|
1540
|
+
function TestSuiteFormComponentExtended_div_50_div_17_Template(rf, ctx) { if (rf & 1) {
|
|
1541
|
+
const _r38 = i0.ɵɵgetCurrentView();
|
|
1542
|
+
i0.ɵɵelementStart(0, "div", 77)(1, "div", 78);
|
|
1543
|
+
i0.ɵɵelement(2, "i", 27);
|
|
168
1544
|
i0.ɵɵelementEnd();
|
|
169
|
-
i0.ɵɵ
|
|
170
|
-
i0.ɵɵ
|
|
171
|
-
i0.ɵɵelement(14, "i", 42);
|
|
1545
|
+
i0.ɵɵelementStart(3, "h4");
|
|
1546
|
+
i0.ɵɵtext(4, "Not Enough Runs to Compare");
|
|
172
1547
|
i0.ɵɵelementEnd();
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
i0.ɵɵadvance();
|
|
176
|
-
i0.ɵɵstyleProp("background-color", run_r5.Status === "Completed" ? "#4caf50" : run_r5.Status === "Failed" ? "#f44336" : "#2196f3");
|
|
177
|
-
i0.ɵɵadvance(5);
|
|
178
|
-
i0.ɵɵtextInterpolate(run_r5.ID.substring(0, 8));
|
|
179
|
-
i0.ɵɵadvance(2);
|
|
180
|
-
i0.ɵɵtextInterpolate(run_r5.Status);
|
|
181
|
-
i0.ɵɵadvance(3);
|
|
182
|
-
i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(12, 6, run_r5.StartedAt, "short"));
|
|
183
|
-
i0.ɵɵadvance(2);
|
|
184
|
-
i0.ɵɵproperty("ngIf", run_r5.TotalTests);
|
|
185
|
-
} }
|
|
186
|
-
function TestSuiteFormComponentExtended_div_32_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
187
|
-
i0.ɵɵelementStart(0, "div", 47);
|
|
188
|
-
i0.ɵɵtemplate(1, TestSuiteFormComponentExtended_div_32_div_1_div_1_Template, 15, 9, "div", 48);
|
|
1548
|
+
i0.ɵɵelementStart(5, "p");
|
|
1549
|
+
i0.ɵɵtext(6, "You need at least 2 suite runs to use the comparison feature.");
|
|
189
1550
|
i0.ɵɵelementEnd();
|
|
190
|
-
|
|
191
|
-
const ctx_r0 = i0.ɵɵnextContext(2);
|
|
192
|
-
i0.ɵɵ
|
|
193
|
-
i0.ɵɵ
|
|
194
|
-
} }
|
|
195
|
-
function TestSuiteFormComponentExtended_div_32_div_2_Template(rf, ctx) { if (rf & 1) {
|
|
196
|
-
i0.ɵɵelementStart(0, "div", 43);
|
|
197
|
-
i0.ɵɵelement(1, "i", 44);
|
|
198
|
-
i0.ɵɵelementStart(2, "p");
|
|
199
|
-
i0.ɵɵtext(3, "No runs for this suite");
|
|
1551
|
+
i0.ɵɵelementStart(7, "button", 15);
|
|
1552
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_50_div_17_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r38); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.runSuite()); });
|
|
1553
|
+
i0.ɵɵelement(8, "i", 16);
|
|
1554
|
+
i0.ɵɵtext(9, " Run Suite Now ");
|
|
200
1555
|
i0.ɵɵelementEnd()();
|
|
201
1556
|
} }
|
|
202
|
-
function
|
|
203
|
-
i0.ɵɵelementStart(0, "div",
|
|
204
|
-
i0.ɵɵ
|
|
1557
|
+
function TestSuiteFormComponentExtended_div_50_Template(rf, ctx) { if (rf & 1) {
|
|
1558
|
+
i0.ɵɵelementStart(0, "div", 249)(1, "div", 250)(2, "div", 251)(3, "h4");
|
|
1559
|
+
i0.ɵɵtext(4, "Run A (Baseline)");
|
|
1560
|
+
i0.ɵɵelementEnd();
|
|
1561
|
+
i0.ɵɵtemplate(5, TestSuiteFormComponentExtended_div_50_div_5_Template, 2, 1, "div", 252)(6, TestSuiteFormComponentExtended_div_50_div_6_Template, 12, 5, "div", 253);
|
|
1562
|
+
i0.ɵɵelementEnd();
|
|
1563
|
+
i0.ɵɵelementStart(7, "div", 254);
|
|
1564
|
+
i0.ɵɵelement(8, "i", 255);
|
|
1565
|
+
i0.ɵɵelementEnd();
|
|
1566
|
+
i0.ɵɵelementStart(9, "div", 251)(10, "h4");
|
|
1567
|
+
i0.ɵɵtext(11, "Run B (Compare)");
|
|
1568
|
+
i0.ɵɵelementEnd();
|
|
1569
|
+
i0.ɵɵtemplate(12, TestSuiteFormComponentExtended_div_50_div_12_Template, 2, 1, "div", 252)(13, TestSuiteFormComponentExtended_div_50_div_13_Template, 12, 5, "div", 253);
|
|
1570
|
+
i0.ɵɵelementEnd()();
|
|
1571
|
+
i0.ɵɵtemplate(14, TestSuiteFormComponentExtended_div_50_div_14_Template, 46, 24, "div", 256)(15, TestSuiteFormComponentExtended_div_50_div_15_Template, 2, 0, "div", 55)(16, TestSuiteFormComponentExtended_div_50_div_16_Template, 7, 0, "div", 257)(17, TestSuiteFormComponentExtended_div_50_div_17_Template, 10, 0, "div", 57);
|
|
205
1572
|
i0.ɵɵelementEnd();
|
|
206
1573
|
} if (rf & 2) {
|
|
207
1574
|
const ctx_r0 = i0.ɵɵnextContext();
|
|
1575
|
+
i0.ɵɵadvance(5);
|
|
1576
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
|
|
1577
|
+
i0.ɵɵadvance();
|
|
1578
|
+
i0.ɵɵproperty("ngIf", ctx_r0.compareRunA);
|
|
1579
|
+
i0.ɵɵadvance(6);
|
|
1580
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.loadingRuns && ctx_r0.suiteRuns.length > 0);
|
|
1581
|
+
i0.ɵɵadvance();
|
|
1582
|
+
i0.ɵɵproperty("ngIf", ctx_r0.compareRunB);
|
|
208
1583
|
i0.ɵɵadvance();
|
|
209
|
-
i0.ɵɵproperty("ngIf", ctx_r0.
|
|
1584
|
+
i0.ɵɵproperty("ngIf", ctx_r0.compareRunA && ctx_r0.compareRunB && !ctx_r0.loadingCompare);
|
|
210
1585
|
i0.ɵɵadvance();
|
|
211
|
-
i0.ɵɵproperty("ngIf", ctx_r0.
|
|
1586
|
+
i0.ɵɵproperty("ngIf", ctx_r0.loadingCompare);
|
|
1587
|
+
i0.ɵɵadvance();
|
|
1588
|
+
i0.ɵɵproperty("ngIf", !ctx_r0.compareRunA || !ctx_r0.compareRunB);
|
|
1589
|
+
i0.ɵɵadvance();
|
|
1590
|
+
i0.ɵɵproperty("ngIf", ctx_r0.runsLoaded && ctx_r0.suiteRuns.length < 2);
|
|
1591
|
+
} }
|
|
1592
|
+
function TestSuiteFormComponentExtended_div_53_Template(rf, ctx) { if (rf & 1) {
|
|
1593
|
+
const _r39 = i0.ɵɵgetCurrentView();
|
|
1594
|
+
i0.ɵɵelementStart(0, "div", 307)(1, "div", 308);
|
|
1595
|
+
i0.ɵɵelement(2, "i", 35);
|
|
1596
|
+
i0.ɵɵtext(3, " Shortcuts ");
|
|
1597
|
+
i0.ɵɵelementStart(4, "button", 309);
|
|
1598
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_div_53_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r39); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.toggleShortcuts()); });
|
|
1599
|
+
i0.ɵɵelement(5, "i", 189);
|
|
1600
|
+
i0.ɵɵelementEnd()();
|
|
1601
|
+
i0.ɵɵelementStart(6, "div", 310)(7, "div", 311)(8, "span");
|
|
1602
|
+
i0.ɵɵtext(9, "Refresh");
|
|
1603
|
+
i0.ɵɵelementEnd();
|
|
1604
|
+
i0.ɵɵelementStart(10, "span", 312)(11, "kbd");
|
|
1605
|
+
i0.ɵɵtext(12, "Cmd");
|
|
1606
|
+
i0.ɵɵelementEnd();
|
|
1607
|
+
i0.ɵɵelementStart(13, "kbd");
|
|
1608
|
+
i0.ɵɵtext(14, "R");
|
|
1609
|
+
i0.ɵɵelementEnd()()();
|
|
1610
|
+
i0.ɵɵelementStart(15, "div", 311)(16, "span");
|
|
1611
|
+
i0.ɵɵtext(17, "Run Suite");
|
|
1612
|
+
i0.ɵɵelementEnd();
|
|
1613
|
+
i0.ɵɵelementStart(18, "span", 312)(19, "kbd");
|
|
1614
|
+
i0.ɵɵtext(20, "Cmd");
|
|
1615
|
+
i0.ɵɵelementEnd();
|
|
1616
|
+
i0.ɵɵelementStart(21, "kbd");
|
|
1617
|
+
i0.ɵɵtext(22, "Enter");
|
|
1618
|
+
i0.ɵɵelementEnd()()();
|
|
1619
|
+
i0.ɵɵelementStart(23, "div", 311)(24, "span");
|
|
1620
|
+
i0.ɵɵtext(25, "Switch Tabs");
|
|
1621
|
+
i0.ɵɵelementEnd();
|
|
1622
|
+
i0.ɵɵelementStart(26, "span", 312)(27, "kbd");
|
|
1623
|
+
i0.ɵɵtext(28, "1");
|
|
1624
|
+
i0.ɵɵelementEnd();
|
|
1625
|
+
i0.ɵɵtext(29, "-");
|
|
1626
|
+
i0.ɵɵelementStart(30, "kbd");
|
|
1627
|
+
i0.ɵɵtext(31, "5");
|
|
1628
|
+
i0.ɵɵelementEnd()()()()();
|
|
212
1629
|
} }
|
|
1630
|
+
/** Settings key for keyboard shortcuts visibility */
|
|
1631
|
+
const SHORTCUTS_SETTINGS_KEY = '__mj.Testing.ShowKeyboardShortcuts';
|
|
213
1632
|
let TestSuiteFormComponentExtended = class TestSuiteFormComponentExtended extends TestSuiteFormComponent {
|
|
214
|
-
constructor(elementRef, sharedService, router, route, cdr, testingDialogService) {
|
|
1633
|
+
constructor(elementRef, sharedService, router, route, cdr, testingDialogService, evalPrefsService, viewContainerRef) {
|
|
215
1634
|
super(elementRef, sharedService, router, route, cdr);
|
|
216
1635
|
this.router = router;
|
|
217
1636
|
this.cdr = cdr;
|
|
218
1637
|
this.testingDialogService = testingDialogService;
|
|
1638
|
+
this.evalPrefsService = evalPrefsService;
|
|
1639
|
+
this.viewContainerRef = viewContainerRef;
|
|
219
1640
|
this.destroy$ = new Subject();
|
|
1641
|
+
// UI state
|
|
220
1642
|
this.activeTab = 'overview';
|
|
221
1643
|
this.loading = false;
|
|
1644
|
+
this.loadingTests = false;
|
|
1645
|
+
this.loadingRuns = false;
|
|
1646
|
+
this.loadingAnalytics = false;
|
|
1647
|
+
this.loadingCompare = false;
|
|
222
1648
|
this.testsLoaded = false;
|
|
223
1649
|
this.runsLoaded = false;
|
|
1650
|
+
this.analyticsLoaded = false;
|
|
1651
|
+
this.isRefreshing = false;
|
|
1652
|
+
this.error = null;
|
|
1653
|
+
// Related data
|
|
224
1654
|
this.suiteTests = [];
|
|
225
1655
|
this.suiteRuns = [];
|
|
1656
|
+
// Analytics data
|
|
1657
|
+
this.analyticsData = [];
|
|
1658
|
+
this.uniqueTags = [];
|
|
1659
|
+
this.selectedTags = []; // Multi-select: empty array means "All Tags"
|
|
1660
|
+
this.analyticsTimeRange = '30d';
|
|
1661
|
+
this.analyticsView = 'summary';
|
|
1662
|
+
this.matrixData = [];
|
|
1663
|
+
this.loadingMatrix = false;
|
|
1664
|
+
this.matrixLoaded = false;
|
|
1665
|
+
this.chartRendered = false;
|
|
1666
|
+
// Compare data
|
|
1667
|
+
this.compareRunA = null;
|
|
1668
|
+
this.compareRunB = null;
|
|
1669
|
+
this.compareResults = [];
|
|
1670
|
+
this.compareRunATests = [];
|
|
1671
|
+
this.compareRunBTests = [];
|
|
1672
|
+
// Keyboard shortcuts
|
|
1673
|
+
this.keyboardShortcutsEnabled = true;
|
|
1674
|
+
this.showShortcuts = false; // Hidden by default
|
|
1675
|
+
this.shortcutsSettingEntity = null;
|
|
1676
|
+
this.metadata = new Metadata();
|
|
1677
|
+
// Evaluation preferences
|
|
1678
|
+
this.evalPreferences = { showExecution: true, showHuman: true, showAuto: false };
|
|
1679
|
+
// Filter collapse state
|
|
1680
|
+
this.filtersCollapsed = false;
|
|
1681
|
+
// Matrix sorting
|
|
1682
|
+
this.matrixSortBy = 'sequence';
|
|
1683
|
+
this.matrixSortAsc = true;
|
|
1684
|
+
// Matrix row selection
|
|
1685
|
+
this.selectedMatrixTestId = null;
|
|
1686
|
+
// Matrix test name filter
|
|
1687
|
+
this.matrixTestFilter = '';
|
|
1688
|
+
this.matrixFilterSubject$ = new Subject();
|
|
226
1689
|
}
|
|
227
1690
|
async ngOnInit() {
|
|
228
1691
|
await super.ngOnInit();
|
|
1692
|
+
this.loadShortcutsSetting();
|
|
1693
|
+
// Subscribe to evaluation preferences
|
|
1694
|
+
this.evalPrefsService.preferences$
|
|
1695
|
+
.pipe(takeUntil(this.destroy$))
|
|
1696
|
+
.subscribe(prefs => {
|
|
1697
|
+
this.evalPreferences = prefs;
|
|
1698
|
+
this.cdr.markForCheck();
|
|
1699
|
+
// Re-render chart when preferences change (D3 chart needs manual update)
|
|
1700
|
+
if (this.chartRendered && this.analyticsView === 'chart') {
|
|
1701
|
+
this.renderChart();
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
// Subscribe to matrix filter with debounce
|
|
1705
|
+
this.matrixFilterSubject$
|
|
1706
|
+
.pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroy$))
|
|
1707
|
+
.subscribe(value => {
|
|
1708
|
+
this.matrixTestFilter = value;
|
|
1709
|
+
this.cdr.markForCheck();
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
ngAfterViewInit() {
|
|
1713
|
+
// Initialize any view-dependent logic
|
|
229
1714
|
}
|
|
230
1715
|
ngOnDestroy() {
|
|
231
1716
|
this.destroy$.next();
|
|
232
1717
|
this.destroy$.complete();
|
|
233
1718
|
}
|
|
1719
|
+
// Keyboard shortcuts
|
|
1720
|
+
handleKeyboardShortcut(event) {
|
|
1721
|
+
if (!this.keyboardShortcutsEnabled)
|
|
1722
|
+
return;
|
|
1723
|
+
// Cmd/Ctrl + R: Refresh
|
|
1724
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'r' && !event.shiftKey) {
|
|
1725
|
+
event.preventDefault();
|
|
1726
|
+
this.refresh();
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
// Cmd/Ctrl + Enter: Run suite
|
|
1730
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
|
1731
|
+
event.preventDefault();
|
|
1732
|
+
this.runSuite();
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
// Number keys for tabs (1-5)
|
|
1736
|
+
if (!event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
1737
|
+
switch (event.key) {
|
|
1738
|
+
case '1':
|
|
1739
|
+
this.changeTab('overview');
|
|
1740
|
+
break;
|
|
1741
|
+
case '2':
|
|
1742
|
+
this.changeTab('tests');
|
|
1743
|
+
break;
|
|
1744
|
+
case '3':
|
|
1745
|
+
this.changeTab('runs');
|
|
1746
|
+
break;
|
|
1747
|
+
case '4':
|
|
1748
|
+
this.changeTab('analytics');
|
|
1749
|
+
break;
|
|
1750
|
+
case '5':
|
|
1751
|
+
this.changeTab('compare');
|
|
1752
|
+
break;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
234
1756
|
changeTab(tab) {
|
|
235
1757
|
this.activeTab = tab;
|
|
236
1758
|
if (tab === 'tests' && !this.testsLoaded)
|
|
237
1759
|
this.loadTests();
|
|
238
1760
|
if (tab === 'runs' && !this.runsLoaded)
|
|
239
1761
|
this.loadRuns();
|
|
1762
|
+
if (tab === 'analytics' && !this.analyticsLoaded)
|
|
1763
|
+
this.loadAnalytics();
|
|
240
1764
|
this.cdr.markForCheck();
|
|
241
1765
|
}
|
|
242
1766
|
async loadTests() {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
ExtraFilter: `SuiteID='${this.record.ID}'`,
|
|
247
|
-
OrderBy: 'Sequence',
|
|
248
|
-
ResultType: 'entity_object'
|
|
249
|
-
});
|
|
250
|
-
if (result.Success)
|
|
251
|
-
this.suiteTests = result.Results || [];
|
|
252
|
-
this.testsLoaded = true;
|
|
1767
|
+
if (this.testsLoaded)
|
|
1768
|
+
return;
|
|
1769
|
+
this.loadingTests = true;
|
|
253
1770
|
this.cdr.markForCheck();
|
|
1771
|
+
try {
|
|
1772
|
+
const rv = new RunView();
|
|
1773
|
+
const result = await rv.RunView({
|
|
1774
|
+
EntityName: 'MJ: Test Suite Tests',
|
|
1775
|
+
ExtraFilter: `SuiteID='${this.record.ID}'`,
|
|
1776
|
+
OrderBy: 'Sequence',
|
|
1777
|
+
ResultType: 'entity_object'
|
|
1778
|
+
});
|
|
1779
|
+
if (result.Success)
|
|
1780
|
+
this.suiteTests = result.Results || [];
|
|
1781
|
+
this.testsLoaded = true;
|
|
1782
|
+
}
|
|
1783
|
+
catch (error) {
|
|
1784
|
+
console.error('Error loading tests:', error);
|
|
1785
|
+
SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
|
|
1786
|
+
}
|
|
1787
|
+
finally {
|
|
1788
|
+
this.loadingTests = false;
|
|
1789
|
+
this.cdr.markForCheck();
|
|
1790
|
+
}
|
|
254
1791
|
}
|
|
255
1792
|
async loadRuns() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
ExtraFilter: `SuiteID='${this.record.ID}'`,
|
|
260
|
-
OrderBy: 'StartedAt DESC',
|
|
261
|
-
MaxRows: 50,
|
|
262
|
-
ResultType: 'entity_object'
|
|
263
|
-
});
|
|
264
|
-
if (result.Success)
|
|
265
|
-
this.suiteRuns = result.Results || [];
|
|
266
|
-
this.runsLoaded = true;
|
|
1793
|
+
if (this.runsLoaded)
|
|
1794
|
+
return;
|
|
1795
|
+
this.loadingRuns = true;
|
|
267
1796
|
this.cdr.markForCheck();
|
|
1797
|
+
try {
|
|
1798
|
+
const rv = new RunView();
|
|
1799
|
+
const result = await rv.RunView({
|
|
1800
|
+
EntityName: 'MJ: Test Suite Runs',
|
|
1801
|
+
ExtraFilter: `SuiteID='${this.record.ID}'`,
|
|
1802
|
+
OrderBy: 'StartedAt DESC',
|
|
1803
|
+
MaxRows: 50,
|
|
1804
|
+
ResultType: 'entity_object'
|
|
1805
|
+
});
|
|
1806
|
+
if (result.Success)
|
|
1807
|
+
this.suiteRuns = result.Results || [];
|
|
1808
|
+
this.runsLoaded = true;
|
|
1809
|
+
}
|
|
1810
|
+
catch (error) {
|
|
1811
|
+
console.error('Error loading runs:', error);
|
|
1812
|
+
SharedService.Instance.CreateSimpleNotification('Failed to load runs', 'error', 3000);
|
|
1813
|
+
}
|
|
1814
|
+
finally {
|
|
1815
|
+
this.loadingRuns = false;
|
|
1816
|
+
this.cdr.markForCheck();
|
|
1817
|
+
}
|
|
268
1818
|
}
|
|
269
1819
|
getStatusColor() {
|
|
270
|
-
|
|
1820
|
+
switch (this.record.Status) {
|
|
1821
|
+
case 'Active': return '#10b981';
|
|
1822
|
+
case 'Disabled': return '#6b7280';
|
|
1823
|
+
case 'Pending': return '#f59e0b';
|
|
1824
|
+
default: return '#9ca3af';
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
getStatusClass() {
|
|
1828
|
+
return `status-${this.record.Status?.toLowerCase() || 'unknown'}`;
|
|
1829
|
+
}
|
|
1830
|
+
getRunStatusColor(status) {
|
|
1831
|
+
switch (status) {
|
|
1832
|
+
case 'Completed': return '#10b981';
|
|
1833
|
+
case 'Failed': return '#ef4444';
|
|
1834
|
+
case 'Running': return '#3b82f6';
|
|
1835
|
+
case 'Pending': return '#8b5cf6';
|
|
1836
|
+
case 'Cancelled': return '#6b7280';
|
|
1837
|
+
default: return '#9ca3af';
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
formatTimeout(ms) {
|
|
1841
|
+
if (ms === null || ms === undefined)
|
|
1842
|
+
return 'Default (5 min)';
|
|
1843
|
+
if (ms < 1000)
|
|
1844
|
+
return `${ms}ms`;
|
|
1845
|
+
if (ms < 60000)
|
|
1846
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
1847
|
+
if (ms < 3600000) {
|
|
1848
|
+
const mins = Math.floor(ms / 60000);
|
|
1849
|
+
const secs = Math.floor((ms % 60000) / 1000);
|
|
1850
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
1851
|
+
}
|
|
1852
|
+
const hours = Math.floor(ms / 3600000);
|
|
1853
|
+
const mins = Math.floor((ms % 3600000) / 60000);
|
|
1854
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
1855
|
+
}
|
|
1856
|
+
getRelativeTime(date) {
|
|
1857
|
+
if (!date)
|
|
1858
|
+
return 'N/A';
|
|
1859
|
+
const d = new Date(date);
|
|
1860
|
+
const now = new Date();
|
|
1861
|
+
const diffMs = now.getTime() - d.getTime();
|
|
1862
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
1863
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
1864
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
1865
|
+
if (diffMins < 1)
|
|
1866
|
+
return 'Just now';
|
|
1867
|
+
if (diffMins < 60)
|
|
1868
|
+
return `${diffMins}m ago`;
|
|
1869
|
+
if (diffHours < 24)
|
|
1870
|
+
return `${diffHours}h ago`;
|
|
1871
|
+
if (diffDays < 7)
|
|
1872
|
+
return `${diffDays}d ago`;
|
|
1873
|
+
return d.toLocaleDateString();
|
|
1874
|
+
}
|
|
1875
|
+
getPassRate(run) {
|
|
1876
|
+
const total = run.TotalTests || 0;
|
|
1877
|
+
const passed = run.PassedTests || 0;
|
|
1878
|
+
if (total === 0)
|
|
1879
|
+
return 0;
|
|
1880
|
+
return (passed / total) * 100;
|
|
271
1881
|
}
|
|
272
1882
|
openTest(testId) {
|
|
273
1883
|
SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(testId));
|
|
@@ -277,74 +1887,1566 @@ let TestSuiteFormComponentExtended = class TestSuiteFormComponentExtended extend
|
|
|
277
1887
|
}
|
|
278
1888
|
async runSuite() {
|
|
279
1889
|
if (this.record?.ID) {
|
|
280
|
-
this.testingDialogService.OpenSuiteDialog(this.record.ID);
|
|
1890
|
+
this.testingDialogService.OpenSuiteDialog(this.record.ID, this.viewContainerRef);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
async refresh() {
|
|
1894
|
+
this.isRefreshing = true;
|
|
1895
|
+
this.cdr.markForCheck();
|
|
1896
|
+
try {
|
|
1897
|
+
await this.record.Load(this.record.ID);
|
|
1898
|
+
// Reset lazy-loaded data
|
|
1899
|
+
if (this.testsLoaded) {
|
|
1900
|
+
this.testsLoaded = false;
|
|
1901
|
+
this.suiteTests = [];
|
|
1902
|
+
await this.loadTests();
|
|
1903
|
+
}
|
|
1904
|
+
if (this.runsLoaded) {
|
|
1905
|
+
this.runsLoaded = false;
|
|
1906
|
+
this.suiteRuns = [];
|
|
1907
|
+
await this.loadRuns();
|
|
1908
|
+
}
|
|
1909
|
+
if (this.analyticsLoaded) {
|
|
1910
|
+
this.analyticsLoaded = false;
|
|
1911
|
+
this.analyticsData = [];
|
|
1912
|
+
// Also reset matrix data so it reloads with fresh data
|
|
1913
|
+
this.matrixLoaded = false;
|
|
1914
|
+
this.matrixData = [];
|
|
1915
|
+
await this.loadAnalytics();
|
|
1916
|
+
// Reload matrix if currently viewing matrix or chart view
|
|
1917
|
+
if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
|
|
1918
|
+
await this.loadMatrixData();
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
SharedService.Instance.CreateSimpleNotification('Refreshed successfully', 'success', 2000);
|
|
1922
|
+
}
|
|
1923
|
+
catch {
|
|
1924
|
+
SharedService.Instance.CreateSimpleNotification('Failed to refresh', 'error', 3000);
|
|
1925
|
+
}
|
|
1926
|
+
finally {
|
|
1927
|
+
this.isRefreshing = false;
|
|
1928
|
+
this.cdr.markForCheck();
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
// ==========================================
|
|
1932
|
+
// Analytics Tab Methods
|
|
1933
|
+
// ==========================================
|
|
1934
|
+
async loadAnalytics() {
|
|
1935
|
+
if (this.analyticsLoaded)
|
|
1936
|
+
return;
|
|
1937
|
+
this.loadingAnalytics = true;
|
|
1938
|
+
this.cdr.markForCheck();
|
|
1939
|
+
try {
|
|
1940
|
+
// Load all runs for analytics (not just recent 50)
|
|
1941
|
+
const rv = new RunView();
|
|
1942
|
+
const result = await rv.RunView({
|
|
1943
|
+
EntityName: 'MJ: Test Suite Runs',
|
|
1944
|
+
ExtraFilter: `SuiteID='${this.record.ID}'`,
|
|
1945
|
+
OrderBy: 'StartedAt DESC',
|
|
1946
|
+
MaxRows: 200,
|
|
1947
|
+
ResultType: 'entity_object'
|
|
1948
|
+
});
|
|
1949
|
+
if (result.Success && result.Results) {
|
|
1950
|
+
// Process runs into analytics data points
|
|
1951
|
+
this.analyticsData = result.Results.map(run => this.runToDataPoint(run));
|
|
1952
|
+
// Extract unique tags
|
|
1953
|
+
this.uniqueTags = TagsHelper.getUniqueTags(result.Results.map(r => r.Tags));
|
|
1954
|
+
// Also populate suiteRuns if not already loaded
|
|
1955
|
+
if (!this.runsLoaded) {
|
|
1956
|
+
this.suiteRuns = result.Results.slice(0, 50);
|
|
1957
|
+
this.runsLoaded = true;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
this.analyticsLoaded = true;
|
|
1961
|
+
}
|
|
1962
|
+
catch (error) {
|
|
1963
|
+
console.error('Error loading analytics:', error);
|
|
1964
|
+
SharedService.Instance.CreateSimpleNotification('Failed to load analytics data', 'error', 3000);
|
|
1965
|
+
}
|
|
1966
|
+
finally {
|
|
1967
|
+
this.loadingAnalytics = false;
|
|
1968
|
+
this.cdr.markForCheck();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
runToDataPoint(run) {
|
|
1972
|
+
const total = run.TotalTests || 0;
|
|
1973
|
+
const passed = run.PassedTests || 0;
|
|
1974
|
+
return {
|
|
1975
|
+
runId: run.ID,
|
|
1976
|
+
date: run.StartedAt ? new Date(run.StartedAt) : new Date(),
|
|
1977
|
+
passRate: total > 0 ? (passed / total) * 100 : 0,
|
|
1978
|
+
totalTests: total,
|
|
1979
|
+
passedTests: passed,
|
|
1980
|
+
failedTests: run.FailedTests || 0,
|
|
1981
|
+
errorTests: run.ErrorTests || 0,
|
|
1982
|
+
skippedTests: run.SkippedTests || 0,
|
|
1983
|
+
duration: run.TotalDurationSeconds || 0,
|
|
1984
|
+
cost: run.TotalCostUSD || 0,
|
|
1985
|
+
tags: TagsHelper.parseTags(run.Tags),
|
|
1986
|
+
status: run.Status || 'Unknown'
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
getFilteredAnalyticsData() {
|
|
1990
|
+
let data = this.analyticsData;
|
|
1991
|
+
// Apply time range filter
|
|
1992
|
+
const now = new Date();
|
|
1993
|
+
let cutoffDate = null;
|
|
1994
|
+
switch (this.analyticsTimeRange) {
|
|
1995
|
+
case '7d':
|
|
1996
|
+
cutoffDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
1997
|
+
break;
|
|
1998
|
+
case '30d':
|
|
1999
|
+
cutoffDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
2000
|
+
break;
|
|
2001
|
+
case '90d':
|
|
2002
|
+
cutoffDate = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
|
2003
|
+
break;
|
|
2004
|
+
}
|
|
2005
|
+
if (cutoffDate) {
|
|
2006
|
+
data = data.filter(d => d.date >= cutoffDate);
|
|
2007
|
+
}
|
|
2008
|
+
// Apply tag filter (multi-select: empty array means all tags)
|
|
2009
|
+
if (this.selectedTags.length > 0) {
|
|
2010
|
+
data = data.filter(d => this.selectedTags.some(tag => d.tags.includes(tag)));
|
|
2011
|
+
}
|
|
2012
|
+
return data;
|
|
2013
|
+
}
|
|
2014
|
+
setTimeRange(range) {
|
|
2015
|
+
this.analyticsTimeRange = range;
|
|
2016
|
+
// Reload matrix data when time range changes (if currently viewing matrix or chart)
|
|
2017
|
+
if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
|
|
2018
|
+
this.reloadMatrixData();
|
|
2019
|
+
}
|
|
2020
|
+
this.cdr.markForCheck();
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Toggle a tag in the multi-select filter.
|
|
2024
|
+
* If tag is null, clear all selections (show all tags).
|
|
2025
|
+
*/
|
|
2026
|
+
toggleTagFilter(tag) {
|
|
2027
|
+
if (tag === null) {
|
|
2028
|
+
// Clear all - show all tags
|
|
2029
|
+
this.selectedTags = [];
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
// Toggle the tag
|
|
2033
|
+
const index = this.selectedTags.indexOf(tag);
|
|
2034
|
+
if (index >= 0) {
|
|
2035
|
+
this.selectedTags = this.selectedTags.filter(t => t !== tag);
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
this.selectedTags = [...this.selectedTags, tag];
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
// Reload matrix data when tag filter changes (if currently viewing matrix or chart)
|
|
2042
|
+
if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
|
|
2043
|
+
this.reloadMatrixData();
|
|
2044
|
+
}
|
|
2045
|
+
this.cdr.markForCheck();
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Check if a tag is currently selected in the filter
|
|
2049
|
+
*/
|
|
2050
|
+
isTagSelected(tag) {
|
|
2051
|
+
return this.selectedTags.includes(tag);
|
|
2052
|
+
}
|
|
2053
|
+
setAnalyticsView(view) {
|
|
2054
|
+
this.analyticsView = view;
|
|
2055
|
+
if ((view === 'matrix' || view === 'chart') && !this.matrixLoaded) {
|
|
2056
|
+
this.loadMatrixData();
|
|
2057
|
+
}
|
|
2058
|
+
// Render chart when switching to chart view
|
|
2059
|
+
if (view === 'chart' && this.matrixLoaded) {
|
|
2060
|
+
setTimeout(() => this.renderChart(), 100);
|
|
2061
|
+
}
|
|
2062
|
+
this.cdr.markForCheck();
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Force reload of matrix data (used when filters change)
|
|
2066
|
+
*/
|
|
2067
|
+
reloadMatrixData() {
|
|
2068
|
+
this.matrixLoaded = false;
|
|
2069
|
+
this.loadMatrixData();
|
|
2070
|
+
}
|
|
2071
|
+
async loadMatrixData() {
|
|
2072
|
+
if (this.loadingMatrix)
|
|
2073
|
+
return;
|
|
2074
|
+
this.loadingMatrix = true;
|
|
2075
|
+
this.cdr.markForCheck();
|
|
2076
|
+
try {
|
|
2077
|
+
const rv = new RunView();
|
|
2078
|
+
const filteredRuns = this.getFilteredAnalyticsData();
|
|
2079
|
+
// Load test runs for each suite run (limit to most recent 10 for performance)
|
|
2080
|
+
const runsToLoad = filteredRuns.slice(0, 10);
|
|
2081
|
+
const matrixData = [];
|
|
2082
|
+
// Collect all test run IDs to batch load feedbacks
|
|
2083
|
+
const allTestRunIds = [];
|
|
2084
|
+
for (const runData of runsToLoad) {
|
|
2085
|
+
const testRunsResult = await rv.RunView({
|
|
2086
|
+
EntityName: 'MJ: Test Runs',
|
|
2087
|
+
ExtraFilter: `TestSuiteRunID='${runData.runId}'`,
|
|
2088
|
+
OrderBy: 'Sequence',
|
|
2089
|
+
ResultType: 'entity_object'
|
|
2090
|
+
});
|
|
2091
|
+
if (testRunsResult.Success && testRunsResult.Results) {
|
|
2092
|
+
const testResults = new Map();
|
|
2093
|
+
for (const testRun of testRunsResult.Results) {
|
|
2094
|
+
allTestRunIds.push(testRun.ID);
|
|
2095
|
+
testResults.set(testRun.TestID, {
|
|
2096
|
+
testRunId: testRun.ID,
|
|
2097
|
+
testId: testRun.TestID,
|
|
2098
|
+
testName: testRun.Test || 'Unknown',
|
|
2099
|
+
status: testRun.Status,
|
|
2100
|
+
score: testRun.Score,
|
|
2101
|
+
duration: testRun.DurationSeconds,
|
|
2102
|
+
humanRating: null, // Will be populated below
|
|
2103
|
+
humanComments: null, // Will be populated below
|
|
2104
|
+
sequence: testRun.Sequence ?? 0
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
matrixData.push({
|
|
2108
|
+
runId: runData.runId,
|
|
2109
|
+
date: runData.date,
|
|
2110
|
+
tags: runData.tags,
|
|
2111
|
+
status: runData.status,
|
|
2112
|
+
passRate: runData.passRate,
|
|
2113
|
+
testResults
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
// Batch load feedbacks for all test runs
|
|
2118
|
+
if (allTestRunIds.length > 0) {
|
|
2119
|
+
const feedbackMap = await this.loadFeedbacksForTestRuns(allTestRunIds);
|
|
2120
|
+
// Apply feedbacks to matrix data
|
|
2121
|
+
for (const run of matrixData) {
|
|
2122
|
+
run.testResults.forEach((cell, _testId) => {
|
|
2123
|
+
const feedback = feedbackMap.get(cell.testRunId);
|
|
2124
|
+
if (feedback) {
|
|
2125
|
+
if (feedback.Rating != null) {
|
|
2126
|
+
cell.humanRating = feedback.Rating;
|
|
2127
|
+
}
|
|
2128
|
+
// Use CorrectionSummary if available (from inline feedback), fallback to Comments
|
|
2129
|
+
const commentText = feedback.CorrectionSummary || feedback.Comments;
|
|
2130
|
+
if (commentText) {
|
|
2131
|
+
cell.humanComments = commentText;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
this.matrixData = matrixData;
|
|
2138
|
+
this.matrixLoaded = true;
|
|
2139
|
+
// Render chart if currently on chart view
|
|
2140
|
+
if (this.analyticsView === 'chart') {
|
|
2141
|
+
setTimeout(() => this.renderChart(), 100);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
catch (error) {
|
|
2145
|
+
console.error('Error loading matrix data:', error);
|
|
2146
|
+
SharedService.Instance.CreateSimpleNotification('Failed to load matrix data', 'error', 3000);
|
|
2147
|
+
}
|
|
2148
|
+
finally {
|
|
2149
|
+
this.loadingMatrix = false;
|
|
2150
|
+
this.cdr.markForCheck();
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Load feedbacks for a batch of test run IDs
|
|
2155
|
+
* Returns a map of testRunId -> TestRunFeedbackEntity
|
|
2156
|
+
*/
|
|
2157
|
+
async loadFeedbacksForTestRuns(testRunIds) {
|
|
2158
|
+
const feedbackMap = new Map();
|
|
2159
|
+
if (testRunIds.length === 0)
|
|
2160
|
+
return feedbackMap;
|
|
2161
|
+
try {
|
|
2162
|
+
const rv = new RunView();
|
|
2163
|
+
// Build IN clause for the IDs (batch in chunks to avoid query size limits)
|
|
2164
|
+
const chunkSize = 50;
|
|
2165
|
+
for (let i = 0; i < testRunIds.length; i += chunkSize) {
|
|
2166
|
+
const chunk = testRunIds.slice(i, i + chunkSize);
|
|
2167
|
+
const inClause = chunk.map(id => `'${id}'`).join(',');
|
|
2168
|
+
const result = await rv.RunView({
|
|
2169
|
+
EntityName: 'MJ: Test Run Feedbacks',
|
|
2170
|
+
ExtraFilter: `TestRunID IN (${inClause})`,
|
|
2171
|
+
ResultType: 'entity_object'
|
|
2172
|
+
});
|
|
2173
|
+
if (result.Success && result.Results) {
|
|
2174
|
+
for (const feedback of result.Results) {
|
|
2175
|
+
// If multiple feedbacks exist for same test run, keep the most recent (last one)
|
|
2176
|
+
feedbackMap.set(feedback.TestRunID, feedback);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
catch (error) {
|
|
2182
|
+
console.warn('Failed to load feedbacks:', error);
|
|
2183
|
+
}
|
|
2184
|
+
return feedbackMap;
|
|
2185
|
+
}
|
|
2186
|
+
getUniqueTestsFromMatrix() {
|
|
2187
|
+
const testsMap = new Map();
|
|
2188
|
+
for (const runData of this.matrixData) {
|
|
2189
|
+
for (const [testId, testResult] of runData.testResults) {
|
|
2190
|
+
if (!testsMap.has(testId)) {
|
|
2191
|
+
testsMap.set(testId, { testName: testResult.testName, sequence: testResult.sequence });
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
let tests = Array.from(testsMap.entries()).map(([testId, data]) => ({
|
|
2196
|
+
testId,
|
|
2197
|
+
testName: data.testName,
|
|
2198
|
+
sequence: data.sequence
|
|
2199
|
+
}));
|
|
2200
|
+
// Apply test name filter if set
|
|
2201
|
+
if (this.matrixTestFilter.trim()) {
|
|
2202
|
+
const filterLower = this.matrixTestFilter.toLowerCase().trim();
|
|
2203
|
+
tests = tests.filter(t => t.testName.toLowerCase().includes(filterLower));
|
|
2204
|
+
}
|
|
2205
|
+
// Apply sorting
|
|
2206
|
+
if (this.matrixSortBy === 'sequence') {
|
|
2207
|
+
tests = tests.sort((a, b) => this.matrixSortAsc ? a.sequence - b.sequence : b.sequence - a.sequence);
|
|
2208
|
+
}
|
|
2209
|
+
else {
|
|
2210
|
+
tests = tests.sort((a, b) => {
|
|
2211
|
+
const cmp = a.testName.localeCompare(b.testName);
|
|
2212
|
+
return this.matrixSortAsc ? cmp : -cmp;
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
return tests;
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Toggle matrix sort column
|
|
2219
|
+
*/
|
|
2220
|
+
toggleMatrixSort(column) {
|
|
2221
|
+
if (this.matrixSortBy === column) {
|
|
2222
|
+
this.matrixSortAsc = !this.matrixSortAsc;
|
|
2223
|
+
}
|
|
2224
|
+
else {
|
|
2225
|
+
this.matrixSortBy = column;
|
|
2226
|
+
this.matrixSortAsc = true;
|
|
2227
|
+
}
|
|
2228
|
+
this.cdr.markForCheck();
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Select/deselect a matrix row for highlighting
|
|
2232
|
+
*/
|
|
2233
|
+
selectMatrixRow(testId) {
|
|
2234
|
+
this.selectedMatrixTestId = this.selectedMatrixTestId === testId ? null : testId;
|
|
2235
|
+
this.cdr.markForCheck();
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* Handle test name filter input - uses Subject for debounce
|
|
2239
|
+
*/
|
|
2240
|
+
onMatrixFilterInput(event) {
|
|
2241
|
+
const value = event.target.value;
|
|
2242
|
+
this.matrixFilterSubject$.next(value);
|
|
2243
|
+
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Clear the matrix test name filter
|
|
2246
|
+
*/
|
|
2247
|
+
clearMatrixFilter() {
|
|
2248
|
+
this.matrixTestFilter = '';
|
|
2249
|
+
this.matrixFilterSubject$.next('');
|
|
2250
|
+
this.cdr.markForCheck();
|
|
2251
|
+
}
|
|
2252
|
+
getTestResultForRun(runId, testId) {
|
|
2253
|
+
const runData = this.matrixData.find(r => r.runId === runId);
|
|
2254
|
+
if (!runData)
|
|
2255
|
+
return null;
|
|
2256
|
+
return runData.testResults.get(testId) || null;
|
|
2257
|
+
}
|
|
2258
|
+
getMatrixCellClass(result) {
|
|
2259
|
+
if (!result)
|
|
2260
|
+
return 'cell-none cell-not-run';
|
|
2261
|
+
switch (result.status) {
|
|
2262
|
+
case 'Passed': return 'cell-passed';
|
|
2263
|
+
case 'Failed': return 'cell-failed';
|
|
2264
|
+
case 'Error': return 'cell-error';
|
|
2265
|
+
case 'Timeout': return 'cell-timeout';
|
|
2266
|
+
case 'Skipped': return 'cell-skipped cell-not-run';
|
|
2267
|
+
case 'Running': return 'cell-running';
|
|
2268
|
+
default: return 'cell-pending';
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Get descriptive tooltip for execution status
|
|
2273
|
+
*/
|
|
2274
|
+
getStatusTooltip(status) {
|
|
2275
|
+
switch (status) {
|
|
2276
|
+
case 'Passed': return 'Status: Passed - Test completed without error';
|
|
2277
|
+
case 'Failed': return 'Status: Failed - Test assertions did not pass';
|
|
2278
|
+
case 'Error': return 'Status: Error - Test encountered an exception';
|
|
2279
|
+
case 'Timeout': return 'Status: Timeout - Test exceeded time limit';
|
|
2280
|
+
case 'Skipped': return 'Status: Skipped - Test was not executed';
|
|
2281
|
+
case 'Running': return 'Status: Running - Test is currently executing';
|
|
2282
|
+
case 'Pending': return 'Status: Pending - Test waiting to run';
|
|
2283
|
+
default: return `Status: ${status}`;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Get tooltip for human review with rating and optional comments
|
|
2288
|
+
*/
|
|
2289
|
+
getHumanTooltip(rating, comments) {
|
|
2290
|
+
let tooltip = `Human Review: ${rating}/10 rating`;
|
|
2291
|
+
if (comments) {
|
|
2292
|
+
const truncated = comments.length > 200 ? comments.substring(0, 200) + '...' : comments;
|
|
2293
|
+
tooltip += `\n\n"${truncated}"`;
|
|
2294
|
+
}
|
|
2295
|
+
return tooltip;
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Get the count of enabled evaluation types for matrix cell layout
|
|
2299
|
+
*/
|
|
2300
|
+
getEvalCount() {
|
|
2301
|
+
let count = 0;
|
|
2302
|
+
if (this.evalPreferences.showExecution)
|
|
2303
|
+
count++;
|
|
2304
|
+
if (this.evalPreferences.showHuman)
|
|
2305
|
+
count++;
|
|
2306
|
+
if (this.evalPreferences.showAuto)
|
|
2307
|
+
count++;
|
|
2308
|
+
return count;
|
|
2309
|
+
}
|
|
2310
|
+
// ===========================
|
|
2311
|
+
// Matrix Totals Row Methods
|
|
2312
|
+
// ===========================
|
|
2313
|
+
/**
|
|
2314
|
+
* Get count of passed tests for a run
|
|
2315
|
+
*/
|
|
2316
|
+
getRunPassedCount(run) {
|
|
2317
|
+
let count = 0;
|
|
2318
|
+
run.testResults.forEach(result => {
|
|
2319
|
+
if (result.status === 'Passed')
|
|
2320
|
+
count++;
|
|
2321
|
+
});
|
|
2322
|
+
return count;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Get total count of tests for a run
|
|
2326
|
+
*/
|
|
2327
|
+
getRunTotalCount(run) {
|
|
2328
|
+
return run.testResults.size;
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Get average human rating for a run (only from tests that have ratings)
|
|
2332
|
+
*/
|
|
2333
|
+
getRunHumanAvg(run) {
|
|
2334
|
+
let sum = 0;
|
|
2335
|
+
let count = 0;
|
|
2336
|
+
run.testResults.forEach(result => {
|
|
2337
|
+
if (result.humanRating != null) {
|
|
2338
|
+
sum += result.humanRating;
|
|
2339
|
+
count++;
|
|
2340
|
+
}
|
|
2341
|
+
});
|
|
2342
|
+
return count > 0 ? sum / count : null;
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Get count of tests with human ratings for a run
|
|
2346
|
+
*/
|
|
2347
|
+
getRunHumanCount(run) {
|
|
2348
|
+
let count = 0;
|
|
2349
|
+
run.testResults.forEach(result => {
|
|
2350
|
+
if (result.humanRating != null)
|
|
2351
|
+
count++;
|
|
2352
|
+
});
|
|
2353
|
+
return count;
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Get average auto score for a run (only from tests that have scores)
|
|
2357
|
+
*/
|
|
2358
|
+
getRunAutoAvg(run) {
|
|
2359
|
+
let sum = 0;
|
|
2360
|
+
let count = 0;
|
|
2361
|
+
run.testResults.forEach(result => {
|
|
2362
|
+
if (result.score != null) {
|
|
2363
|
+
sum += result.score;
|
|
2364
|
+
count++;
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
return count > 0 ? sum / count : null;
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Get count of tests with auto scores for a run
|
|
2371
|
+
*/
|
|
2372
|
+
getRunAutoCount(run) {
|
|
2373
|
+
let count = 0;
|
|
2374
|
+
run.testResults.forEach(result => {
|
|
2375
|
+
if (result.score != null)
|
|
2376
|
+
count++;
|
|
2377
|
+
});
|
|
2378
|
+
return count;
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Navigate to a test run when clicking a matrix cell
|
|
2382
|
+
*/
|
|
2383
|
+
openTestRun(testRunId) {
|
|
2384
|
+
SharedService.Instance.OpenEntityRecord('MJ: Test Runs', CompositeKey.FromID(testRunId));
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Handle matrix cell click - navigate to the test run
|
|
2388
|
+
*/
|
|
2389
|
+
onMatrixCellClick(result, event) {
|
|
2390
|
+
event.stopPropagation(); // Prevent row click from also firing
|
|
2391
|
+
if (result?.testRunId) {
|
|
2392
|
+
this.openTestRun(result.testRunId);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
// ===========================
|
|
2396
|
+
// D3 Chart Rendering
|
|
2397
|
+
// ===========================
|
|
2398
|
+
/**
|
|
2399
|
+
* Renders an interactive heatmap-style chart showing test results across runs
|
|
2400
|
+
* with trend lines and better visual hierarchy
|
|
2401
|
+
*/
|
|
2402
|
+
renderChart() {
|
|
2403
|
+
if (!this.chartContainer?.nativeElement || this.matrixData.length === 0) {
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
const container = this.chartContainer.nativeElement;
|
|
2407
|
+
const tests = this.getUniqueTestsFromMatrix();
|
|
2408
|
+
const runs = this.matrixData;
|
|
2409
|
+
// Dynamic sizing based on content and evaluation preferences
|
|
2410
|
+
const width = container.clientWidth || 900;
|
|
2411
|
+
const evalCount = this.getEvalCount();
|
|
2412
|
+
// Adjust row height and column width based on how many eval types are shown
|
|
2413
|
+
const rowHeight = evalCount >= 3 ? 34 : evalCount === 2 ? 30 : 28;
|
|
2414
|
+
const minColWidth = evalCount >= 3 ? 65 : evalCount === 2 ? 55 : 50;
|
|
2415
|
+
const colWidth = Math.min(90, Math.max(minColWidth, (width - 250) / runs.length));
|
|
2416
|
+
const margin = { top: 80, right: 40, bottom: 20, left: 220 };
|
|
2417
|
+
const chartWidth = runs.length * colWidth;
|
|
2418
|
+
const chartHeight = tests.length * rowHeight;
|
|
2419
|
+
const height = chartHeight + margin.top + margin.bottom;
|
|
2420
|
+
// Update container height
|
|
2421
|
+
container.style.height = `${Math.max(400, height)}px`;
|
|
2422
|
+
// Clear previous chart
|
|
2423
|
+
d3.select(container).selectAll('*').remove();
|
|
2424
|
+
// Status colors with better contrast
|
|
2425
|
+
const statusColors = {
|
|
2426
|
+
'Passed': '#22c55e',
|
|
2427
|
+
'Failed': '#ef4444',
|
|
2428
|
+
'Error': '#f97316',
|
|
2429
|
+
'Skipped': '#a1a1aa',
|
|
2430
|
+
'Running': '#3b82f6',
|
|
2431
|
+
'Pending': '#d1d5db'
|
|
2432
|
+
};
|
|
2433
|
+
// Create SVG
|
|
2434
|
+
const svg = d3.select(container)
|
|
2435
|
+
.append('svg')
|
|
2436
|
+
.attr('width', width)
|
|
2437
|
+
.attr('height', height);
|
|
2438
|
+
// Create tooltip div
|
|
2439
|
+
const tooltip = d3.select(container)
|
|
2440
|
+
.append('div')
|
|
2441
|
+
.attr('class', 'chart-tooltip')
|
|
2442
|
+
.style('position', 'absolute')
|
|
2443
|
+
.style('opacity', 0)
|
|
2444
|
+
.style('background', 'rgba(15, 23, 42, 0.95)')
|
|
2445
|
+
.style('color', 'white')
|
|
2446
|
+
.style('padding', '10px 14px')
|
|
2447
|
+
.style('border-radius', '8px')
|
|
2448
|
+
.style('font-size', '12px')
|
|
2449
|
+
.style('pointer-events', 'none')
|
|
2450
|
+
.style('z-index', '1000')
|
|
2451
|
+
.style('box-shadow', '0 4px 20px rgba(0,0,0,0.3)')
|
|
2452
|
+
.style('max-width', '280px');
|
|
2453
|
+
// Create chart group
|
|
2454
|
+
const chart = svg.append('g')
|
|
2455
|
+
.attr('transform', `translate(${margin.left}, ${margin.top})`);
|
|
2456
|
+
// Add gradient definitions for cells
|
|
2457
|
+
const defs = svg.append('defs');
|
|
2458
|
+
// Create gradient for each status
|
|
2459
|
+
Object.entries(statusColors).forEach(([status, color]) => {
|
|
2460
|
+
const gradient = defs.append('linearGradient')
|
|
2461
|
+
.attr('id', `gradient-${status.toLowerCase()}`)
|
|
2462
|
+
.attr('x1', '0%')
|
|
2463
|
+
.attr('y1', '0%')
|
|
2464
|
+
.attr('x2', '0%')
|
|
2465
|
+
.attr('y2', '100%');
|
|
2466
|
+
gradient.append('stop')
|
|
2467
|
+
.attr('offset', '0%')
|
|
2468
|
+
.attr('stop-color', color)
|
|
2469
|
+
.attr('stop-opacity', 0.95);
|
|
2470
|
+
gradient.append('stop')
|
|
2471
|
+
.attr('offset', '100%')
|
|
2472
|
+
.attr('stop-color', d3.color(color)?.darker(0.3)?.toString() || color)
|
|
2473
|
+
.attr('stop-opacity', 0.95);
|
|
2474
|
+
});
|
|
2475
|
+
// Draw evaluation legend at top-left corner
|
|
2476
|
+
this.renderChartLegend(chart, margin);
|
|
2477
|
+
// Draw column headers (run dates) at top
|
|
2478
|
+
runs.forEach((run, i) => {
|
|
2479
|
+
const x = i * colWidth + colWidth / 2;
|
|
2480
|
+
// Date text
|
|
2481
|
+
chart.append('text')
|
|
2482
|
+
.attr('x', x)
|
|
2483
|
+
.attr('y', -45)
|
|
2484
|
+
.attr('text-anchor', 'middle')
|
|
2485
|
+
.attr('fill', '#475569')
|
|
2486
|
+
.attr('font-size', '10px')
|
|
2487
|
+
.attr('font-weight', '500')
|
|
2488
|
+
.text(this.getRelativeTime(run.date))
|
|
2489
|
+
.style('cursor', 'pointer')
|
|
2490
|
+
.on('click', () => this.openSuiteRun(run.runId));
|
|
2491
|
+
// Pass rate badge
|
|
2492
|
+
const passRateColor = run.passRate >= 80 ? '#22c55e' : run.passRate >= 50 ? '#f97316' : '#ef4444';
|
|
2493
|
+
const badgeWidth = 36;
|
|
2494
|
+
const badgeHeight = 18;
|
|
2495
|
+
chart.append('rect')
|
|
2496
|
+
.attr('x', x - badgeWidth / 2)
|
|
2497
|
+
.attr('y', -35)
|
|
2498
|
+
.attr('width', badgeWidth)
|
|
2499
|
+
.attr('height', badgeHeight)
|
|
2500
|
+
.attr('rx', 9)
|
|
2501
|
+
.attr('fill', passRateColor)
|
|
2502
|
+
.attr('opacity', 0.15);
|
|
2503
|
+
chart.append('text')
|
|
2504
|
+
.attr('x', x)
|
|
2505
|
+
.attr('y', -22)
|
|
2506
|
+
.attr('text-anchor', 'middle')
|
|
2507
|
+
.attr('fill', passRateColor)
|
|
2508
|
+
.attr('font-size', '11px')
|
|
2509
|
+
.attr('font-weight', '700')
|
|
2510
|
+
.text(`${run.passRate.toFixed(0)}%`);
|
|
2511
|
+
// Tags indicator - styled pills
|
|
2512
|
+
if (run.tags.length > 0) {
|
|
2513
|
+
const tagsToShow = run.tags.slice(0, 2);
|
|
2514
|
+
const tagPillWidth = 42;
|
|
2515
|
+
const tagPillHeight = 14;
|
|
2516
|
+
const tagGap = 4;
|
|
2517
|
+
const totalTagsWidth = tagsToShow.length * tagPillWidth + (tagsToShow.length - 1) * tagGap;
|
|
2518
|
+
const tagStartX = x - totalTagsWidth / 2;
|
|
2519
|
+
tagsToShow.forEach((tag, tagIndex) => {
|
|
2520
|
+
const tagX = tagStartX + tagIndex * (tagPillWidth + tagGap);
|
|
2521
|
+
const tagY = -68;
|
|
2522
|
+
// Pill background with gradient
|
|
2523
|
+
chart.append('rect')
|
|
2524
|
+
.attr('x', tagX)
|
|
2525
|
+
.attr('y', tagY)
|
|
2526
|
+
.attr('width', tagPillWidth)
|
|
2527
|
+
.attr('height', tagPillHeight)
|
|
2528
|
+
.attr('rx', 7)
|
|
2529
|
+
.attr('fill', '#dbeafe')
|
|
2530
|
+
.attr('stroke', '#93c5fd')
|
|
2531
|
+
.attr('stroke-width', 1)
|
|
2532
|
+
.style('cursor', 'pointer')
|
|
2533
|
+
.on('click', () => this.openSuiteRun(run.runId));
|
|
2534
|
+
// Tag text
|
|
2535
|
+
chart.append('text')
|
|
2536
|
+
.attr('x', tagX + tagPillWidth / 2)
|
|
2537
|
+
.attr('y', tagY + tagPillHeight / 2 + 3)
|
|
2538
|
+
.attr('text-anchor', 'middle')
|
|
2539
|
+
.attr('fill', '#1d4ed8')
|
|
2540
|
+
.attr('font-size', '8px')
|
|
2541
|
+
.attr('font-weight', '600')
|
|
2542
|
+
.text(tag.length > 8 ? tag.substring(0, 7) + '…' : tag)
|
|
2543
|
+
.style('cursor', 'pointer')
|
|
2544
|
+
.on('click', () => this.openSuiteRun(run.runId));
|
|
2545
|
+
});
|
|
2546
|
+
// Show +N indicator if more tags
|
|
2547
|
+
if (run.tags.length > 2) {
|
|
2548
|
+
const moreX = tagStartX + tagsToShow.length * (tagPillWidth + tagGap);
|
|
2549
|
+
chart.append('text')
|
|
2550
|
+
.attr('x', moreX)
|
|
2551
|
+
.attr('y', -68 + tagPillHeight / 2 + 3)
|
|
2552
|
+
.attr('text-anchor', 'start')
|
|
2553
|
+
.attr('fill', '#64748b')
|
|
2554
|
+
.attr('font-size', '8px')
|
|
2555
|
+
.attr('font-weight', '500')
|
|
2556
|
+
.text(`+${run.tags.length - 2}`);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
// Draw row labels (test names) on left
|
|
2561
|
+
tests.forEach((test, i) => {
|
|
2562
|
+
const y = i * rowHeight + rowHeight / 2;
|
|
2563
|
+
chart.append('text')
|
|
2564
|
+
.attr('x', -12)
|
|
2565
|
+
.attr('y', y + 4)
|
|
2566
|
+
.attr('text-anchor', 'end')
|
|
2567
|
+
.attr('fill', '#334155')
|
|
2568
|
+
.attr('font-size', '11px')
|
|
2569
|
+
.text(test.testName.length > 28 ? test.testName.substring(0, 28) + '...' : test.testName)
|
|
2570
|
+
.style('cursor', 'pointer')
|
|
2571
|
+
.on('click', () => {
|
|
2572
|
+
SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(test.testId));
|
|
2573
|
+
})
|
|
2574
|
+
.on('mouseover', function () {
|
|
2575
|
+
d3.select(this).attr('fill', '#3b82f6').attr('font-weight', '600');
|
|
2576
|
+
})
|
|
2577
|
+
.on('mouseout', function () {
|
|
2578
|
+
d3.select(this).attr('fill', '#334155').attr('font-weight', 'normal');
|
|
2579
|
+
});
|
|
2580
|
+
});
|
|
2581
|
+
// Draw grid lines
|
|
2582
|
+
tests.forEach((_, i) => {
|
|
2583
|
+
chart.append('line')
|
|
2584
|
+
.attr('x1', 0)
|
|
2585
|
+
.attr('y1', (i + 1) * rowHeight)
|
|
2586
|
+
.attr('x2', chartWidth)
|
|
2587
|
+
.attr('y2', (i + 1) * rowHeight)
|
|
2588
|
+
.attr('stroke', '#e2e8f0')
|
|
2589
|
+
.attr('stroke-width', 1);
|
|
2590
|
+
});
|
|
2591
|
+
runs.forEach((_, i) => {
|
|
2592
|
+
chart.append('line')
|
|
2593
|
+
.attr('x1', (i + 1) * colWidth)
|
|
2594
|
+
.attr('y1', 0)
|
|
2595
|
+
.attr('x2', (i + 1) * colWidth)
|
|
2596
|
+
.attr('y2', chartHeight)
|
|
2597
|
+
.attr('stroke', '#e2e8f0')
|
|
2598
|
+
.attr('stroke-width', 1);
|
|
2599
|
+
});
|
|
2600
|
+
// Draw cells with evaluation toggle support
|
|
2601
|
+
const cellPadding = 3;
|
|
2602
|
+
runs.forEach((run, runIndex) => {
|
|
2603
|
+
tests.forEach((test, testIndex) => {
|
|
2604
|
+
const result = run.testResults.get(test.testId);
|
|
2605
|
+
const x = runIndex * colWidth + cellPadding;
|
|
2606
|
+
const y = testIndex * rowHeight + cellPadding;
|
|
2607
|
+
const cellWidth = colWidth - cellPadding * 2;
|
|
2608
|
+
const cellHeight = rowHeight - cellPadding * 2;
|
|
2609
|
+
if (!result) {
|
|
2610
|
+
// Empty cell indicator
|
|
2611
|
+
chart.append('rect')
|
|
2612
|
+
.attr('x', x)
|
|
2613
|
+
.attr('y', y)
|
|
2614
|
+
.attr('width', cellWidth)
|
|
2615
|
+
.attr('height', cellHeight)
|
|
2616
|
+
.attr('rx', 4)
|
|
2617
|
+
.attr('fill', '#f8fafc')
|
|
2618
|
+
.attr('stroke', '#e2e8f0')
|
|
2619
|
+
.attr('stroke-width', 1);
|
|
2620
|
+
chart.append('text')
|
|
2621
|
+
.attr('x', x + cellWidth / 2)
|
|
2622
|
+
.attr('y', y + cellHeight / 2 + 4)
|
|
2623
|
+
.attr('text-anchor', 'middle')
|
|
2624
|
+
.attr('fill', '#cbd5e1')
|
|
2625
|
+
.attr('font-size', '12px')
|
|
2626
|
+
.text('—');
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
// Build tooltip content based on evaluation preferences
|
|
2630
|
+
const tooltipParts = [];
|
|
2631
|
+
if (this.evalPreferences.showExecution) {
|
|
2632
|
+
tooltipParts.push(`<div style="display:inline-block; padding:2px 8px; border-radius:4px; background:${statusColors[result.status]}; color:white; font-size:11px; font-weight:600">${result.status}</div>`);
|
|
2633
|
+
}
|
|
2634
|
+
if (this.evalPreferences.showHuman) {
|
|
2635
|
+
tooltipParts.push(`<div style="margin-top:4px"><span style="color:#f59e0b">👤</span> <strong>Human:</strong> <span style="color:#94a3b8">Needs review</span></div>`);
|
|
2636
|
+
}
|
|
2637
|
+
if (this.evalPreferences.showAuto && result.score != null) {
|
|
2638
|
+
tooltipParts.push(`<div style="margin-top:4px"><span style="color:#3b82f6">🤖</span> <strong>Auto:</strong> ${(result.score * 100).toFixed(1)}%</div>`);
|
|
2639
|
+
}
|
|
2640
|
+
const durationText = result.duration != null ? `<div><strong>Duration:</strong> ${result.duration.toFixed(2)}s</div>` : '';
|
|
2641
|
+
const cellGroup = chart.append('g')
|
|
2642
|
+
.attr('class', 'result-cell')
|
|
2643
|
+
.style('cursor', 'pointer')
|
|
2644
|
+
.on('click', () => this.openTestRun(result.testRunId))
|
|
2645
|
+
.on('mouseover', (event) => {
|
|
2646
|
+
d3.select(event.currentTarget).select('rect.cell-bg')
|
|
2647
|
+
.attr('stroke', '#1e40af')
|
|
2648
|
+
.attr('stroke-width', 2);
|
|
2649
|
+
tooltip
|
|
2650
|
+
.style('opacity', 1)
|
|
2651
|
+
.html(`
|
|
2652
|
+
<div style="font-weight:600; margin-bottom:6px; color:#f1f5f9">${result.testName}</div>
|
|
2653
|
+
${tooltipParts.join('')}
|
|
2654
|
+
${durationText}
|
|
2655
|
+
<div style="margin-top:6px; color:#94a3b8; font-size:10px">${this.getRelativeTime(run.date)} • Click to view</div>
|
|
2656
|
+
`)
|
|
2657
|
+
.style('left', `${event.offsetX + 15}px`)
|
|
2658
|
+
.style('top', `${event.offsetY - 10}px`);
|
|
2659
|
+
})
|
|
2660
|
+
.on('mouseout', (event) => {
|
|
2661
|
+
d3.select(event.currentTarget).select('rect.cell-bg')
|
|
2662
|
+
.attr('stroke', 'none')
|
|
2663
|
+
.attr('stroke-width', 0);
|
|
2664
|
+
tooltip.style('opacity', 0);
|
|
2665
|
+
});
|
|
2666
|
+
// Determine cell background color based on eval preferences
|
|
2667
|
+
let cellBgColor = '#f1f5f9';
|
|
2668
|
+
let cellBgGradient = '';
|
|
2669
|
+
if (this.evalPreferences.showExecution) {
|
|
2670
|
+
// Use status-based gradient
|
|
2671
|
+
cellBgGradient = `url(#gradient-${result.status.toLowerCase()})`;
|
|
2672
|
+
}
|
|
2673
|
+
else if (this.evalPreferences.showAuto && result.score != null) {
|
|
2674
|
+
// Use score-based color
|
|
2675
|
+
const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
|
|
2676
|
+
cellBgColor = scoreColor;
|
|
2677
|
+
}
|
|
2678
|
+
else {
|
|
2679
|
+
// Neutral background when only Human is selected
|
|
2680
|
+
cellBgColor = '#fef3c7';
|
|
2681
|
+
}
|
|
2682
|
+
// Cell background
|
|
2683
|
+
cellGroup.append('rect')
|
|
2684
|
+
.attr('class', 'cell-bg')
|
|
2685
|
+
.attr('x', x)
|
|
2686
|
+
.attr('y', y)
|
|
2687
|
+
.attr('width', cellWidth)
|
|
2688
|
+
.attr('height', cellHeight)
|
|
2689
|
+
.attr('rx', 4)
|
|
2690
|
+
.attr('fill', cellBgGradient || cellBgColor)
|
|
2691
|
+
.attr('stroke', 'none')
|
|
2692
|
+
.attr('stroke-width', 0)
|
|
2693
|
+
.style('transition', 'all 0.15s ease');
|
|
2694
|
+
// Calculate icon positions based on how many eval types are shown
|
|
2695
|
+
// With wider cells, we can use larger icons and better spacing
|
|
2696
|
+
const iconSize = evalCount === 1 ? 14 : evalCount === 2 ? 12 : 10;
|
|
2697
|
+
const iconSpacing = evalCount === 1 ? 0 : evalCount === 2 ? 16 : 14;
|
|
2698
|
+
const startX = x + cellWidth / 2 - ((evalCount - 1) * iconSpacing) / 2;
|
|
2699
|
+
let iconIndex = 0;
|
|
2700
|
+
// Status icon (execution)
|
|
2701
|
+
if (this.evalPreferences.showExecution) {
|
|
2702
|
+
const iconText = {
|
|
2703
|
+
'Passed': '✓',
|
|
2704
|
+
'Failed': '✕',
|
|
2705
|
+
'Error': '!',
|
|
2706
|
+
'Skipped': '»',
|
|
2707
|
+
'Running': '●',
|
|
2708
|
+
'Pending': '○'
|
|
2709
|
+
};
|
|
2710
|
+
const iconX = startX + iconIndex * iconSpacing;
|
|
2711
|
+
// If status is NOT the only thing shown, add a small circular bg
|
|
2712
|
+
if (evalCount > 1) {
|
|
2713
|
+
cellGroup.append('circle')
|
|
2714
|
+
.attr('cx', iconX)
|
|
2715
|
+
.attr('cy', y + cellHeight / 2)
|
|
2716
|
+
.attr('r', iconSize / 2 + 3)
|
|
2717
|
+
.attr('fill', statusColors[result.status])
|
|
2718
|
+
.attr('opacity', 0.9);
|
|
2719
|
+
}
|
|
2720
|
+
cellGroup.append('text')
|
|
2721
|
+
.attr('x', iconX)
|
|
2722
|
+
.attr('y', y + cellHeight / 2 + iconSize / 3)
|
|
2723
|
+
.attr('text-anchor', 'middle')
|
|
2724
|
+
.attr('fill', 'white')
|
|
2725
|
+
.attr('font-size', `${iconSize}px`)
|
|
2726
|
+
.attr('font-weight', 'bold')
|
|
2727
|
+
.attr('font-family', 'system-ui, -apple-system, sans-serif')
|
|
2728
|
+
.text(iconText[result.status] || '?');
|
|
2729
|
+
iconIndex++;
|
|
2730
|
+
}
|
|
2731
|
+
// Human icon
|
|
2732
|
+
if (this.evalPreferences.showHuman) {
|
|
2733
|
+
const iconX = startX + iconIndex * iconSpacing;
|
|
2734
|
+
// Human evaluation indicator (clock icon for pending)
|
|
2735
|
+
cellGroup.append('circle')
|
|
2736
|
+
.attr('cx', iconX)
|
|
2737
|
+
.attr('cy', y + cellHeight / 2)
|
|
2738
|
+
.attr('r', iconSize / 2 + 3)
|
|
2739
|
+
.attr('fill', '#fef3c7')
|
|
2740
|
+
.attr('stroke', '#f59e0b')
|
|
2741
|
+
.attr('stroke-width', 1);
|
|
2742
|
+
cellGroup.append('text')
|
|
2743
|
+
.attr('x', iconX)
|
|
2744
|
+
.attr('y', y + cellHeight / 2 + iconSize / 3)
|
|
2745
|
+
.attr('text-anchor', 'middle')
|
|
2746
|
+
.attr('fill', '#d97706')
|
|
2747
|
+
.attr('font-size', `${iconSize - 2}px`)
|
|
2748
|
+
.attr('font-weight', '500')
|
|
2749
|
+
.text('⏱');
|
|
2750
|
+
iconIndex++;
|
|
2751
|
+
}
|
|
2752
|
+
// Auto score icon/indicator
|
|
2753
|
+
if (this.evalPreferences.showAuto) {
|
|
2754
|
+
const iconX = startX + iconIndex * iconSpacing;
|
|
2755
|
+
if (result.score != null) {
|
|
2756
|
+
const scorePercent = Math.round(result.score * 100);
|
|
2757
|
+
const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
|
|
2758
|
+
// Score pill background
|
|
2759
|
+
if (evalCount > 1) {
|
|
2760
|
+
cellGroup.append('rect')
|
|
2761
|
+
.attr('x', iconX - 10)
|
|
2762
|
+
.attr('y', y + cellHeight / 2 - iconSize / 2 - 1)
|
|
2763
|
+
.attr('width', 20)
|
|
2764
|
+
.attr('height', iconSize + 2)
|
|
2765
|
+
.attr('rx', (iconSize + 2) / 2)
|
|
2766
|
+
.attr('fill', scoreColor)
|
|
2767
|
+
.attr('opacity', 0.9);
|
|
2768
|
+
}
|
|
2769
|
+
cellGroup.append('text')
|
|
2770
|
+
.attr('x', iconX)
|
|
2771
|
+
.attr('y', y + cellHeight / 2 + iconSize / 3)
|
|
2772
|
+
.attr('text-anchor', 'middle')
|
|
2773
|
+
.attr('fill', evalCount > 1 ? 'white' : 'white')
|
|
2774
|
+
.attr('font-size', `${iconSize - 1}px`)
|
|
2775
|
+
.attr('font-weight', '700')
|
|
2776
|
+
.text(`${scorePercent}`);
|
|
2777
|
+
}
|
|
2778
|
+
else {
|
|
2779
|
+
// No auto score available
|
|
2780
|
+
cellGroup.append('circle')
|
|
2781
|
+
.attr('cx', iconX)
|
|
2782
|
+
.attr('cy', y + cellHeight / 2)
|
|
2783
|
+
.attr('r', iconSize / 2 + 2)
|
|
2784
|
+
.attr('fill', '#e2e8f0')
|
|
2785
|
+
.attr('stroke', '#94a3b8')
|
|
2786
|
+
.attr('stroke-width', 1);
|
|
2787
|
+
cellGroup.append('text')
|
|
2788
|
+
.attr('x', iconX)
|
|
2789
|
+
.attr('y', y + cellHeight / 2 + iconSize / 3)
|
|
2790
|
+
.attr('text-anchor', 'middle')
|
|
2791
|
+
.attr('fill', '#94a3b8')
|
|
2792
|
+
.attr('font-size', `${iconSize - 2}px`)
|
|
2793
|
+
.text('—');
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
});
|
|
2797
|
+
});
|
|
2798
|
+
// Draw trend line for pass rate across runs
|
|
2799
|
+
if (runs.length > 1) {
|
|
2800
|
+
const trendLineY = chartHeight + 50;
|
|
2801
|
+
const trendHeight = 40;
|
|
2802
|
+
// Trend line label
|
|
2803
|
+
chart.append('text')
|
|
2804
|
+
.attr('x', -12)
|
|
2805
|
+
.attr('y', trendLineY + trendHeight / 2)
|
|
2806
|
+
.attr('text-anchor', 'end')
|
|
2807
|
+
.attr('fill', '#64748b')
|
|
2808
|
+
.attr('font-size', '10px')
|
|
2809
|
+
.attr('font-weight', '500')
|
|
2810
|
+
.text('Pass Rate Trend');
|
|
2811
|
+
// Create trend line path
|
|
2812
|
+
const lineGenerator = d3.line()
|
|
2813
|
+
.x((_, i) => i * colWidth + colWidth / 2)
|
|
2814
|
+
.y(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
|
|
2815
|
+
.curve(d3.curveMonotoneX);
|
|
2816
|
+
// Draw area under line
|
|
2817
|
+
const areaGenerator = d3.area()
|
|
2818
|
+
.x((_, i) => i * colWidth + colWidth / 2)
|
|
2819
|
+
.y0(trendLineY + trendHeight)
|
|
2820
|
+
.y1(d => trendLineY + trendHeight - (d.passRate / 100) * trendHeight)
|
|
2821
|
+
.curve(d3.curveMonotoneX);
|
|
2822
|
+
chart.append('path')
|
|
2823
|
+
.datum(runs)
|
|
2824
|
+
.attr('d', areaGenerator)
|
|
2825
|
+
.attr('fill', 'url(#trendGradient)')
|
|
2826
|
+
.attr('opacity', 0.3);
|
|
2827
|
+
// Create gradient for trend area
|
|
2828
|
+
const trendGradient = defs.append('linearGradient')
|
|
2829
|
+
.attr('id', 'trendGradient')
|
|
2830
|
+
.attr('x1', '0%')
|
|
2831
|
+
.attr('y1', '0%')
|
|
2832
|
+
.attr('x2', '0%')
|
|
2833
|
+
.attr('y2', '100%');
|
|
2834
|
+
trendGradient.append('stop')
|
|
2835
|
+
.attr('offset', '0%')
|
|
2836
|
+
.attr('stop-color', '#3b82f6')
|
|
2837
|
+
.attr('stop-opacity', 0.4);
|
|
2838
|
+
trendGradient.append('stop')
|
|
2839
|
+
.attr('offset', '100%')
|
|
2840
|
+
.attr('stop-color', '#3b82f6')
|
|
2841
|
+
.attr('stop-opacity', 0);
|
|
2842
|
+
chart.append('path')
|
|
2843
|
+
.datum(runs)
|
|
2844
|
+
.attr('d', lineGenerator)
|
|
2845
|
+
.attr('fill', 'none')
|
|
2846
|
+
.attr('stroke', '#3b82f6')
|
|
2847
|
+
.attr('stroke-width', 2.5)
|
|
2848
|
+
.attr('stroke-linecap', 'round');
|
|
2849
|
+
// Draw dots on trend line
|
|
2850
|
+
runs.forEach((run, i) => {
|
|
2851
|
+
const cx = i * colWidth + colWidth / 2;
|
|
2852
|
+
const cy = trendLineY + trendHeight - (run.passRate / 100) * trendHeight;
|
|
2853
|
+
chart.append('circle')
|
|
2854
|
+
.attr('cx', cx)
|
|
2855
|
+
.attr('cy', cy)
|
|
2856
|
+
.attr('r', 4)
|
|
2857
|
+
.attr('fill', 'white')
|
|
2858
|
+
.attr('stroke', '#3b82f6')
|
|
2859
|
+
.attr('stroke-width', 2);
|
|
2860
|
+
});
|
|
2861
|
+
// Update container height for trend line
|
|
2862
|
+
container.style.height = `${Math.max(400, height + 80)}px`;
|
|
2863
|
+
svg.attr('height', height + 80);
|
|
2864
|
+
}
|
|
2865
|
+
this.chartRendered = true;
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Renders a legend showing which evaluation types are currently displayed
|
|
2869
|
+
*/
|
|
2870
|
+
renderChartLegend(chart, margin) {
|
|
2871
|
+
const legendGroup = chart.append('g')
|
|
2872
|
+
.attr('class', 'eval-legend')
|
|
2873
|
+
.attr('transform', `translate(${-margin.left + 10}, ${-margin.top + 15})`);
|
|
2874
|
+
// Legend title
|
|
2875
|
+
legendGroup.append('text')
|
|
2876
|
+
.attr('x', 0)
|
|
2877
|
+
.attr('y', 0)
|
|
2878
|
+
.attr('fill', '#64748b')
|
|
2879
|
+
.attr('font-size', '9px')
|
|
2880
|
+
.attr('font-weight', '600')
|
|
2881
|
+
.attr('text-transform', 'uppercase')
|
|
2882
|
+
.text('SHOWING:');
|
|
2883
|
+
let xOffset = 55;
|
|
2884
|
+
// Status indicator
|
|
2885
|
+
if (this.evalPreferences.showExecution) {
|
|
2886
|
+
const statusGroup = legendGroup.append('g')
|
|
2887
|
+
.attr('transform', `translate(${xOffset}, -4)`);
|
|
2888
|
+
statusGroup.append('circle')
|
|
2889
|
+
.attr('cx', 6)
|
|
2890
|
+
.attr('cy', 0)
|
|
2891
|
+
.attr('r', 6)
|
|
2892
|
+
.attr('fill', '#22c55e');
|
|
2893
|
+
statusGroup.append('text')
|
|
2894
|
+
.attr('x', 6)
|
|
2895
|
+
.attr('y', 4)
|
|
2896
|
+
.attr('text-anchor', 'middle')
|
|
2897
|
+
.attr('fill', 'white')
|
|
2898
|
+
.attr('font-size', '8px')
|
|
2899
|
+
.attr('font-weight', 'bold')
|
|
2900
|
+
.text('✓');
|
|
2901
|
+
statusGroup.append('text')
|
|
2902
|
+
.attr('x', 16)
|
|
2903
|
+
.attr('y', 3)
|
|
2904
|
+
.attr('fill', '#475569')
|
|
2905
|
+
.attr('font-size', '10px')
|
|
2906
|
+
.attr('font-weight', '500')
|
|
2907
|
+
.text('Status');
|
|
2908
|
+
xOffset += 60;
|
|
2909
|
+
}
|
|
2910
|
+
// Human indicator
|
|
2911
|
+
if (this.evalPreferences.showHuman) {
|
|
2912
|
+
const humanGroup = legendGroup.append('g')
|
|
2913
|
+
.attr('transform', `translate(${xOffset}, -4)`);
|
|
2914
|
+
humanGroup.append('circle')
|
|
2915
|
+
.attr('cx', 6)
|
|
2916
|
+
.attr('cy', 0)
|
|
2917
|
+
.attr('r', 6)
|
|
2918
|
+
.attr('fill', '#fef3c7')
|
|
2919
|
+
.attr('stroke', '#f59e0b')
|
|
2920
|
+
.attr('stroke-width', 1);
|
|
2921
|
+
humanGroup.append('text')
|
|
2922
|
+
.attr('x', 6)
|
|
2923
|
+
.attr('y', 3)
|
|
2924
|
+
.attr('text-anchor', 'middle')
|
|
2925
|
+
.attr('fill', '#d97706')
|
|
2926
|
+
.attr('font-size', '7px')
|
|
2927
|
+
.text('⏱');
|
|
2928
|
+
humanGroup.append('text')
|
|
2929
|
+
.attr('x', 16)
|
|
2930
|
+
.attr('y', 3)
|
|
2931
|
+
.attr('fill', '#475569')
|
|
2932
|
+
.attr('font-size', '10px')
|
|
2933
|
+
.attr('font-weight', '500')
|
|
2934
|
+
.text('Human');
|
|
2935
|
+
xOffset += 60;
|
|
2936
|
+
}
|
|
2937
|
+
// Auto indicator
|
|
2938
|
+
if (this.evalPreferences.showAuto) {
|
|
2939
|
+
const autoGroup = legendGroup.append('g')
|
|
2940
|
+
.attr('transform', `translate(${xOffset}, -4)`);
|
|
2941
|
+
autoGroup.append('rect')
|
|
2942
|
+
.attr('x', 0)
|
|
2943
|
+
.attr('y', -6)
|
|
2944
|
+
.attr('width', 18)
|
|
2945
|
+
.attr('height', 12)
|
|
2946
|
+
.attr('rx', 6)
|
|
2947
|
+
.attr('fill', '#3b82f6');
|
|
2948
|
+
autoGroup.append('text')
|
|
2949
|
+
.attr('x', 9)
|
|
2950
|
+
.attr('y', 3)
|
|
2951
|
+
.attr('text-anchor', 'middle')
|
|
2952
|
+
.attr('fill', 'white')
|
|
2953
|
+
.attr('font-size', '7px')
|
|
2954
|
+
.attr('font-weight', '700')
|
|
2955
|
+
.text('%');
|
|
2956
|
+
autoGroup.append('text')
|
|
2957
|
+
.attr('x', 24)
|
|
2958
|
+
.attr('y', 3)
|
|
2959
|
+
.attr('fill', '#475569')
|
|
2960
|
+
.attr('font-size', '10px')
|
|
2961
|
+
.attr('font-weight', '500')
|
|
2962
|
+
.text('Auto');
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
getAveragePassRate() {
|
|
2966
|
+
const data = this.getFilteredAnalyticsData();
|
|
2967
|
+
if (data.length === 0)
|
|
2968
|
+
return 0;
|
|
2969
|
+
return data.reduce((sum, d) => sum + d.passRate, 0) / data.length;
|
|
2970
|
+
}
|
|
2971
|
+
getTotalRuns() {
|
|
2972
|
+
return this.getFilteredAnalyticsData().length;
|
|
2973
|
+
}
|
|
2974
|
+
getAverageDuration() {
|
|
2975
|
+
const data = this.getFilteredAnalyticsData();
|
|
2976
|
+
if (data.length === 0)
|
|
2977
|
+
return 0;
|
|
2978
|
+
return data.reduce((sum, d) => sum + d.duration, 0) / data.length;
|
|
2979
|
+
}
|
|
2980
|
+
getTotalCost() {
|
|
2981
|
+
return this.getFilteredAnalyticsData().reduce((sum, d) => sum + d.cost, 0);
|
|
2982
|
+
}
|
|
2983
|
+
getPassRateTrend() {
|
|
2984
|
+
const data = this.getFilteredAnalyticsData();
|
|
2985
|
+
if (data.length < 2)
|
|
2986
|
+
return { direction: 'stable', value: 0 };
|
|
2987
|
+
// Compare recent half to older half
|
|
2988
|
+
const midpoint = Math.floor(data.length / 2);
|
|
2989
|
+
const recentData = data.slice(0, midpoint);
|
|
2990
|
+
const olderData = data.slice(midpoint);
|
|
2991
|
+
const recentAvg = recentData.reduce((s, d) => s + d.passRate, 0) / recentData.length;
|
|
2992
|
+
const olderAvg = olderData.reduce((s, d) => s + d.passRate, 0) / olderData.length;
|
|
2993
|
+
const diff = recentAvg - olderAvg;
|
|
2994
|
+
if (Math.abs(diff) < 1)
|
|
2995
|
+
return { direction: 'stable', value: diff };
|
|
2996
|
+
return { direction: diff > 0 ? 'up' : 'down', value: Math.abs(diff) };
|
|
2997
|
+
}
|
|
2998
|
+
formatDuration(seconds) {
|
|
2999
|
+
if (seconds < 60)
|
|
3000
|
+
return `${seconds.toFixed(1)}s`;
|
|
3001
|
+
if (seconds < 3600) {
|
|
3002
|
+
const mins = Math.floor(seconds / 60);
|
|
3003
|
+
const secs = Math.floor(seconds % 60);
|
|
3004
|
+
return secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
|
3005
|
+
}
|
|
3006
|
+
const hours = Math.floor(seconds / 3600);
|
|
3007
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
3008
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
3009
|
+
}
|
|
3010
|
+
formatCost(cost) {
|
|
3011
|
+
if (cost < 0.01)
|
|
3012
|
+
return `$${cost.toFixed(6)}`;
|
|
3013
|
+
if (cost < 1)
|
|
3014
|
+
return `$${cost.toFixed(4)}`;
|
|
3015
|
+
return `$${cost.toFixed(2)}`;
|
|
3016
|
+
}
|
|
3017
|
+
// ==========================================
|
|
3018
|
+
// Compare Tab Methods
|
|
3019
|
+
// ==========================================
|
|
3020
|
+
async selectCompareRunA(run) {
|
|
3021
|
+
this.compareRunA = run;
|
|
3022
|
+
await this.loadCompareData();
|
|
3023
|
+
}
|
|
3024
|
+
async selectCompareRunB(run) {
|
|
3025
|
+
this.compareRunB = run;
|
|
3026
|
+
await this.loadCompareData();
|
|
3027
|
+
}
|
|
3028
|
+
clearCompareSelection() {
|
|
3029
|
+
this.compareRunA = null;
|
|
3030
|
+
this.compareRunB = null;
|
|
3031
|
+
this.compareResults = [];
|
|
3032
|
+
this.compareRunATests = [];
|
|
3033
|
+
this.compareRunBTests = [];
|
|
3034
|
+
this.cdr.markForCheck();
|
|
3035
|
+
}
|
|
3036
|
+
async loadCompareData() {
|
|
3037
|
+
if (!this.compareRunA || !this.compareRunB) {
|
|
3038
|
+
this.compareResults = [];
|
|
3039
|
+
this.cdr.markForCheck();
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
this.loadingCompare = true;
|
|
3043
|
+
this.cdr.markForCheck();
|
|
3044
|
+
try {
|
|
3045
|
+
const rv = new RunView();
|
|
3046
|
+
// Load test runs for both suite runs in parallel
|
|
3047
|
+
const [resultA, resultB] = await Promise.all([
|
|
3048
|
+
rv.RunView({
|
|
3049
|
+
EntityName: 'MJ: Test Runs',
|
|
3050
|
+
ExtraFilter: `TestSuiteRunID='${this.compareRunA.ID}'`,
|
|
3051
|
+
OrderBy: 'Sequence ASC',
|
|
3052
|
+
ResultType: 'entity_object'
|
|
3053
|
+
}),
|
|
3054
|
+
rv.RunView({
|
|
3055
|
+
EntityName: 'MJ: Test Runs',
|
|
3056
|
+
ExtraFilter: `TestSuiteRunID='${this.compareRunB.ID}'`,
|
|
3057
|
+
OrderBy: 'Sequence ASC',
|
|
3058
|
+
ResultType: 'entity_object'
|
|
3059
|
+
})
|
|
3060
|
+
]);
|
|
3061
|
+
this.compareRunATests = resultA.Success ? resultA.Results || [] : [];
|
|
3062
|
+
this.compareRunBTests = resultB.Success ? resultB.Results || [] : [];
|
|
3063
|
+
// Build comparison results
|
|
3064
|
+
this.compareResults = this.buildComparisonResults(this.compareRunATests, this.compareRunBTests);
|
|
3065
|
+
}
|
|
3066
|
+
catch (error) {
|
|
3067
|
+
console.error('Error loading comparison data:', error);
|
|
3068
|
+
SharedService.Instance.CreateSimpleNotification('Failed to load comparison data', 'error', 3000);
|
|
3069
|
+
}
|
|
3070
|
+
finally {
|
|
3071
|
+
this.loadingCompare = false;
|
|
3072
|
+
this.cdr.markForCheck();
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
buildComparisonResults(testsA, testsB) {
|
|
3076
|
+
const results = [];
|
|
3077
|
+
const testMap = new Map();
|
|
3078
|
+
// Map all tests from run A
|
|
3079
|
+
for (const test of testsA) {
|
|
3080
|
+
testMap.set(test.TestID, { a: test, name: test.Test || 'Unknown Test' });
|
|
3081
|
+
}
|
|
3082
|
+
// Map all tests from run B
|
|
3083
|
+
for (const test of testsB) {
|
|
3084
|
+
const existing = testMap.get(test.TestID);
|
|
3085
|
+
if (existing) {
|
|
3086
|
+
existing.b = test;
|
|
3087
|
+
}
|
|
3088
|
+
else {
|
|
3089
|
+
testMap.set(test.TestID, { b: test, name: test.Test || 'Unknown Test' });
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
// Build comparison array
|
|
3093
|
+
for (const [testId, { a, b, name }] of testMap) {
|
|
3094
|
+
const comparison = {
|
|
3095
|
+
testId,
|
|
3096
|
+
testName: name,
|
|
3097
|
+
runA: a ? {
|
|
3098
|
+
status: a.Status || 'Unknown',
|
|
3099
|
+
score: a.Score,
|
|
3100
|
+
duration: a.DurationSeconds,
|
|
3101
|
+
cost: a.CostUSD
|
|
3102
|
+
} : null,
|
|
3103
|
+
runB: b ? {
|
|
3104
|
+
status: b.Status || 'Unknown',
|
|
3105
|
+
score: b.Score,
|
|
3106
|
+
duration: b.DurationSeconds,
|
|
3107
|
+
cost: b.CostUSD
|
|
3108
|
+
} : null,
|
|
3109
|
+
scoreDiff: (a?.Score != null && b?.Score != null) ? b.Score - a.Score : null,
|
|
3110
|
+
durationDiff: (a?.DurationSeconds != null && b?.DurationSeconds != null) ? b.DurationSeconds - a.DurationSeconds : null,
|
|
3111
|
+
statusChanged: (a?.Status || 'none') !== (b?.Status || 'none')
|
|
3112
|
+
};
|
|
3113
|
+
results.push(comparison);
|
|
3114
|
+
}
|
|
3115
|
+
return results;
|
|
3116
|
+
}
|
|
3117
|
+
getComparePassRateDiff() {
|
|
3118
|
+
if (!this.compareRunA || !this.compareRunB)
|
|
3119
|
+
return null;
|
|
3120
|
+
const rateA = this.getPassRate(this.compareRunA);
|
|
3121
|
+
const rateB = this.getPassRate(this.compareRunB);
|
|
3122
|
+
return rateB - rateA;
|
|
3123
|
+
}
|
|
3124
|
+
getCompareDurationDiff() {
|
|
3125
|
+
if (!this.compareRunA || !this.compareRunB)
|
|
3126
|
+
return null;
|
|
3127
|
+
const durA = this.compareRunA.TotalDurationSeconds || 0;
|
|
3128
|
+
const durB = this.compareRunB.TotalDurationSeconds || 0;
|
|
3129
|
+
return durB - durA;
|
|
3130
|
+
}
|
|
3131
|
+
getAbsCompareDurationDiff() {
|
|
3132
|
+
const diff = this.getCompareDurationDiff();
|
|
3133
|
+
return diff != null ? Math.abs(diff) : 0;
|
|
3134
|
+
}
|
|
3135
|
+
getCompareCostDiff() {
|
|
3136
|
+
if (!this.compareRunA || !this.compareRunB)
|
|
3137
|
+
return null;
|
|
3138
|
+
const costA = this.compareRunA.TotalCostUSD || 0;
|
|
3139
|
+
const costB = this.compareRunB.TotalCostUSD || 0;
|
|
3140
|
+
return costB - costA;
|
|
3141
|
+
}
|
|
3142
|
+
getCompareImprovedCount() {
|
|
3143
|
+
return this.compareResults.filter(r => r.runA && r.runB &&
|
|
3144
|
+
r.runA.status !== 'Passed' && r.runB.status === 'Passed').length;
|
|
3145
|
+
}
|
|
3146
|
+
getCompareRegressedCount() {
|
|
3147
|
+
return this.compareResults.filter(r => r.runA && r.runB &&
|
|
3148
|
+
r.runA.status === 'Passed' && r.runB.status !== 'Passed').length;
|
|
3149
|
+
}
|
|
3150
|
+
// ==========================================
|
|
3151
|
+
// Excel Export Methods
|
|
3152
|
+
// ==========================================
|
|
3153
|
+
async exportToExcel() {
|
|
3154
|
+
try {
|
|
3155
|
+
// Ensure runs are loaded
|
|
3156
|
+
if (!this.runsLoaded)
|
|
3157
|
+
await this.loadRuns();
|
|
3158
|
+
const data = this.suiteRuns.map(run => ({
|
|
3159
|
+
'Run ID': run.ID,
|
|
3160
|
+
'Status': run.Status,
|
|
3161
|
+
'Started At': run.StartedAt ? new Date(run.StartedAt).toLocaleString() : 'N/A',
|
|
3162
|
+
'Completed At': run.CompletedAt ? new Date(run.CompletedAt).toLocaleString() : 'N/A',
|
|
3163
|
+
'Duration (s)': run.TotalDurationSeconds?.toFixed(2) || 'N/A',
|
|
3164
|
+
'Total Tests': run.TotalTests || 0,
|
|
3165
|
+
'Passed': run.PassedTests || 0,
|
|
3166
|
+
'Failed': run.FailedTests || 0,
|
|
3167
|
+
'Errors': run.ErrorTests || 0,
|
|
3168
|
+
'Skipped': run.SkippedTests || 0,
|
|
3169
|
+
'Pass Rate (%)': run.TotalTests ? ((run.PassedTests || 0) / run.TotalTests * 100).toFixed(1) : 'N/A',
|
|
3170
|
+
'Total Cost ($)': run.TotalCostUSD?.toFixed(6) || 'N/A',
|
|
3171
|
+
'Tags': TagsHelper.parseTags(run.Tags).join(', ') || 'None',
|
|
3172
|
+
'Environment': run.Environment || 'N/A',
|
|
3173
|
+
'Trigger Type': run.TriggerType || 'N/A',
|
|
3174
|
+
'Run By': run.RunByUser || 'N/A'
|
|
3175
|
+
}));
|
|
3176
|
+
this.downloadAsCSV(data, `${this.record.Name}_runs_export.csv`);
|
|
3177
|
+
SharedService.Instance.CreateSimpleNotification('Export successful', 'success', 2000);
|
|
3178
|
+
}
|
|
3179
|
+
catch (error) {
|
|
3180
|
+
console.error('Export failed:', error);
|
|
3181
|
+
SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
downloadAsCSV(data, filename) {
|
|
3185
|
+
if (data.length === 0)
|
|
3186
|
+
return;
|
|
3187
|
+
const headers = Object.keys(data[0]);
|
|
3188
|
+
const csvContent = [
|
|
3189
|
+
headers.join(','),
|
|
3190
|
+
...data.map(row => headers.map(h => {
|
|
3191
|
+
const val = row[h];
|
|
3192
|
+
// Escape quotes and wrap in quotes if contains comma
|
|
3193
|
+
const strVal = String(val);
|
|
3194
|
+
if (strVal.includes(',') || strVal.includes('"') || strVal.includes('\n')) {
|
|
3195
|
+
return `"${strVal.replace(/"/g, '""')}"`;
|
|
3196
|
+
}
|
|
3197
|
+
return strVal;
|
|
3198
|
+
}).join(','))
|
|
3199
|
+
].join('\n');
|
|
3200
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
3201
|
+
const link = document.createElement('a');
|
|
3202
|
+
link.href = URL.createObjectURL(blob);
|
|
3203
|
+
link.download = filename;
|
|
3204
|
+
link.click();
|
|
3205
|
+
URL.revokeObjectURL(link.href);
|
|
3206
|
+
}
|
|
3207
|
+
getRunTags(run) {
|
|
3208
|
+
return TagsHelper.parseTags(run.Tags);
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Export matrix view data to CSV
|
|
3212
|
+
*/
|
|
3213
|
+
exportMatrixToCSV() {
|
|
3214
|
+
if (this.matrixData.length === 0) {
|
|
3215
|
+
SharedService.Instance.CreateSimpleNotification('No data to export', 'warning', 2000);
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
try {
|
|
3219
|
+
const tests = this.getUniqueTestsFromMatrix();
|
|
3220
|
+
const runs = this.matrixData;
|
|
3221
|
+
// Build CSV rows
|
|
3222
|
+
const rows = [];
|
|
3223
|
+
for (const test of tests) {
|
|
3224
|
+
const row = {
|
|
3225
|
+
'Seq': test.sequence,
|
|
3226
|
+
'Test Name': test.testName
|
|
3227
|
+
};
|
|
3228
|
+
// Add column for each run
|
|
3229
|
+
for (const run of runs) {
|
|
3230
|
+
const result = run.testResults.get(test.testId);
|
|
3231
|
+
const runLabel = run.tags.length > 0
|
|
3232
|
+
? `${run.tags.slice(0, 2).join('/')} (${this.getRelativeTime(run.date)})`
|
|
3233
|
+
: this.getRelativeTime(run.date);
|
|
3234
|
+
if (result) {
|
|
3235
|
+
// Build cell value based on what's shown
|
|
3236
|
+
const parts = [];
|
|
3237
|
+
if (this.evalPreferences.showExecution)
|
|
3238
|
+
parts.push(result.status);
|
|
3239
|
+
if (this.evalPreferences.showHuman)
|
|
3240
|
+
parts.push(result.humanRating != null ? `H:${result.humanRating}` : 'H:-');
|
|
3241
|
+
if (this.evalPreferences.showAuto)
|
|
3242
|
+
parts.push(result.score != null ? `A:${Math.round(result.score * 100)}%` : 'A:-');
|
|
3243
|
+
row[runLabel] = parts.join(' | ');
|
|
3244
|
+
}
|
|
3245
|
+
else {
|
|
3246
|
+
row[runLabel] = 'Not Run';
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
rows.push(row);
|
|
3250
|
+
}
|
|
3251
|
+
this.downloadAsCSV(rows, `${this.record.Name}_matrix_export.csv`);
|
|
3252
|
+
SharedService.Instance.CreateSimpleNotification('Matrix exported successfully', 'success', 2000);
|
|
3253
|
+
}
|
|
3254
|
+
catch (error) {
|
|
3255
|
+
console.error('Matrix export failed:', error);
|
|
3256
|
+
SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
// ==========================================
|
|
3260
|
+
// Keyboard Shortcuts Settings
|
|
3261
|
+
// ==========================================
|
|
3262
|
+
/**
|
|
3263
|
+
* Load keyboard shortcuts visibility setting from user settings
|
|
3264
|
+
*/
|
|
3265
|
+
loadShortcutsSetting() {
|
|
3266
|
+
try {
|
|
3267
|
+
const engine = UserInfoEngine.Instance;
|
|
3268
|
+
const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
|
|
3269
|
+
if (setting) {
|
|
3270
|
+
this.shortcutsSettingEntity = setting;
|
|
3271
|
+
this.showShortcuts = setting.Value === 'true';
|
|
3272
|
+
}
|
|
3273
|
+
else {
|
|
3274
|
+
// Default to hidden
|
|
3275
|
+
this.showShortcuts = false;
|
|
3276
|
+
}
|
|
3277
|
+
this.cdr.markForCheck();
|
|
3278
|
+
}
|
|
3279
|
+
catch (error) {
|
|
3280
|
+
console.warn('Failed to load shortcuts setting:', error);
|
|
281
3281
|
}
|
|
282
3282
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
3283
|
+
/**
|
|
3284
|
+
* Toggle analytics filters visibility
|
|
3285
|
+
*/
|
|
3286
|
+
toggleFilters() {
|
|
3287
|
+
this.filtersCollapsed = !this.filtersCollapsed;
|
|
3288
|
+
this.cdr.markForCheck();
|
|
3289
|
+
}
|
|
3290
|
+
/**
|
|
3291
|
+
* Toggle keyboard shortcuts visibility and save preference
|
|
3292
|
+
*/
|
|
3293
|
+
async toggleShortcuts() {
|
|
3294
|
+
this.showShortcuts = !this.showShortcuts;
|
|
3295
|
+
this.cdr.markForCheck();
|
|
3296
|
+
try {
|
|
3297
|
+
const userId = this.metadata.CurrentUser?.ID;
|
|
3298
|
+
if (!userId)
|
|
3299
|
+
return;
|
|
3300
|
+
if (!this.shortcutsSettingEntity) {
|
|
3301
|
+
const engine = UserInfoEngine.Instance;
|
|
3302
|
+
const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
|
|
3303
|
+
if (setting) {
|
|
3304
|
+
this.shortcutsSettingEntity = setting;
|
|
3305
|
+
}
|
|
3306
|
+
else {
|
|
3307
|
+
this.shortcutsSettingEntity = await this.metadata.GetEntityObject('MJ: User Settings');
|
|
3308
|
+
this.shortcutsSettingEntity.UserID = userId;
|
|
3309
|
+
this.shortcutsSettingEntity.Setting = SHORTCUTS_SETTINGS_KEY;
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
this.shortcutsSettingEntity.Value = this.showShortcuts ? 'true' : 'false';
|
|
3313
|
+
await this.shortcutsSettingEntity.Save();
|
|
3314
|
+
}
|
|
3315
|
+
catch (error) {
|
|
3316
|
+
console.warn('Failed to save shortcuts setting:', error);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
static { this.ɵfac = function TestSuiteFormComponentExtended_Factory(t) { return new (t || TestSuiteFormComponentExtended)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i1.SharedService), i0.ɵɵdirectiveInject(i2.Router), i0.ɵɵdirectiveInject(i2.ActivatedRoute), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i3.TestingDialogService), i0.ɵɵdirectiveInject(i3.EvaluationPreferencesService), i0.ɵɵdirectiveInject(i0.ViewContainerRef)); }; }
|
|
3320
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestSuiteFormComponentExtended, selectors: [["mj-test-suite-form"]], viewQuery: function TestSuiteFormComponentExtended_Query(rf, ctx) { if (rf & 1) {
|
|
3321
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
3322
|
+
} if (rf & 2) {
|
|
3323
|
+
let _t;
|
|
3324
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chartContainer = _t.first);
|
|
3325
|
+
} }, hostBindings: function TestSuiteFormComponentExtended_HostBindings(rf, ctx) { if (rf & 1) {
|
|
3326
|
+
i0.ɵɵlistener("keydown", function TestSuiteFormComponentExtended_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, false, i0.ɵɵresolveDocument);
|
|
3327
|
+
} }, features: [i0.ɵɵInheritDefinitionFeature], decls: 54, vars: 35, consts: [["chartContainer", ""], ["kendoDialogContainer", "", 1, "test-suite-form"], [1, "suite-header"], [1, "header-content"], [1, "header-left"], [1, "suite-icon"], [1, "fas", "fa-layer-group"], [1, "suite-info"], [1, "suite-meta"], [1, "status-badge", 3, "ngClass"], [1, "fas", 3, "ngClass"], ["class", "test-count", 4, "ngIf"], [1, "header-actions"], ["kendoButton", "", "title", "Export to CSV", 3, "click"], [1, "fas", "fa-file-excel"], ["kendoButton", "", "themeColor", "primary", 3, "click"], [1, "fas", "fa-play"], ["kendoButton", "", 3, "click", "disabled"], ["class", "suite-description", 4, "ngIf"], [1, "tabs-container"], ["role", "tablist", 1, "tabs"], ["role", "tab", 1, "tab", 3, "click"], [1, "fas", "fa-th-large"], [1, "fas", "fa-flask"], ["class", "tab-badge", 4, "ngIf"], [1, "fas", "fa-history"], [1, "fas", "fa-chart-line"], [1, "fas", "fa-balance-scale"], [1, "tab-content"], ["class", "overview-tab", 4, "ngIf"], ["class", "tests-tab", 4, "ngIf"], ["class", "runs-tab", 4, "ngIf"], ["class", "analytics-tab", 4, "ngIf"], ["class", "compare-tab", 4, "ngIf"], [1, "shortcuts-toggle", 3, "click", "title"], [1, "fas", "fa-keyboard"], ["class", "keyboard-shortcuts", 4, "ngIf"], [1, "test-count"], [1, "suite-description"], [1, "tab-badge"], [1, "overview-tab"], [1, "info-section"], [1, "fas", "fa-info-circle"], [1, "info-grid"], [1, "info-item"], [1, "info-label"], [1, "info-value"], [1, "status-badge-inline", 3, "ngClass"], [1, "config-section"], [1, "fas", "fa-cogs"], [1, "config-grid"], [1, "config-item"], ["type", "number", "placeholder", "Default: 300000 (5 min)", 1, "config-input", 3, "ngModelChange", "ngModel"], [1, "config-hint"], [1, "tests-tab"], ["class", "loading-state", 4, "ngIf"], ["class", "tests-list", 4, "ngIf"], ["class", "empty-state", 4, "ngIf"], [1, "loading-state"], [1, "skeleton-list"], ["class", "skeleton-card", 4, "ngFor", "ngForOf"], [1, "skeleton-card"], [1, "skeleton-sequence"], [1, "skeleton-icon"], [1, "skeleton-content"], [1, "skeleton-line", "wide"], [1, "skeleton-line", "narrow"], [1, "tests-list"], ["class", "test-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "test-item", 3, "click"], [1, "test-sequence"], [1, "test-icon"], [1, "test-content"], [1, "test-name"], [1, "test-status"], [4, "ngIf"], [1, "fas", "fa-chevron-right"], [1, "empty-state"], [1, "empty-icon"], [1, "runs-tab"], ["class", "runs-list", 4, "ngIf"], [1, "runs-list"], ["class", "run-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "run-item", 3, "click"], [1, "run-icon"], [1, "fas"], [1, "run-content"], [1, "run-header"], [1, "run-id"], [1, "run-status"], [1, "run-meta"], [1, "fas", "fa-calendar"], [1, "run-eval-metrics"], ["class", "eval-metric status", 3, "class", 4, "ngIf"], ["class", "eval-metric human", "title", "Human evaluation", 4, "ngIf"], ["class", "eval-metric auto", "title", "Auto score (pass rate)", 3, "high", "medium", "low", 4, "ngIf"], ["class", "run-tags", 4, "ngIf"], [1, "fas", "fa-check-circle"], [1, "eval-metric", "status"], ["title", "Human evaluation", 1, "eval-metric", "human"], [1, "fas", "fa-user"], [1, "eval-pending"], [1, "fas", "fa-clock"], ["title", "Auto score (pass rate)", 1, "eval-metric", "auto"], [1, "fas", "fa-robot"], [1, "run-tags"], ["class", "tag-chip", 4, "ngFor", "ngForOf"], [1, "tag-chip"], [1, "fas", "fa-play-circle"], [1, "analytics-tab"], ["text", "Loading analytics data..."], [1, "analytics-subnav"], [1, "subnav-tabs"], [1, "subnav-tab", 3, "click"], [1, "fas", "fa-chart-bar"], [1, "fas", "fa-th"], [1, "fas", "fa-project-diagram"], [1, "analytics-filters"], [1, "filters-header", 3, "click"], [1, "filters-title"], [1, "fas", "fa-filter"], ["class", "filter-summary", 4, "ngIf"], ["class", "filters-content", 4, "ngIf"], [1, "filter-summary"], [1, "filters-content"], [1, "filter-group"], [1, "filter-buttons"], [1, "filter-btn", 3, "click"], ["class", "filter-group", 4, "ngIf"], ["class", "filter-hint", 4, "ngIf"], [1, "filter-buttons", "tag-filters"], [1, "filter-btn", "tag-btn", "all-tags-btn", 3, "click"], ["class", "filter-btn tag-btn", 3, "active", "click", 4, "ngFor", "ngForOf"], [1, "filter-hint"], [1, "filter-btn", "tag-btn", 3, "click"], ["class", "fas fa-check", 4, "ngIf"], [1, "fas", "fa-check"], [1, "analytics-kpis"], [1, "kpi-card"], [1, "kpi-icon"], [1, "kpi-content"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-icon", "success"], [1, "kpi-trend", 3, "ngClass"], [1, "kpi-icon", "info"], [1, "kpi-icon", "warning"], [1, "fas", "fa-dollar-sign"], [1, "analytics-table-section"], [1, "fas", "fa-table"], [1, "analytics-table-wrapper"], [1, "analytics-table"], ["class", "clickable-row", 3, "click", 4, "ngFor", "ngForOf"], ["class", "empty-state small", 4, "ngIf"], [1, "clickable-row", 3, "click"], [1, "status-chip", 3, "ngClass"], [1, "pass-rate-cell"], [1, "pass-rate-bar", 3, "ngClass"], [1, "tag-cell"], ["class", "tag-chip-table", 4, "ngFor", "ngForOf"], ["class", "tag-more", 4, "ngIf"], [1, "tag-chip-table"], [1, "tag-more"], [1, "empty-state", "small"], ["class", "matrix-section", 4, "ngIf"], ["text", "Loading test matrix..."], [1, "matrix-section"], [1, "matrix-header"], [1, "matrix-header-right"], [1, "matrix-filter-input"], [1, "fas", "fa-search"], ["type", "text", "placeholder", "Filter tests...", 1, "filter-input", 3, "input", "value"], ["class", "clear-filter-btn", "title", "Clear filter", 3, "click", 4, "ngIf"], [1, "matrix-run-count"], ["kendoButton", "", "title", "Export matrix to CSV", 3, "click", "disabled"], [1, "fas", "fa-download"], [1, "matrix-scroll-container"], [1, "test-matrix"], ["title", "Sort by sequence", 1, "seq-header", 3, "click"], ["title", "Sort by name", 1, "test-name-header", 3, "click"], ["class", "run-header", 3, "title", "click", 4, "ngFor", "ngForOf"], [1, "spacer-header"], [3, "row-selected", "click", 4, "ngFor", "ngForOf"], [1, "totals-row"], [1, "seq-cell", "totals-label"], [1, "test-name-cell", "totals-label"], ["class", "result-cell totals-cell", 4, "ngFor", "ngForOf"], [1, "spacer-cell"], ["title", "Clear filter", 1, "clear-filter-btn", 3, "click"], [1, "fas", "fa-times"], [1, "run-header", 3, "click", "title"], [1, "run-header-content"], ["class", "run-tags-header", 4, "ngIf"], [1, "run-date"], [1, "run-pass-rate", 3, "ngClass"], [1, "run-tags-header"], ["class", "tag-chip-header", 4, "ngFor", "ngForOf"], ["class", "tag-more-header", 4, "ngIf"], [1, "tag-chip-header"], [1, "tag-more-header"], [3, "click"], [1, "seq-cell"], [1, "test-name-cell"], [1, "test-name", 3, "title"], ["class", "result-cell", 3, "ngClass", "clickable", "cell-not-run", "title", "click", 4, "ngFor", "ngForOf"], [1, "result-cell", 3, "click", "ngClass", "title"], ["class", "cell-not-run-indicator", 4, "ngIf"], [1, "cell-eval-stack"], ["class", "cell-status", 3, "ngClass", "cell-skipped-status", "title", 4, "ngIf"], ["class", "cell-human no-feedback", "title", "Human Review: No rating submitted yet", 4, "ngIf"], ["class", "cell-human has-feedback", 3, "rating-low", "rating-medium", "rating-good", "rating-excellent", "title", 4, "ngIf"], ["class", "cell-auto has-score", 3, "score-low", "score-medium", "score-good", "score-excellent", "title", 4, "ngIf"], ["class", "cell-auto no-score", "title", "Auto Score: No automated score available", 4, "ngIf"], [1, "cell-status", 3, "ngClass", "title"], ["title", "Human Review: No rating submitted yet", 1, "cell-human", "no-feedback"], [1, "fas", "fa-user-slash"], [1, "cell-human", "has-feedback", 3, "title"], [1, "rating-value"], [1, "cell-auto", "has-score", 3, "title"], [1, "score-value"], ["title", "Auto Score: No automated score available", 1, "cell-auto", "no-score"], [1, "cell-not-run-indicator"], [1, "fas", "fa-minus"], [1, "result-cell", "totals-cell"], [1, "cell-eval-stack", "totals-stack"], ["class", "totals-status", 4, "ngIf"], ["class", "totals-human", 4, "ngIf"], ["class", "totals-auto", 4, "ngIf"], [1, "totals-status"], [1, "pass-count"], [1, "totals-human"], ["class", "avg-label", 4, "ngIf"], [1, "count-label"], [1, "avg-label"], [1, "totals-auto"], ["class", "chart-section", 4, "ngIf"], ["text", "Loading chart data..."], [1, "chart-section"], [1, "chart-header"], [1, "chart-legend"], [1, "legend-item", "chart-passed"], [1, "legend-item", "chart-failed"], [1, "legend-item", "chart-error"], [1, "fas", "fa-exclamation"], [1, "legend-item", "chart-skipped"], [1, "fas", "fa-forward"], [1, "chart-container"], [1, "d3-chart"], [1, "chart-info"], [1, "compare-tab"], [1, "compare-selection"], [1, "compare-run-selector"], ["class", "run-selector-list", 4, "ngIf"], ["class", "selected-run-preview", 4, "ngIf"], [1, "compare-vs"], [1, "fas", "fa-exchange-alt"], ["class", "compare-results", 4, "ngIf"], ["class", "compare-empty", 4, "ngIf"], [1, "run-selector-list"], ["class", "run-selector-item", 3, "selected", "click", 4, "ngFor", "ngForOf"], [1, "run-selector-item", 3, "click"], [1, "selector-status"], [1, "selector-content"], [1, "selector-date"], [1, "selector-rate"], ["class", "selector-tags", 4, "ngIf"], [1, "selector-tags"], ["class", "tag-mini", 4, "ngFor", "ngForOf"], [1, "tag-mini"], [1, "selected-run-preview"], [1, "preview-header"], [1, "preview-label"], [1, "clear-btn", 3, "click"], [1, "preview-details"], [1, "preview-rate"], ["class", "run-selector-item", 3, "selected", "disabled", "click", 4, "ngFor", "ngForOf"], [1, "compare-results"], [1, "compare-summary"], [1, "compare-summary-card"], [1, "summary-label"], [1, "summary-value", 3, "ngClass"], [1, "compare-summary-card", "improved"], [1, "summary-value"], [1, "compare-summary-card", "regressed"], [1, "compare-table-section"], [1, "fas", "fa-list"], [1, "compare-table-wrapper"], [1, "compare-table"], [3, "ngClass", 4, "ngFor", "ngForOf"], [3, "ngClass"], ["class", "status-chip", 3, "ngClass", 4, "ngIf"], ["class", "status-chip status-missing", 4, "ngIf"], [3, "ngClass", 4, "ngIf"], ["class", "muted", 4, "ngIf"], ["class", "change-indicator improved", 4, "ngIf"], ["class", "change-indicator regressed", 4, "ngIf"], ["class", "change-indicator unchanged", 4, "ngIf"], [1, "status-chip", "status-missing"], [1, "muted"], [1, "change-indicator", "improved"], [1, "fas", "fa-arrow-up"], [1, "change-indicator", "regressed"], [1, "fas", "fa-arrow-down"], [1, "change-indicator", "unchanged"], ["text", "Loading comparison data..."], [1, "compare-empty"], [1, "compare-empty-icon"], [1, "keyboard-shortcuts"], [1, "shortcuts-header"], ["title", "Hide shortcuts", 1, "shortcuts-close", 3, "click"], [1, "shortcut-list"], [1, "shortcut-item"], [1, "shortcut-keys"]], template: function TestSuiteFormComponentExtended_Template(rf, ctx) { if (rf & 1) {
|
|
3328
|
+
i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "div", 3)(3, "div", 4)(4, "div", 5);
|
|
3329
|
+
i0.ɵɵelement(5, "i", 6);
|
|
287
3330
|
i0.ɵɵelementEnd();
|
|
288
|
-
i0.ɵɵelementStart(6, "div",
|
|
3331
|
+
i0.ɵɵelementStart(6, "div", 7)(7, "h1");
|
|
289
3332
|
i0.ɵɵtext(8);
|
|
290
3333
|
i0.ɵɵelementEnd();
|
|
291
|
-
i0.ɵɵelementStart(9, "div",
|
|
292
|
-
i0.ɵɵ
|
|
293
|
-
i0.ɵɵ
|
|
294
|
-
i0.ɵɵ
|
|
295
|
-
i0.ɵɵ
|
|
296
|
-
i0.ɵɵtext(14, "Run Suite");
|
|
3334
|
+
i0.ɵɵelementStart(9, "div", 8)(10, "span", 9);
|
|
3335
|
+
i0.ɵɵelement(11, "i", 10);
|
|
3336
|
+
i0.ɵɵtext(12);
|
|
3337
|
+
i0.ɵɵelementEnd();
|
|
3338
|
+
i0.ɵɵtemplate(13, TestSuiteFormComponentExtended_span_13_Template, 3, 1, "span", 11);
|
|
297
3339
|
i0.ɵɵelementEnd()()();
|
|
298
|
-
i0.ɵɵ
|
|
3340
|
+
i0.ɵɵelementStart(14, "div", 12);
|
|
3341
|
+
i0.ɵɵelement(15, "app-evaluation-mode-toggle");
|
|
3342
|
+
i0.ɵɵelementStart(16, "button", 13);
|
|
3343
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_16_listener() { return ctx.exportToExcel(); });
|
|
3344
|
+
i0.ɵɵelement(17, "i", 14);
|
|
3345
|
+
i0.ɵɵtext(18, " Export ");
|
|
299
3346
|
i0.ɵɵelementEnd();
|
|
300
|
-
i0.ɵɵelementStart(
|
|
301
|
-
i0.ɵɵlistener("click", function
|
|
302
|
-
i0.ɵɵelement(
|
|
303
|
-
i0.ɵɵtext(
|
|
3347
|
+
i0.ɵɵelementStart(19, "button", 15);
|
|
3348
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_19_listener() { return ctx.runSuite(); });
|
|
3349
|
+
i0.ɵɵelement(20, "i", 16);
|
|
3350
|
+
i0.ɵɵtext(21, " Run Suite ");
|
|
304
3351
|
i0.ɵɵelementEnd();
|
|
305
|
-
i0.ɵɵelementStart(
|
|
306
|
-
i0.ɵɵlistener("click", function
|
|
307
|
-
i0.ɵɵelement(
|
|
308
|
-
i0.ɵɵtext(
|
|
309
|
-
i0.ɵɵ
|
|
3352
|
+
i0.ɵɵelementStart(22, "button", 17);
|
|
3353
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_22_listener() { return ctx.refresh(); });
|
|
3354
|
+
i0.ɵɵelement(23, "i", 10);
|
|
3355
|
+
i0.ɵɵtext(24);
|
|
3356
|
+
i0.ɵɵelementEnd()()();
|
|
3357
|
+
i0.ɵɵtemplate(25, TestSuiteFormComponentExtended_div_25_Template, 3, 1, "div", 18);
|
|
3358
|
+
i0.ɵɵelementEnd();
|
|
3359
|
+
i0.ɵɵelementStart(26, "div", 19)(27, "div", 20)(28, "button", 21);
|
|
3360
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_28_listener() { return ctx.changeTab("overview"); });
|
|
3361
|
+
i0.ɵɵelement(29, "i", 22);
|
|
3362
|
+
i0.ɵɵtext(30, " Overview ");
|
|
3363
|
+
i0.ɵɵelementEnd();
|
|
3364
|
+
i0.ɵɵelementStart(31, "button", 21);
|
|
3365
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_31_listener() { return ctx.changeTab("tests"); });
|
|
3366
|
+
i0.ɵɵelement(32, "i", 23);
|
|
3367
|
+
i0.ɵɵtext(33, " Tests ");
|
|
3368
|
+
i0.ɵɵtemplate(34, TestSuiteFormComponentExtended_span_34_Template, 2, 1, "span", 24);
|
|
3369
|
+
i0.ɵɵelementEnd();
|
|
3370
|
+
i0.ɵɵelementStart(35, "button", 21);
|
|
3371
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_35_listener() { return ctx.changeTab("runs"); });
|
|
3372
|
+
i0.ɵɵelement(36, "i", 25);
|
|
3373
|
+
i0.ɵɵtext(37, " Runs ");
|
|
3374
|
+
i0.ɵɵtemplate(38, TestSuiteFormComponentExtended_span_38_Template, 2, 1, "span", 24);
|
|
310
3375
|
i0.ɵɵelementEnd();
|
|
311
|
-
i0.ɵɵelementStart(
|
|
312
|
-
i0.ɵɵlistener("click", function
|
|
313
|
-
i0.ɵɵelement(
|
|
314
|
-
i0.ɵɵtext(
|
|
315
|
-
i0.ɵɵ
|
|
3376
|
+
i0.ɵɵelementStart(39, "button", 21);
|
|
3377
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_39_listener() { return ctx.changeTab("analytics"); });
|
|
3378
|
+
i0.ɵɵelement(40, "i", 26);
|
|
3379
|
+
i0.ɵɵtext(41, " Analytics ");
|
|
3380
|
+
i0.ɵɵelementEnd();
|
|
3381
|
+
i0.ɵɵelementStart(42, "button", 21);
|
|
3382
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_42_listener() { return ctx.changeTab("compare"); });
|
|
3383
|
+
i0.ɵɵelement(43, "i", 27);
|
|
3384
|
+
i0.ɵɵtext(44, " Compare ");
|
|
316
3385
|
i0.ɵɵelementEnd()()();
|
|
317
|
-
i0.ɵɵelementStart(
|
|
318
|
-
i0.ɵɵtemplate(
|
|
319
|
-
i0.ɵɵelementEnd()
|
|
3386
|
+
i0.ɵɵelementStart(45, "div", 28);
|
|
3387
|
+
i0.ɵɵtemplate(46, TestSuiteFormComponentExtended_div_46_Template, 45, 13, "div", 29)(47, TestSuiteFormComponentExtended_div_47_Template, 4, 3, "div", 30)(48, TestSuiteFormComponentExtended_div_48_Template, 4, 3, "div", 31)(49, TestSuiteFormComponentExtended_div_49_Template, 4, 3, "div", 32)(50, TestSuiteFormComponentExtended_div_50_Template, 18, 8, "div", 33);
|
|
3388
|
+
i0.ɵɵelementEnd();
|
|
3389
|
+
i0.ɵɵelementStart(51, "button", 34);
|
|
3390
|
+
i0.ɵɵlistener("click", function TestSuiteFormComponentExtended_Template_button_click_51_listener() { return ctx.toggleShortcuts(); });
|
|
3391
|
+
i0.ɵɵelement(52, "i", 35);
|
|
3392
|
+
i0.ɵɵelementEnd();
|
|
3393
|
+
i0.ɵɵtemplate(53, TestSuiteFormComponentExtended_div_53_Template, 32, 0, "div", 36);
|
|
3394
|
+
i0.ɵɵelementEnd();
|
|
320
3395
|
} if (rf & 2) {
|
|
321
3396
|
i0.ɵɵadvance(4);
|
|
322
3397
|
i0.ɵɵstyleProp("background-color", ctx.getStatusColor());
|
|
323
3398
|
i0.ɵɵadvance(4);
|
|
324
3399
|
i0.ɵɵtextInterpolate(ctx.record.Name);
|
|
325
3400
|
i0.ɵɵadvance(2);
|
|
326
|
-
i0.ɵɵ
|
|
3401
|
+
i0.ɵɵproperty("ngClass", ctx.getStatusClass());
|
|
3402
|
+
i0.ɵɵadvance();
|
|
3403
|
+
i0.ɵɵproperty("ngClass", ctx.record.Status === "Active" ? "fa-circle-check" : "fa-circle-pause");
|
|
327
3404
|
i0.ɵɵadvance();
|
|
328
3405
|
i0.ɵɵtextInterpolate1(" ", ctx.record.Status, " ");
|
|
329
|
-
i0.ɵɵadvance(
|
|
3406
|
+
i0.ɵɵadvance();
|
|
3407
|
+
i0.ɵɵproperty("ngIf", ctx.testsLoaded);
|
|
3408
|
+
i0.ɵɵadvance(9);
|
|
3409
|
+
i0.ɵɵproperty("disabled", ctx.isRefreshing);
|
|
3410
|
+
i0.ɵɵadvance();
|
|
3411
|
+
i0.ɵɵproperty("ngClass", ctx.isRefreshing ? "fa-sync fa-spin" : "fa-sync");
|
|
3412
|
+
i0.ɵɵadvance();
|
|
3413
|
+
i0.ɵɵtextInterpolate1(" ", ctx.isRefreshing ? "Refreshing..." : "Refresh", " ");
|
|
3414
|
+
i0.ɵɵadvance();
|
|
330
3415
|
i0.ɵɵproperty("ngIf", ctx.record.Description);
|
|
331
3416
|
i0.ɵɵadvance(3);
|
|
332
3417
|
i0.ɵɵclassProp("active", ctx.activeTab === "overview");
|
|
3418
|
+
i0.ɵɵattribute("aria-selected", ctx.activeTab === "overview");
|
|
333
3419
|
i0.ɵɵadvance(3);
|
|
334
3420
|
i0.ɵɵclassProp("active", ctx.activeTab === "tests");
|
|
3421
|
+
i0.ɵɵattribute("aria-selected", ctx.activeTab === "tests");
|
|
335
3422
|
i0.ɵɵadvance(3);
|
|
336
3423
|
i0.ɵɵproperty("ngIf", ctx.testsLoaded);
|
|
337
3424
|
i0.ɵɵadvance();
|
|
338
3425
|
i0.ɵɵclassProp("active", ctx.activeTab === "runs");
|
|
3426
|
+
i0.ɵɵattribute("aria-selected", ctx.activeTab === "runs");
|
|
339
3427
|
i0.ɵɵadvance(3);
|
|
340
3428
|
i0.ɵɵproperty("ngIf", ctx.runsLoaded);
|
|
341
|
-
i0.ɵɵadvance(
|
|
3429
|
+
i0.ɵɵadvance();
|
|
3430
|
+
i0.ɵɵclassProp("active", ctx.activeTab === "analytics");
|
|
3431
|
+
i0.ɵɵattribute("aria-selected", ctx.activeTab === "analytics");
|
|
3432
|
+
i0.ɵɵadvance(3);
|
|
3433
|
+
i0.ɵɵclassProp("active", ctx.activeTab === "compare");
|
|
3434
|
+
i0.ɵɵattribute("aria-selected", ctx.activeTab === "compare");
|
|
3435
|
+
i0.ɵɵadvance(4);
|
|
342
3436
|
i0.ɵɵproperty("ngIf", ctx.activeTab === "overview");
|
|
343
3437
|
i0.ɵɵadvance();
|
|
344
3438
|
i0.ɵɵproperty("ngIf", ctx.activeTab === "tests");
|
|
345
3439
|
i0.ɵɵadvance();
|
|
346
3440
|
i0.ɵɵproperty("ngIf", ctx.activeTab === "runs");
|
|
347
|
-
|
|
3441
|
+
i0.ɵɵadvance();
|
|
3442
|
+
i0.ɵɵproperty("ngIf", ctx.activeTab === "analytics");
|
|
3443
|
+
i0.ɵɵadvance();
|
|
3444
|
+
i0.ɵɵproperty("ngIf", ctx.activeTab === "compare");
|
|
3445
|
+
i0.ɵɵadvance();
|
|
3446
|
+
i0.ɵɵproperty("title", ctx.showShortcuts ? "Hide keyboard shortcuts" : "Show keyboard shortcuts");
|
|
3447
|
+
i0.ɵɵadvance(2);
|
|
3448
|
+
i0.ɵɵproperty("ngIf", ctx.showShortcuts);
|
|
3449
|
+
} }, dependencies: [i4.NgClass, i4.NgForOf, i4.NgIf, i5.DefaultValueAccessor, i5.NumberValueAccessor, i5.NgControlStatus, i5.NgModel, i6.DialogContainerDirective, i7.ButtonComponent, i3.EvaluationModeToggleComponent, i8.LoadingComponent, i4.DatePipe], styles: ["\n\n\n\n\n\n\n[_nghost-%COMP%] {\n --test-primary: #2563eb;\n --test-primary-light: #3b82f6;\n --test-success: #10b981;\n --test-error: #ef4444;\n --test-warning: #f59e0b;\n --test-disabled: #6b7280;\n --test-bg: #f8fafc;\n --test-surface: #ffffff;\n --test-border: #e2e8f0;\n --test-text: #1e293b;\n --test-text-secondary: #64748b;\n --test-text-muted: #94a3b8;\n --test-radius-sm: 6px;\n --test-radius-md: 10px;\n --test-radius-lg: 16px;\n --test-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);\n --test-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n --test-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n --test-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: block;\n height: 100%;\n}\n\n.test-suite-form[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--test-bg);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n\n\n.suite-header[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n padding: 20px;\n}\n\n.header-content[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n gap: 16px;\n}\n\n.header-left[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex: 1;\n min-width: 0;\n}\n\n.suite-icon[_ngcontent-%COMP%] {\n width: 56px;\n height: 56px;\n border-radius: var(--test-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 24px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-md);\n transition: var(--test-transition);\n}\n\n.suite-icon[_ngcontent-%COMP%]:hover { transform: scale(1.05); }\n\n.suite-info[_ngcontent-%COMP%] { flex: 1; min-width: 0; }\n\n.suite-info[_ngcontent-%COMP%] h1[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: clamp(18px, 4vw, 24px);\n font-weight: 700;\n color: var(--test-text);\n word-wrap: break-word;\n}\n\n.suite-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border-radius: 20px;\n color: white;\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-badge.status-active[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-success) 0%, #059669 100%); }\n.status-badge.status-disabled[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-disabled) 0%, #4b5563 100%); }\n.status-badge.status-pending[_ngcontent-%COMP%] { background: linear-gradient(135deg, var(--test-warning) 0%, #d97706 100%); }\n\n.status-badge-inline[_ngcontent-%COMP%] {\n display: inline-flex;\n padding: 2px 10px;\n border-radius: 10px;\n color: white;\n font-size: 11px;\n font-weight: 600;\n}\n\n.status-badge-inline.status-active[_ngcontent-%COMP%] { background: var(--test-success); }\n.status-badge-inline.status-disabled[_ngcontent-%COMP%] { background: var(--test-disabled); }\n.status-badge-inline.status-pending[_ngcontent-%COMP%] { background: var(--test-warning); }\n\n.test-count[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--test-text-secondary);\n padding: 4px 10px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.suite-description[_ngcontent-%COMP%] {\n padding: 16px;\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.suite-description[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n color: var(--test-text-secondary);\n line-height: 1.6;\n font-size: 14px;\n}\n\n\n\n.tabs-container[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n position: sticky;\n top: 0;\n z-index: 10;\n}\n\n.tabs[_ngcontent-%COMP%] {\n display: flex;\n padding: 0 20px;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n.tabs[_ngcontent-%COMP%]::-webkit-scrollbar { display: none; }\n\n.tab[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 18px;\n border: none;\n background: transparent;\n border-bottom: 3px solid transparent;\n color: var(--test-text-secondary);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n white-space: nowrap;\n}\n\n.tab[_ngcontent-%COMP%]:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.tab.active[_ngcontent-%COMP%] {\n color: var(--test-primary);\n border-bottom-color: var(--test-primary);\n font-weight: 600;\n}\n\n.tab-badge[_ngcontent-%COMP%] {\n background: var(--test-border);\n color: var(--test-text-secondary);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.tab.active[_ngcontent-%COMP%] .tab-badge[_ngcontent-%COMP%] {\n background: rgba(37, 99, 235, 0.15);\n color: var(--test-primary);\n}\n\n.tab-shortcut[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--test-text-muted);\n background: var(--test-bg);\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n}\n\n\n\n.tab-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n\n\n.overview-tab[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 20px;\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.info-section[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.info-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 20px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.info-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%], .config-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.info-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n}\n\n.info-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.info-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.info-value[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 500;\n}\n\n.config-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n}\n\n.config-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.config-item[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.config-input[_ngcontent-%COMP%] {\n padding: 10px 14px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.config-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);\n}\n\n.config-hint[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--test-text-muted);\n}\n\n\n\n.tests-tab[_ngcontent-%COMP%], .runs-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.loading-state[_ngcontent-%COMP%] { padding: 0; }\n\n.skeleton-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skeleton-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.skeleton-sequence[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: var(--test-radius-md);\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-content[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.skeleton-line[_ngcontent-%COMP%] {\n height: 14px;\n border-radius: 4px;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: _ngcontent-%COMP%_shimmer 1.5s infinite;\n}\n\n.skeleton-line.wide[_ngcontent-%COMP%] { width: 70%; }\n.skeleton-line.narrow[_ngcontent-%COMP%] { width: 40%; }\n\n@keyframes _ngcontent-%COMP%_shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n.tests-list[_ngcontent-%COMP%], .runs-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.test-item[_ngcontent-%COMP%]:hover, .run-item[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n border-color: var(--test-primary-light);\n transform: translateX(4px);\n}\n\n.test-sequence[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n font-size: 14px;\n font-weight: 700;\n color: var(--test-text-secondary);\n flex-shrink: 0;\n}\n\n.test-icon[_ngcontent-%COMP%], .run-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--test-radius-md);\n color: white;\n font-size: 18px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-sm);\n}\n\n.test-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, var(--test-primary) 0%, #1d4ed8 100%);\n}\n\n.test-content[_ngcontent-%COMP%], .run-content[_ngcontent-%COMP%] { flex: 1; min-width: 0; }\n\n.test-name[_ngcontent-%COMP%], .run-header[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n margin-bottom: 4px;\n}\n\n.run-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.run-id[_ngcontent-%COMP%] { font-weight: 600; }\n\n.run-status[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.test-status[_ngcontent-%COMP%], .run-meta[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.test-status[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .run-meta[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.test-item[_ngcontent-%COMP%] > i[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.test-item[_ngcontent-%COMP%]:hover > i[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%]:hover > i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n transform: translateX(2px);\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n}\n\n.empty-icon[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin-bottom: 20px;\n}\n\n.empty-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--test-text-muted);\n}\n\n.empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0 0 20px 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 300px;\n}\n\n\n\n\n\n.shortcuts-toggle[_ngcontent-%COMP%] {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n box-shadow: var(--test-shadow-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--test-text-secondary);\n font-size: 14px;\n z-index: 99;\n transition: var(--test-transition);\n opacity: 0.7;\n}\n\n.shortcuts-toggle[_ngcontent-%COMP%]:hover {\n opacity: 1;\n transform: scale(1.1);\n color: var(--test-primary);\n border-color: var(--test-primary);\n}\n\n.keyboard-shortcuts[_ngcontent-%COMP%] {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n padding: 12px 16px;\n box-shadow: var(--test-shadow-lg);\n font-size: 12px;\n z-index: 100;\n max-width: 260px;\n}\n\n.shortcuts-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text);\n}\n\n.shortcuts-close[_ngcontent-%COMP%] {\n margin-left: auto;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--test-text-muted);\n font-size: 12px;\n padding: 2px 4px;\n border-radius: 4px;\n transition: var(--test-transition);\n}\n\n.shortcuts-close[_ngcontent-%COMP%]:hover {\n color: var(--test-text);\n background: var(--test-border);\n}\n\n.shortcut-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.shortcut-item[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: var(--test-text-secondary);\n}\n\n.shortcut-keys[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n}\n\n.shortcut-keys[_ngcontent-%COMP%] kbd[_ngcontent-%COMP%] {\n background: var(--test-bg);\n border: 1px solid var(--test-border);\n border-radius: 4px;\n padding: 2px 6px;\n font-size: 11px;\n color: var(--test-text);\n}\n\n\n\n@media (max-width: 1024px) {\n .keyboard-shortcuts[_ngcontent-%COMP%], .shortcuts-toggle[_ngcontent-%COMP%] { display: none; }\n}\n\n@media (max-width: 768px) {\n .suite-header[_ngcontent-%COMP%] { padding: 16px; }\n\n .header-content[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions[_ngcontent-%COMP%] {\n width: 100%;\n justify-content: stretch;\n }\n\n .header-actions[_ngcontent-%COMP%] button[_ngcontent-%COMP%] { flex: 1; }\n\n .tab-shortcut[_ngcontent-%COMP%] { display: none; }\n\n .info-grid[_ngcontent-%COMP%] { grid-template-columns: 1fr; }\n\n .test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] { padding: 14px; }\n}\n\n@media (max-width: 480px) {\n .suite-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n font-size: 18px;\n }\n\n .suite-info[_ngcontent-%COMP%] h1[_ngcontent-%COMP%] { font-size: 16px; }\n\n .tab-badge[_ngcontent-%COMP%] { display: none; }\n\n .test-sequence[_ngcontent-%COMP%] { display: none; }\n}\n\n@media (hover: none) and (pointer: coarse) {\n .test-item[_ngcontent-%COMP%]:active, .run-item[_ngcontent-%COMP%]:active {\n background: rgba(37, 99, 235, 0.1);\n transform: scale(0.98);\n }\n\n .tab[_ngcontent-%COMP%] { min-height: 48px; }\n .test-item[_ngcontent-%COMP%], .run-item[_ngcontent-%COMP%] { min-height: 64px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *[_ngcontent-%COMP%], *[_ngcontent-%COMP%]::before, *[_ngcontent-%COMP%]::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media print {\n .header-actions[_ngcontent-%COMP%], .tabs-container[_ngcontent-%COMP%], .keyboard-shortcuts[_ngcontent-%COMP%] {\n display: none !important;\n }\n}\n\n\n\n\n\n.run-tags[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.tag-chip[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n border: 1px solid #bfdbfe;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 500;\n color: #1d4ed8;\n}\n\n.tag-mini[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n background: #f1f5f9;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n color: #64748b;\n}\n\n.tag-more[_ngcontent-%COMP%] {\n font-size: 10px;\n color: #94a3b8;\n font-weight: 500;\n}\n\n\n\n.run-eval-metrics[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n\n.eval-metric[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 11px;\n font-weight: 500;\n}\n\n.eval-metric.status[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.eval-metric.status.status-completed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.eval-metric.status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.eval-metric.status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.eval-metric.status.status-pending[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.eval-metric.human[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n color: #92400e;\n}\n\n.eval-metric.human[_ngcontent-%COMP%] .eval-pending[_ngcontent-%COMP%] {\n font-size: 9px;\n color: #d97706;\n}\n\n.eval-metric.auto[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n}\n\n.eval-metric.auto.high[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);\n border: 1px solid #86efac;\n color: #166534;\n}\n\n.eval-metric.auto.medium[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #fcd34d;\n color: #92400e;\n}\n\n.eval-metric.auto.low[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);\n border: 1px solid #fca5a5;\n color: #991b1b;\n}\n\n.tag-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.tag-chip-table[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n\n\n.analytics-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n\n\n.analytics-filters[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-bottom: 16px;\n box-shadow: var(--test-shadow-sm);\n overflow: hidden;\n}\n\n.analytics-filters.collapsed[_ngcontent-%COMP%] {\n margin-bottom: 12px;\n}\n\n.filters-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n transition: background 0.15s ease;\n}\n\n.filters-header[_ngcontent-%COMP%]:hover {\n background: var(--test-bg);\n}\n\n.filters-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--test-text-secondary);\n}\n\n.filters-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.filter-summary[_ngcontent-%COMP%] {\n font-weight: 400;\n color: var(--test-text-muted);\n margin-left: 8px;\n}\n\n.filters-header[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n font-size: 12px;\n}\n\n.filters-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 16px 16px 16px;\n border-top: 1px solid var(--test-border);\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.filter-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.filter-buttons[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.filter-btn[_ngcontent-%COMP%] {\n padding: 8px 16px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.filter-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--test-primary);\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.filter-btn.active[_ngcontent-%COMP%] {\n background: var(--test-primary);\n border-color: var(--test-primary);\n color: white;\n}\n\n.filter-btn.tag-btn[_ngcontent-%COMP%] {\n padding: 6px 12px;\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.filter-btn.tag-btn[_ngcontent-%COMP%] i.fa-check[_ngcontent-%COMP%] {\n font-size: 10px;\n}\n\n.filter-btn.tag-btn.all-tags-btn[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.filter-btn.tag-btn.all-tags-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.filter-hint[_ngcontent-%COMP%] {\n font-weight: 400;\n font-size: 10px;\n color: var(--test-primary);\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-filters[_ngcontent-%COMP%] {\n max-height: 120px;\n overflow-y: auto;\n}\n\n\n\n.analytics-kpis[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n transition: var(--test-transition);\n}\n\n.kpi-card[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: var(--test-shadow-md);\n}\n\n.kpi-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #eff6ff;\n border-radius: var(--test-radius-md);\n color: var(--test-primary);\n font-size: 20px;\n}\n\n.kpi-icon.success[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-icon.info[_ngcontent-%COMP%] {\n background: #f0f9ff;\n color: #0ea5e9;\n}\n\n.kpi-icon.warning[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.kpi-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n line-height: 1.2;\n}\n\n.kpi-label[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--test-text-secondary);\n margin-top: 2px;\n}\n\n.kpi-trend[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n margin-top: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n background: #f1f5f9;\n color: var(--test-text-secondary);\n}\n\n.kpi-trend.trend-up[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-trend.trend-down[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n\n\n.analytics-table-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.analytics-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.analytics-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.analytics-table-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n margin: 0 -24px;\n padding: 0 24px;\n}\n\n.analytics-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.analytics-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.analytics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.clickable-row[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.analytics-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.clickable-row[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.status-chip[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-chip.status-completed[_ngcontent-%COMP%] { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-passed[_ngcontent-%COMP%] { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-failed[_ngcontent-%COMP%] { background: #fef2f2; color: var(--test-error); }\n.status-chip.status-error[_ngcontent-%COMP%] { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-running[_ngcontent-%COMP%] { background: #eff6ff; color: var(--test-primary); }\n.status-chip.status-pending[_ngcontent-%COMP%] { background: #f5f3ff; color: #8b5cf6; }\n.status-chip.status-cancelled[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-disabled); }\n.status-chip.status-missing[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-text-muted); }\n.status-chip.status-timeout[_ngcontent-%COMP%] { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-skipped[_ngcontent-%COMP%] { background: #f1f5f9; color: var(--test-disabled); }\n\n.pass-rate-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 100px;\n}\n\n.pass-rate-bar[_ngcontent-%COMP%] {\n height: 6px;\n border-radius: 3px;\n background: var(--test-success);\n transition: width 0.3s ease;\n max-width: 60px;\n}\n\n.pass-rate-bar.medium[_ngcontent-%COMP%] { background: var(--test-warning); }\n.pass-rate-bar.low[_ngcontent-%COMP%] { background: var(--test-error); }\n\n\n\n.analytics-subnav[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 8px;\n margin-bottom: 20px;\n box-shadow: var(--test-shadow-sm);\n display: inline-flex;\n}\n\n.subnav-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n background: var(--test-bg);\n border-radius: var(--test-radius-md);\n padding: 4px;\n}\n\n.subnav-tab[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--test-radius-sm);\n background: transparent;\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.subnav-tab[_ngcontent-%COMP%]:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.subnav-tab.active[_ngcontent-%COMP%] {\n background: var(--test-surface);\n color: var(--test-primary);\n box-shadow: var(--test-shadow-sm);\n}\n\n.subnav-tab[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.matrix-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 16px;\n box-shadow: var(--test-shadow-sm);\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 280px);\n min-height: 300px;\n}\n\n.matrix-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--test-border);\n flex-shrink: 0;\n}\n\n.matrix-header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.matrix-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.matrix-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.matrix-run-count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--test-text-muted);\n font-weight: 500;\n}\n\n\n\n.matrix-filter-input[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n background: #f8fafc;\n border: 1px solid var(--test-border);\n border-radius: 6px;\n padding: 4px 10px;\n min-width: 180px;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] i.fa-search[_ngcontent-%COMP%] {\n color: #94a3b8;\n font-size: 12px;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%] {\n border: none;\n background: transparent;\n outline: none;\n font-size: 12px;\n color: var(--test-text);\n width: 100%;\n}\n\n.matrix-filter-input[_ngcontent-%COMP%] .filter-input[_ngcontent-%COMP%]::placeholder {\n color: #94a3b8;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n border: none;\n background: none;\n padding: 2px 4px;\n cursor: pointer;\n color: #94a3b8;\n font-size: 10px;\n border-radius: 3px;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: #e2e8f0;\n color: #64748b;\n}\n\n\n\n.matrix-scroll-container[_ngcontent-%COMP%] {\n flex: 1;\n overflow: auto;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n}\n\n.test-matrix[_ngcontent-%COMP%] {\n display: table;\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.test-matrix[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] {\n display: table-header-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] thead[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] {\n display: table-row;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] {\n display: table-row-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%] {\n display: table-row;\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr[_ngcontent-%COMP%]:hover {\n background-color: rgba(59, 130, 246, 0.05);\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n border-top: 1px solid rgba(59, 130, 246, 0.3);\n border-bottom: 1px solid rgba(59, 130, 246, 0.3);\n}\n\n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.row-selected[_ngcontent-%COMP%] .test-name-cell[_ngcontent-%COMP%] {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix[_ngcontent-%COMP%] tfoot[_ngcontent-%COMP%] {\n display: table-footer-group;\n}\n\n.test-matrix[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n display: table-cell;\n border: 1px solid var(--test-border);\n padding: 8px 12px;\n text-align: center;\n vertical-align: middle;\n}\n\n.test-matrix[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n background: var(--test-bg);\n font-weight: 600;\n font-size: 11px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 3px solid #475569 !important;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%] {\n width: 36px;\n min-width: 36px;\n max-width: 36px;\n text-align: center;\n position: sticky;\n left: 0;\n font-size: 11px;\n color: #64748b;\n border-right: 1px solid var(--test-border);\n padding: 6px 4px !important;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%] {\n cursor: pointer;\n font-weight: 600;\n background: var(--test-bg);\n z-index: 12; \n\n top: 0;\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .seq-cell[_ngcontent-%COMP%] {\n background: var(--test-surface);\n z-index: 2;\n}\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n margin-left: 2px;\n opacity: 0.6;\n}\n\n.test-matrix[_ngcontent-%COMP%] .seq-header[_ngcontent-%COMP%]:hover {\n background: #f1f5f9;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%] {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-bg);\n z-index: 11;\n border-right: 2px solid var(--test-border);\n cursor: pointer;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n margin-left: 4px;\n opacity: 0.6;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-header[_ngcontent-%COMP%]:hover {\n background: #f1f5f9;\n}\n\n.test-matrix[_ngcontent-%COMP%] .run-header[_ngcontent-%COMP%] {\n min-width: 120px;\n width: 120px;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n\n\n.test-matrix[_ngcontent-%COMP%] .spacer-header[_ngcontent-%COMP%], \n.test-matrix[_ngcontent-%COMP%] .spacer-cell[_ngcontent-%COMP%] {\n width: 100%;\n min-width: 20px;\n background: var(--test-bg);\n border: none;\n}\n\n.test-matrix[_ngcontent-%COMP%] .spacer-cell[_ngcontent-%COMP%] {\n background: var(--test-surface);\n}\n\n.test-matrix[_ngcontent-%COMP%] .run-header[_ngcontent-%COMP%]:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-header-content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.run-date[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.run-pass-rate[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.run-pass-rate.high[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.run-pass-rate.medium[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.run-pass-rate.low[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.run-tags[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n}\n\n.tag-tiny[_ngcontent-%COMP%] {\n font-size: 9px;\n padding: 2px 6px;\n background: #eff6ff;\n color: var(--test-primary);\n border-radius: 8px;\n white-space: nowrap;\n max-width: 60px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n.run-tags-header[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n justify-content: center;\n margin-bottom: 4px;\n}\n\n.tag-chip-header[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 70px;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-more-header[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--test-text-secondary);\n padding: 2px 4px;\n}\n\n.test-matrix[_ngcontent-%COMP%] .test-name-cell[_ngcontent-%COMP%] {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-surface);\n z-index: 2;\n border-right: 2px solid var(--test-border);\n padding: 6px 10px !important;\n}\n\n.test-name[_ngcontent-%COMP%] {\n display: block;\n white-space: nowrap;\n font-weight: 500;\n color: var(--test-text);\n font-size: 12px;\n}\n\n.result-cell[_ngcontent-%COMP%] {\n position: relative;\n min-width: 120px;\n width: 120px;\n transition: var(--test-transition);\n}\n\n.result-cell.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.result-cell.clickable[_ngcontent-%COMP%]:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 5;\n}\n\n.result-cell[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.result-cell.cell-passed[_ngcontent-%COMP%], \n.result-cell.cell-failed[_ngcontent-%COMP%], \n.result-cell.cell-error[_ngcontent-%COMP%], \n.result-cell.cell-timeout[_ngcontent-%COMP%], \n.result-cell.cell-running[_ngcontent-%COMP%], \n.result-cell.cell-pending[_ngcontent-%COMP%] {\n background: #ffffff;\n color: var(--test-text);\n}\n\n\n\n.result-cell.cell-skipped[_ngcontent-%COMP%] {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: var(--test-disabled);\n}\n\n.result-cell.cell-none[_ngcontent-%COMP%] {\n background: #f8fafc;\n color: var(--test-text-muted);\n}\n\n.cell-score[_ngcontent-%COMP%] {\n display: block;\n font-size: 10px;\n font-weight: 600;\n margin-top: 2px;\n}\n\n\n\n.cell-eval-stack[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-wrap: nowrap;\n}\n\n.result-cell.multi-eval[_ngcontent-%COMP%] {\n min-width: 120px;\n width: auto;\n}\n\n\n\n.cell-status.status-timeout[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-status.status-error[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-status.status-skipped[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-status.status-pending[_ngcontent-%COMP%] {\n background: #f3e8ff;\n color: #7c3aed;\n}\n\n.cell-human[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: #fef3c7;\n color: #d97706;\n font-size: 10px;\n}\n\n.cell-auto[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 600;\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-auto.high[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-auto.medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-auto.low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-none-indicator[_ngcontent-%COMP%] {\n color: var(--test-text-muted);\n opacity: 0.5;\n}\n\n.matrix-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.matrix-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n\n\n\n\n.chart-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.chart-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.chart-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.chart-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.chart-legend[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-passed[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-failed[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-error[_ngcontent-%COMP%] {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.chart-legend[_ngcontent-%COMP%] .legend-item.chart-skipped[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: var(--test-disabled);\n}\n\n.chart-container[_ngcontent-%COMP%] {\n min-height: 500px;\n position: relative;\n overflow: hidden;\n background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.d3-chart[_ngcontent-%COMP%] {\n width: 100%;\n height: 500px;\n}\n\n.d3-chart[_ngcontent-%COMP%] svg[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n}\n\n\n\n.d3-chart[_ngcontent-%COMP%] .node[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.d3-chart[_ngcontent-%COMP%] .node[_ngcontent-%COMP%]:hover {\n transform: scale(1.05);\n}\n\n.d3-chart[_ngcontent-%COMP%] .node-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n fill: var(--test-text);\n pointer-events: none;\n}\n\n.d3-chart[_ngcontent-%COMP%] .link[_ngcontent-%COMP%] {\n fill: none;\n stroke-opacity: 0.4;\n transition: stroke-opacity 0.2s ease;\n}\n\n.d3-chart[_ngcontent-%COMP%] .link[_ngcontent-%COMP%]:hover {\n stroke-opacity: 0.8;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip[_ngcontent-%COMP%] {\n position: absolute;\n padding: 10px 14px;\n background: rgba(30, 41, 59, 0.95);\n color: white;\n border-radius: 8px;\n font-size: 12px;\n pointer-events: none;\n z-index: 100;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n max-width: 250px;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.d3-chart[_ngcontent-%COMP%] .tooltip-value[_ngcontent-%COMP%] {\n opacity: 0.8;\n}\n\n.chart-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n margin-top: 16px;\n}\n\n.chart-info[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n\n\n\n\n.compare-tab[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.compare-selection[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.compare-run-selector[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-run-selector[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-selector-list[_ngcontent-%COMP%] {\n max-height: 200px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.run-selector-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.run-selector-item[_ngcontent-%COMP%]:hover {\n border-color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-selector-item.selected[_ngcontent-%COMP%] {\n border-color: var(--test-primary);\n background: #eff6ff;\n}\n\n.run-selector-item.disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.selector-status[_ngcontent-%COMP%] {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.selector-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.selector-date[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--test-text);\n}\n\n.selector-rate[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--test-text-secondary);\n}\n\n.selector-tags[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.selected-run-preview[_ngcontent-%COMP%] {\n padding: 12px;\n background: #f8fafc;\n border-radius: var(--test-radius-sm);\n border: 1px solid var(--test-border);\n}\n\n.preview-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.preview-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n}\n\n.clear-btn[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: var(--test-error);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.clear-btn[_ngcontent-%COMP%]:hover { text-decoration: underline; }\n\n.preview-details[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--test-text);\n}\n\n.preview-rate[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--test-success);\n}\n\n.compare-vs[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--test-bg);\n border-radius: 50%;\n color: var(--test-text-muted);\n font-size: 16px;\n align-self: center;\n margin-top: 60px;\n}\n\n\n\n.compare-results[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-out;\n}\n\n.compare-summary[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n text-align: center;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n margin-bottom: 8px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n\n.compare-summary-card[_ngcontent-%COMP%] .summary-value.positive[_ngcontent-%COMP%] { color: var(--test-success); }\n.compare-summary-card[_ngcontent-%COMP%] .summary-value.negative[_ngcontent-%COMP%] { color: var(--test-error); }\n\n.compare-summary-card.improved[_ngcontent-%COMP%] {\n border-left: 4px solid var(--test-success);\n}\n\n.compare-summary-card.improved[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] { color: var(--test-success); }\n\n.compare-summary-card.regressed[_ngcontent-%COMP%] {\n border-left: 4px solid var(--test-error);\n}\n\n.compare-summary-card.regressed[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] { color: var(--test-error); }\n\n\n\n.compare-table-section[_ngcontent-%COMP%] {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.compare-table-section[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--test-primary);\n}\n\n.compare-table-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n}\n\n.compare-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.compare-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%] {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.compare-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.compare-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.improved[_ngcontent-%COMP%] {\n background: rgba(16, 185, 129, 0.05);\n}\n\n.compare-table[_ngcontent-%COMP%] tbody[_ngcontent-%COMP%] tr.regressed[_ngcontent-%COMP%] {\n background: rgba(239, 68, 68, 0.05);\n}\n\n.test-name-cell[_ngcontent-%COMP%] {\n font-weight: 500;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.positive[_ngcontent-%COMP%] { color: var(--test-success); }\n.negative[_ngcontent-%COMP%] { color: var(--test-error); }\n.muted[_ngcontent-%COMP%] { color: var(--test-text-muted); }\n\n.change-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.change-indicator.improved[_ngcontent-%COMP%] {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.change-indicator.regressed[_ngcontent-%COMP%] {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.change-indicator.unchanged[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: var(--test-text-muted);\n}\n\n\n\n.compare-empty[_ngcontent-%COMP%] {\n text-align: center;\n padding: 60px 24px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-top: 24px;\n}\n\n.compare-empty-icon[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin: 0 auto 20px;\n}\n\n.compare-empty-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n color: var(--test-text-muted);\n}\n\n.compare-empty[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.compare-empty[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 400px;\n margin: 0 auto;\n}\n\n\n\n.empty-state.small[_ngcontent-%COMP%] {\n padding: 32px 16px;\n}\n\n.empty-state.small[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n\n\n\n\n@media (max-width: 1024px) {\n .compare-selection[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n\n .compare-vs[_ngcontent-%COMP%] {\n margin: 0;\n align-self: center;\n justify-self: center;\n }\n\n .analytics-kpis[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 768px) {\n .filter-buttons[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .filter-btn[_ngcontent-%COMP%] {\n width: 100%;\n text-align: center;\n }\n\n .analytics-kpis[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .kpi-card[_ngcontent-%COMP%] {\n padding: 16px;\n }\n\n .kpi-value[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .compare-summary[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .analytics-table-wrapper[_ngcontent-%COMP%], \n .compare-table-wrapper[_ngcontent-%COMP%] {\n margin: 0 -20px;\n padding: 0 20px;\n }\n\n .run-selector-list[_ngcontent-%COMP%] {\n max-height: 150px;\n }\n}\n\n@media (max-width: 480px) {\n .compare-summary[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .compare-summary-card[_ngcontent-%COMP%] .summary-value[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .analytics-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .analytics-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%], \n .compare-table[_ngcontent-%COMP%] th[_ngcontent-%COMP%], \n .compare-table[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 12px;\n font-size: 12px;\n }\n}\n\n\n\n\n\n\n\n\n.cell-human[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-human.no-feedback[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-human.no-feedback[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n}\n\n.cell-human.has-feedback[_ngcontent-%COMP%] .rating-value[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 11px;\n}\n\n\n\n.cell-human.rating-low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-human.rating-medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-human.rating-good[_ngcontent-%COMP%] {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-human.rating-excellent[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n\n\n.cell-auto[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-auto.no-score[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-auto.no-score[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 9px;\n}\n\n.cell-auto.has-score[_ngcontent-%COMP%] .score-value[_ngcontent-%COMP%] {\n font-weight: 700;\n font-size: 10px;\n}\n\n\n\n.cell-auto.score-low[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-auto.score-medium[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-auto.score-good[_ngcontent-%COMP%] {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-auto.score-excellent[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n\n\n.cell-status[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed[_ngcontent-%COMP%] {\n background: #dcfce7;\n color: #16a34a;\n}\n\n.cell-status.status-failed[_ngcontent-%COMP%] {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-status.status-error[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-timeout[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-skipped[_ngcontent-%COMP%], \n.cell-status.status-pending[_ngcontent-%COMP%] {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #2563eb;\n}\n\n\n\n.result-cell.cell-not-run[_ngcontent-%COMP%] {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: #94a3b8;\n}\n\n.result-cell.cell-not-run[_ngcontent-%COMP%] .cell-eval-stack[_ngcontent-%COMP%] {\n opacity: 0.6;\n}\n\n.cell-not-run-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n color: #94a3b8;\n font-size: 11px;\n}\n\n\n\n\n\n.test-matrix[_ngcontent-%COMP%] tfoot[_ngcontent-%COMP%] {\n position: sticky;\n bottom: 0;\n z-index: 2;\n}\n\n.totals-row[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n border-top: 2px solid var(--test-border);\n}\n\n.totals-row[_ngcontent-%COMP%] td[_ngcontent-%COMP%] {\n padding: 10px 12px;\n font-weight: 600;\n}\n\n.totals-row[_ngcontent-%COMP%] .totals-label[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n font-size: 12px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.totals-row[_ngcontent-%COMP%] .totals-cell[_ngcontent-%COMP%] {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n}\n\n.totals-stack[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.totals-status[_ngcontent-%COMP%], \n.totals-human[_ngcontent-%COMP%], \n.totals-auto[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 10px;\n white-space: nowrap;\n}\n\n.totals-status[_ngcontent-%COMP%] {\n background: #e0f2fe;\n color: #0369a1;\n}\n\n.totals-status[_ngcontent-%COMP%] .pass-count[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-human[_ngcontent-%COMP%] {\n background: #fef3c7;\n color: #92400e;\n}\n\n.totals-human[_ngcontent-%COMP%] .avg-label[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-human[_ngcontent-%COMP%] .count-label[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.8;\n}\n\n.totals-auto[_ngcontent-%COMP%] {\n background: #dbeafe;\n color: #1e40af;\n}\n\n.totals-auto[_ngcontent-%COMP%] .avg-label[_ngcontent-%COMP%] {\n font-weight: 700;\n}\n\n.totals-auto[_ngcontent-%COMP%] .count-label[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.8;\n}"], changeDetection: 0 }); }
|
|
348
3450
|
};
|
|
349
3451
|
TestSuiteFormComponentExtended = __decorate([
|
|
350
3452
|
RegisterClass(BaseFormComponent, 'MJ: Test Suites')
|
|
@@ -352,9 +3454,15 @@ TestSuiteFormComponentExtended = __decorate([
|
|
|
352
3454
|
export { TestSuiteFormComponentExtended };
|
|
353
3455
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestSuiteFormComponentExtended, [{
|
|
354
3456
|
type: Component,
|
|
355
|
-
args: [{ selector: 'mj-test-suite-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"test-suite-form\" kendoDialogContainer>\n <div class=\"suite-header\">\n <div class=\"header-content\">\n <div class=\"header-left\">\n <div class=\"suite-icon\" [style.background-color]=\"getStatusColor()\">\n <i class=\"fas fa-layer-group\"></i>\n </div>\n <div class=\"suite-info\">\n <h1>{{ record.Name }}</h1>\n <div class=\"suite-meta\">\n <span class=\"status-badge\" [style.background-color]=\"getStatusColor()\">\n {{ record.Status }}\n </span>\n </div>\n </div>\n </div>\n <div class=\"header-actions\">\n <button kendoButton (click)=\"runSuite()\" icon=\"play\">Run Suite</button>\n </div>\n </div>\n <div class=\"suite-description\" *ngIf=\"record.Description\">\n <p>{{ record.Description }}</p>\n </div>\n </div>\n\n <div class=\"tabs-container\">\n <div class=\"tabs\">\n <button class=\"tab\" [class.active]=\"activeTab === 'overview'\" (click)=\"changeTab('overview')\">\n <i class=\"fas fa-th-large\"></i> Overview\n </button>\n <button class=\"tab\" [class.active]=\"activeTab === 'tests'\" (click)=\"changeTab('tests')\">\n <i class=\"fas fa-flask\"></i> Tests\n <span class=\"tab-badge\" *ngIf=\"testsLoaded\">{{ suiteTests.length }}</span>\n </button>\n <button class=\"tab\" [class.active]=\"activeTab === 'runs'\" (click)=\"changeTab('runs')\">\n <i class=\"fas fa-history\"></i> Runs\n <span class=\"tab-badge\" *ngIf=\"runsLoaded\">{{ suiteRuns.length }}</span>\n </button>\n </div>\n </div>\n\n <div class=\"tab-content\">\n <div class=\"overview-tab\" *ngIf=\"activeTab === 'overview'\">\n <div class=\"info-section\">\n <h3>Suite Information</h3>\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <div class=\"info-label\">Name</div>\n <div class=\"info-value\">{{ record.Name }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Status</div>\n <div class=\"info-value\">{{ record.Status }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Created</div>\n <div class=\"info-value\">{{ record.__mj_CreatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Updated</div>\n <div class=\"info-value\">{{ record.__mj_UpdatedAt | date:'medium' }}</div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"tests-tab\" *ngIf=\"activeTab === 'tests'\">\n <div class=\"tests-list\" *ngIf=\"suiteTests.length > 0\">\n <div class=\"test-item\" *ngFor=\"let test of suiteTests\" (click)=\"openTest(test.TestID)\">\n <div class=\"test-sequence\">{{ test.Sequence }}</div>\n <div class=\"test-icon\"><i class=\"fas fa-flask\"></i></div>\n <div class=\"test-content\">\n <div class=\"test-name\">{{ test.Test }}</div>\n <div class=\"test-status\">{{ test.Status }}</div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n <div class=\"no-data\" *ngIf=\"testsLoaded && suiteTests.length === 0\">\n <i class=\"fas fa-inbox\"></i>\n <p>No tests in this suite</p>\n </div>\n </div>\n\n <div class=\"runs-tab\" *ngIf=\"activeTab === 'runs'\">\n <div class=\"runs-list\" *ngIf=\"suiteRuns.length > 0\">\n <div class=\"run-item\" *ngFor=\"let run of suiteRuns\" (click)=\"openSuiteRun(run.ID)\">\n <div class=\"run-icon\" [style.background-color]=\"run.Status === 'Completed' ? '#4caf50' : run.Status === 'Failed' ? '#f44336' : '#2196f3'\">\n <i class=\"fas fa-play\"></i>\n </div>\n <div class=\"run-content\">\n <div class=\"run-header\">\n <span class=\"run-id\">{{ run.ID.substring(0, 8) }}</span>\n <span class=\"run-status\">{{ run.Status }}</span>\n </div>\n <div class=\"run-meta\">\n <span>{{ run.StartedAt | date:'short' }}</span>\n <span *ngIf=\"run.TotalTests\">{{ run.PassedTests }}/{{ run.TotalTests }} passed</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n <div class=\"no-data\" *ngIf=\"runsLoaded && suiteRuns.length === 0\">\n <i class=\"fas fa-inbox\"></i>\n <p>No runs for this suite</p>\n </div>\n </div>\n </div>\n</div>\n", styles: [".test-suite-form { display: flex; flex-direction: column; height: 100%; background: #f8f9fa; }\n.suite-header { background: white; border-bottom: 1px solid #e0e0e0; padding: 20px; }\n.header-content { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }\n.header-left { display: flex; gap: 16px; }\n.suite-icon { width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 24px; }\n.suite-info h1 { margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #333; }\n.suite-meta { display: flex; align-items: center; gap: 12px; }\n.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 12px; color: white; font-size: 12px; font-weight: 600; }\n.suite-description { padding: 16px; background: #f8f9fa; border-radius: 8px; }\n.suite-description p { margin: 0; color: #666; line-height: 1.5; }\n.tabs-container { background: white; border-bottom: 1px solid #e0e0e0; }\n.tabs { display: flex; padding: 0 20px; overflow-x: auto; }\n.tab { display: flex; align-items: center; gap: 8px; padding: 16px 20px; border: none; background: transparent; border-bottom: 3px solid transparent; color: #666; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; }\n.tab:hover { color: #2196f3; background: rgba(33, 150, 243, 0.05); }\n.tab.active { color: #2196f3; border-bottom-color: #2196f3; }\n.tab-badge { background: #e0e0e0; color: #666; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }\n.tab.active .tab-badge { background: #e3f2fd; color: #2196f3; }\n.tab-content { flex: 1; overflow-y: auto; padding: 20px; }\n.info-section { background: white; border-radius: 12px; padding: 20px; }\n.info-section h3 { margin: 0 0 16px 0; font-size: 18px; font-weight: 600; color: #333; }\n.info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }\n.info-item { display: flex; flex-direction: column; gap: 4px; }\n.info-label { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #999; }\n.info-value { font-size: 14px; color: #333; word-wrap: break-word; }\n.tests-list, .runs-list { display: flex; flex-direction: column; gap: 12px; }\n.test-item, .run-item { display: flex; align-items: center; gap: 12px; padding: 16px; background: white; border: 2px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; }\n.test-item:hover, .run-item:hover { background: #e3f2fd; border-color: #90caf9; }\n.test-sequence { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; background: #f0f0f0; border-radius: 50%; font-size: 14px; font-weight: 700; color: #666; flex-shrink: 0; }\n.test-icon, .run-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 8px; color: white; font-size: 18px; flex-shrink: 0; background: #2196f3; }\n.test-content, .run-content { flex: 1; }\n.test-name, .run-header { font-size: 14px; font-weight: 600; color: #333; margin-bottom: 4px; }\n.test-status, .run-meta { font-size: 12px; color: #666; }\n.run-meta { display: flex; gap: 12px; }\n.no-data { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: #999; text-align: center; background: white; border-radius: 12px; }\n.no-data i { font-size: 48px; margin-bottom: 16px; opacity: 0.3; }\n.no-data p { margin: 0; font-size: 14px; }\n"] }]
|
|
356
|
-
}], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: i3.TestingDialogService }],
|
|
357
|
-
|
|
3457
|
+
args: [{ selector: 'mj-test-suite-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"test-suite-form\" kendoDialogContainer>\n <!-- Header Section -->\n <div class=\"suite-header\">\n <div class=\"header-content\">\n <div class=\"header-left\">\n <div class=\"suite-icon\" [style.background-color]=\"getStatusColor()\">\n <i class=\"fas fa-layer-group\"></i>\n </div>\n <div class=\"suite-info\">\n <h1>{{ record.Name }}</h1>\n <div class=\"suite-meta\">\n <span class=\"status-badge\" [ngClass]=\"getStatusClass()\">\n <i class=\"fas\" [ngClass]=\"record.Status === 'Active' ? 'fa-circle-check' : 'fa-circle-pause'\"></i>\n {{ record.Status }}\n </span>\n <span class=\"test-count\" *ngIf=\"testsLoaded\">\n <i class=\"fas fa-flask\"></i>\n {{ suiteTests.length }} tests\n </span>\n </div>\n </div>\n </div>\n <div class=\"header-actions\">\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n <button kendoButton (click)=\"exportToExcel()\" title=\"Export to CSV\">\n <i class=\"fas fa-file-excel\"></i> Export\n </button>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite\n </button>\n <button kendoButton (click)=\"refresh()\" [disabled]=\"isRefreshing\">\n <i class=\"fas\" [ngClass]=\"isRefreshing ? 'fa-sync fa-spin' : 'fa-sync'\"></i>\n {{ isRefreshing ? 'Refreshing...' : 'Refresh' }}\n </button>\n </div>\n </div>\n <div class=\"suite-description\" *ngIf=\"record.Description\">\n <p>{{ record.Description }}</p>\n </div>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs-container\">\n <div class=\"tabs\" role=\"tablist\">\n <button class=\"tab\"\n [class.active]=\"activeTab === 'overview'\"\n (click)=\"changeTab('overview')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'overview'\">\n <i class=\"fas fa-th-large\"></i> Overview\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'tests'\"\n (click)=\"changeTab('tests')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'tests'\">\n <i class=\"fas fa-flask\"></i> Tests\n <span class=\"tab-badge\" *ngIf=\"testsLoaded\">{{ suiteTests.length }}</span>\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'runs'\"\n (click)=\"changeTab('runs')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'runs'\">\n <i class=\"fas fa-history\"></i> Runs\n <span class=\"tab-badge\" *ngIf=\"runsLoaded\">{{ suiteRuns.length }}</span>\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'analytics'\"\n (click)=\"changeTab('analytics')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'analytics'\">\n <i class=\"fas fa-chart-line\"></i> Analytics\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'compare'\"\n (click)=\"changeTab('compare')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'compare'\">\n <i class=\"fas fa-balance-scale\"></i> Compare\n </button>\n </div>\n </div>\n\n <!-- Tab Content -->\n <div class=\"tab-content\">\n <!-- Overview Tab -->\n <div class=\"overview-tab\" *ngIf=\"activeTab === 'overview'\">\n <div class=\"info-section\">\n <h3><i class=\"fas fa-info-circle\"></i> Suite Information</h3>\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <div class=\"info-label\">Name</div>\n <div class=\"info-value\">{{ record.Name }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Status</div>\n <div class=\"info-value\">\n <span class=\"status-badge-inline\" [ngClass]=\"getStatusClass()\">{{ record.Status }}</span>\n </div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Created</div>\n <div class=\"info-value\">{{ record.__mj_CreatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Updated</div>\n <div class=\"info-value\">{{ record.__mj_UpdatedAt | date:'medium' }}</div>\n </div>\n <div class=\"info-item\">\n <div class=\"info-label\">Max Execution Time</div>\n <div class=\"info-value\">{{ formatTimeout(record.MaxExecutionTimeMS) }}</div>\n </div>\n </div>\n </div>\n\n <div class=\"config-section\">\n <h3><i class=\"fas fa-cogs\"></i> Execution Settings</h3>\n <div class=\"config-grid\">\n <div class=\"config-item\">\n <label>Max Execution Time (ms)</label>\n <input type=\"number\" [(ngModel)]=\"record.MaxExecutionTimeMS\" class=\"config-input\" placeholder=\"Default: 300000 (5 min)\" />\n <span class=\"config-hint\">Default timeout for tests in this suite</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Tests Tab -->\n <div class=\"tests-tab\" *ngIf=\"activeTab === 'tests'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingTests\">\n <div class=\"skeleton-list\">\n <div class=\"skeleton-card\" *ngFor=\"let i of [1,2,3,4,5]\">\n <div class=\"skeleton-sequence\"></div>\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Tests List -->\n <div class=\"tests-list\" *ngIf=\"!loadingTests && suiteTests.length > 0\">\n <div class=\"test-item\" *ngFor=\"let test of suiteTests\" (click)=\"openTest(test.TestID)\">\n <div class=\"test-sequence\">{{ test.Sequence }}</div>\n <div class=\"test-icon\"><i class=\"fas fa-flask\"></i></div>\n <div class=\"test-content\">\n <div class=\"test-name\">{{ test.Test }}</div>\n <div class=\"test-status\">\n <span *ngIf=\"test.Status\"><i class=\"fas fa-info-circle\"></i> {{ test.Status }}</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"testsLoaded && !loadingTests && suiteTests.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-flask\"></i>\n </div>\n <h4>No Tests in Suite</h4>\n <p>Add tests to this suite to start running them together.</p>\n </div>\n </div>\n\n <!-- Runs Tab -->\n <div class=\"runs-tab\" *ngIf=\"activeTab === 'runs'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingRuns\">\n <div class=\"skeleton-list\">\n <div class=\"skeleton-card\" *ngFor=\"let i of [1,2,3]\">\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Runs List -->\n <div class=\"runs-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-item\" *ngFor=\"let run of suiteRuns\" (click)=\"openSuiteRun(run.ID)\">\n <div class=\"run-icon\" [style.background-color]=\"getRunStatusColor(run.Status)\">\n <i class=\"fas\"\n [class.fa-check]=\"run.Status === 'Completed'\"\n [class.fa-times]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </div>\n <div class=\"run-content\">\n <div class=\"run-header\">\n <span class=\"run-id\">Run #{{ run.ID.substring(0, 8) }}</span>\n <span class=\"run-status\" [style.color]=\"getRunStatusColor(run.Status)\">{{ run.Status }}</span>\n </div>\n <div class=\"run-meta\">\n <span><i class=\"fas fa-calendar\"></i> {{ getRelativeTime(run.StartedAt) }}</span>\n <span *ngIf=\"run.TotalTests\">\n <i class=\"fas fa-check-circle\"></i> {{ run.PassedTests }}/{{ run.TotalTests }}\n ({{ getPassRate(run).toFixed(0) }}%)\n </span>\n </div>\n <!-- Evaluation metrics row -->\n <div class=\"run-eval-metrics\">\n <!-- Status badge -->\n <span class=\"eval-metric status\" *ngIf=\"evalPreferences.showExecution\" [class]=\"'status-' + run.Status.toLowerCase()\">\n <i class=\"fas\"\n [class.fa-circle-check]=\"run.Status === 'Completed'\"\n [class.fa-circle-xmark]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </span>\n <!-- Human score (placeholder - need avg from suite run) -->\n <span class=\"eval-metric human\" *ngIf=\"evalPreferences.showHuman\" title=\"Human evaluation\">\n <i class=\"fas fa-user\"></i>\n <span class=\"eval-pending\"><i class=\"fas fa-clock\"></i></span>\n </span>\n <!-- Auto score (pass rate as proxy) -->\n <span class=\"eval-metric auto\" *ngIf=\"evalPreferences.showAuto && run.TotalTests\"\n [class.high]=\"getPassRate(run) >= 80\"\n [class.medium]=\"getPassRate(run) >= 50 && getPassRate(run) < 80\"\n [class.low]=\"getPassRate(run) < 50\"\n title=\"Auto score (pass rate)\">\n <i class=\"fas fa-robot\"></i>\n <span>{{ getPassRate(run).toFixed(0) }}%</span>\n </span>\n </div>\n <!-- Tags display -->\n <div class=\"run-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-chip\" *ngFor=\"let tag of getRunTags(run)\">{{ tag }}</span>\n </div>\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n </div>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"runsLoaded && !loadingRuns && suiteRuns.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-play-circle\"></i>\n </div>\n <h4>No Suite Runs Yet</h4>\n <p>Run this suite to see execution history and results here.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n\n <!-- Analytics Tab -->\n <div class=\"analytics-tab\" *ngIf=\"activeTab === 'analytics'\">\n <!-- Loading State -->\n <div class=\"loading-state\" *ngIf=\"loadingAnalytics\">\n <mj-loading text=\"Loading analytics data...\"></mj-loading>\n </div>\n\n <ng-container *ngIf=\"!loadingAnalytics && analyticsLoaded\">\n <!-- View Toggle Sub-nav -->\n <div class=\"analytics-subnav\">\n <div class=\"subnav-tabs\">\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'summary'\"\n (click)=\"setAnalyticsView('summary')\">\n <i class=\"fas fa-chart-bar\"></i>\n <span>Summary</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'matrix'\"\n (click)=\"setAnalyticsView('matrix')\">\n <i class=\"fas fa-th\"></i>\n <span>Matrix</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'chart'\"\n (click)=\"setAnalyticsView('chart')\">\n <i class=\"fas fa-project-diagram\"></i>\n <span>Chart</span>\n </button>\n </div>\n </div>\n\n <!-- Collapsible Filters (shared by both views) -->\n <div class=\"analytics-filters\" [class.collapsed]=\"filtersCollapsed\">\n <div class=\"filters-header\" (click)=\"toggleFilters()\">\n <span class=\"filters-title\">\n <i class=\"fas fa-filter\"></i>\n Filters\n <span class=\"filter-summary\" *ngIf=\"filtersCollapsed\">\n {{ analyticsTimeRange === 'all' ? 'All Time' : analyticsTimeRange }}\n <span *ngIf=\"selectedTags.length > 0\"> \u00B7 {{ selectedTags.length }} tags</span>\n </span>\n </span>\n <i class=\"fas\" [ngClass]=\"filtersCollapsed ? 'fa-chevron-down' : 'fa-chevron-up'\"></i>\n </div>\n <div class=\"filters-content\" *ngIf=\"!filtersCollapsed\">\n <div class=\"filter-group\">\n <label>Time Range</label>\n <div class=\"filter-buttons\">\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '7d'\" (click)=\"setTimeRange('7d')\">7 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '30d'\" (click)=\"setTimeRange('30d')\">30 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '90d'\" (click)=\"setTimeRange('90d')\">90 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === 'all'\" (click)=\"setTimeRange('all')\">All Time</button>\n </div>\n </div>\n <div class=\"filter-group\" *ngIf=\"uniqueTags.length > 0\">\n <label>Filter by Tag <span class=\"filter-hint\" *ngIf=\"selectedTags.length > 0\">({{ selectedTags.length }} selected)</span></label>\n <div class=\"filter-buttons tag-filters\">\n <button class=\"filter-btn tag-btn all-tags-btn\"\n [class.active]=\"selectedTags.length === 0\"\n (click)=\"toggleTagFilter(null)\">\n <i class=\"fas fa-layer-group\"></i> All Tags\n </button>\n <button class=\"filter-btn tag-btn\"\n *ngFor=\"let tag of uniqueTags\"\n [class.active]=\"isTagSelected(tag)\"\n (click)=\"toggleTagFilter(tag)\">\n <i class=\"fas fa-check\" *ngIf=\"isTagSelected(tag)\"></i>\n {{ tag }}\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Summary View -->\n <ng-container *ngIf=\"analyticsView === 'summary'\">\n <!-- KPI Cards -->\n <div class=\"analytics-kpis\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon\"><i class=\"fas fa-play-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getTotalRuns() }}</div>\n <div class=\"kpi-label\">Total Runs</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon success\"><i class=\"fas fa-check-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getAveragePassRate().toFixed(1) }}%</div>\n <div class=\"kpi-label\">Avg Pass Rate</div>\n <div class=\"kpi-trend\" [ngClass]=\"{'trend-up': getPassRateTrend().direction === 'up', 'trend-down': getPassRateTrend().direction === 'down'}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getPassRateTrend().direction === 'up', 'fa-arrow-down': getPassRateTrend().direction === 'down', 'fa-minus': getPassRateTrend().direction === 'stable'}\"></i>\n {{ getPassRateTrend().value.toFixed(1) }}%\n </div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon info\"><i class=\"fas fa-clock\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatDuration(getAverageDuration()) }}</div>\n <div class=\"kpi-label\">Avg Duration</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon warning\"><i class=\"fas fa-dollar-sign\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatCost(getTotalCost()) }}</div>\n <div class=\"kpi-label\">Total Cost</div>\n </div>\n </div>\n </div>\n\n <!-- Runs Table -->\n <div class=\"analytics-table-section\">\n <h3><i class=\"fas fa-table\"></i> Run History</h3>\n <div class=\"analytics-table-wrapper\">\n <table class=\"analytics-table\">\n <thead>\n <tr>\n <th>Date</th>\n <th>Status</th>\n <th>Pass Rate</th>\n <th>Tests</th>\n <th>Duration</th>\n <th>Cost</th>\n <th>Tags</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let dp of getFilteredAnalyticsData()\" (click)=\"openSuiteRun(dp.runId)\" class=\"clickable-row\">\n <td>{{ dp.date | date:'short' }}</td>\n <td>\n <span class=\"status-chip\" [ngClass]=\"'status-' + dp.status.toLowerCase()\">{{ dp.status }}</span>\n </td>\n <td>\n <div class=\"pass-rate-cell\">\n <div class=\"pass-rate-bar\" [style.width.%]=\"dp.passRate\" [ngClass]=\"{'high': dp.passRate >= 80, 'medium': dp.passRate >= 50 && dp.passRate < 80, 'low': dp.passRate < 50}\"></div>\n <span>{{ dp.passRate.toFixed(0) }}%</span>\n </div>\n </td>\n <td>{{ dp.passedTests }}/{{ dp.totalTests }}</td>\n <td>{{ formatDuration(dp.duration) }}</td>\n <td>{{ formatCost(dp.cost) }}</td>\n <td>\n <div class=\"tag-cell\">\n <span class=\"tag-chip-table\" *ngFor=\"let tag of dp.tags.slice(0, 2)\">{{ tag }}</span>\n <span class=\"tag-more\" *ngIf=\"dp.tags.length > 2\">+{{ dp.tags.length - 2 }}</span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n <div class=\"empty-state small\" *ngIf=\"getFilteredAnalyticsData().length === 0\">\n <p>No runs match the current filters.</p>\n </div>\n </div>\n </ng-container>\n\n <!-- Matrix View -->\n <ng-container *ngIf=\"analyticsView === 'matrix'\">\n <!-- Loading Matrix -->\n <div class=\"loading-state\" *ngIf=\"loadingMatrix\">\n <mj-loading text=\"Loading test matrix...\"></mj-loading>\n </div>\n\n <!-- Matrix Content -->\n <div class=\"matrix-section\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length > 0\">\n <div class=\"matrix-header\">\n <h3><i class=\"fas fa-th\"></i> Test Results Matrix</h3>\n <div class=\"matrix-header-right\">\n <div class=\"matrix-filter-input\">\n <i class=\"fas fa-search\"></i>\n <input type=\"text\"\n placeholder=\"Filter tests...\"\n [value]=\"matrixTestFilter\"\n (input)=\"onMatrixFilterInput($event)\"\n class=\"filter-input\">\n <button class=\"clear-filter-btn\" *ngIf=\"matrixTestFilter\" (click)=\"clearMatrixFilter()\" title=\"Clear filter\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n <span class=\"matrix-run-count\">{{ matrixData.length }} runs \u00B7 {{ getUniqueTestsFromMatrix().length }} tests</span>\n <button kendoButton (click)=\"exportMatrixToCSV()\" [disabled]=\"matrixData.length === 0\" title=\"Export matrix to CSV\">\n <i class=\"fas fa-download\"></i> Export\n </button>\n </div>\n </div>\n\n <div class=\"matrix-scroll-container\">\n <table class=\"test-matrix\">\n <thead>\n <tr>\n <th class=\"seq-header\" (click)=\"toggleMatrixSort('sequence')\" title=\"Sort by sequence\">\n #\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'sequence' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n <th class=\"test-name-header\" (click)=\"toggleMatrixSort('name')\" title=\"Sort by name\">\n Test\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'name' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n <th class=\"run-header\" *ngFor=\"let run of matrixData\" (click)=\"openSuiteRun(run.runId)\" [title]=\"'Click to view suite run - ' + (run.date | date:'medium')\">\n <div class=\"run-header-content\">\n <div class=\"run-tags-header\" *ngIf=\"run.tags.length > 0\">\n <span class=\"tag-chip-header\" *ngFor=\"let tag of run.tags.slice(0, 2)\">{{ tag }}</span>\n <span class=\"tag-more-header\" *ngIf=\"run.tags.length > 2\">+{{ run.tags.length - 2 }}</span>\n </div>\n <div class=\"run-date\">{{ getRelativeTime(run.date) }}</div>\n <div class=\"run-pass-rate\" [ngClass]=\"{'high': run.passRate >= 80, 'medium': run.passRate >= 50 && run.passRate < 80, 'low': run.passRate < 50}\">\n {{ run.passRate.toFixed(0) }}%\n </div>\n </div>\n </th>\n <th class=\"spacer-header\"></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let test of getUniqueTestsFromMatrix()\"\n [class.row-selected]=\"selectedMatrixTestId === test.testId\"\n (click)=\"selectMatrixRow(test.testId)\">\n <td class=\"seq-cell\">{{ test.sequence }}</td>\n <td class=\"test-name-cell\">\n <span class=\"test-name\" [title]=\"test.testName\">{{ test.testName }}</span>\n </td>\n <td class=\"result-cell\"\n *ngFor=\"let run of matrixData\"\n [ngClass]=\"getMatrixCellClass(getTestResultForRun(run.runId, test.testId))\"\n [class.clickable]=\"getTestResultForRun(run.runId, test.testId)\"\n [class.cell-not-run]=\"!getTestResultForRun(run.runId, test.testId)\"\n [title]=\"getTestResultForRun(run.runId, test.testId)?.status + ' - Click to view test run' || 'Not Run'\"\n (click)=\"onMatrixCellClick(getTestResultForRun(run.runId, test.testId), $event)\">\n <ng-container *ngIf=\"getTestResultForRun(run.runId, test.testId) as result\">\n <div class=\"cell-eval-stack\">\n <!-- Status indicator -->\n <span class=\"cell-status\" *ngIf=\"evalPreferences.showExecution\"\n [ngClass]=\"'status-' + result.status.toLowerCase()\"\n [class.cell-skipped-status]=\"result.status === 'Skipped' || result.status === 'Pending'\"\n [title]=\"getStatusTooltip(result.status)\">\n <i class=\"fas\"\n [class.fa-check]=\"result.status === 'Passed'\"\n [class.fa-times]=\"result.status === 'Failed'\"\n [class.fa-exclamation]=\"result.status === 'Error'\"\n [class.fa-hourglass-end]=\"result.status === 'Timeout'\"\n [class.fa-forward]=\"result.status === 'Skipped'\"\n [class.fa-spinner]=\"result.status === 'Running'\"\n [class.fa-clock]=\"result.status === 'Pending'\"></i>\n </span>\n <!-- Human score - slashed icon if no feedback, colored by rating if has feedback -->\n <span class=\"cell-human no-feedback\" *ngIf=\"evalPreferences.showHuman && !result.humanRating\"\n title=\"Human Review: No rating submitted yet\">\n <i class=\"fas fa-user-slash\"></i>\n </span>\n <span class=\"cell-human has-feedback\" *ngIf=\"evalPreferences.showHuman && result.humanRating\"\n [class.rating-low]=\"result.humanRating <= 4\"\n [class.rating-medium]=\"result.humanRating >= 5 && result.humanRating <= 6\"\n [class.rating-good]=\"result.humanRating >= 7 && result.humanRating <= 8\"\n [class.rating-excellent]=\"result.humanRating >= 9\"\n [title]=\"getHumanTooltip(result.humanRating, result.humanComments)\">\n <i class=\"fas fa-user\"></i>\n <span class=\"rating-value\">{{ result.humanRating }}</span>\n </span>\n <!-- Auto score - colored by percentage -->\n <span class=\"cell-auto has-score\" *ngIf=\"evalPreferences.showAuto && result.score != null\"\n [class.score-low]=\"result.score < 0.5\"\n [class.score-medium]=\"result.score >= 0.5 && result.score < 0.7\"\n [class.score-good]=\"result.score >= 0.7 && result.score < 0.85\"\n [class.score-excellent]=\"result.score >= 0.85\"\n [title]=\"'Auto Score: ' + (result.score * 100).toFixed(0) + '% automated evaluation'\">\n <i class=\"fas fa-robot\"></i>\n <span class=\"score-value\">{{ (result.score * 100).toFixed(0) }}</span>\n </span>\n <span class=\"cell-auto no-score\" *ngIf=\"evalPreferences.showAuto && result.score == null\"\n title=\"Auto Score: No automated score available\">\n <i class=\"fas fa-robot\"></i>\n </span>\n </div>\n </ng-container>\n <span class=\"cell-not-run-indicator\" *ngIf=\"!getTestResultForRun(run.runId, test.testId)\">\n <i class=\"fas fa-minus\"></i>\n </span>\n </td>\n <td class=\"spacer-cell\"></td>\n </tr>\n </tbody>\n <!-- Footer row with totals -->\n <tfoot>\n <tr class=\"totals-row\">\n <td class=\"seq-cell totals-label\"></td>\n <td class=\"test-name-cell totals-label\">\n <strong>Totals</strong>\n </td>\n <td class=\"result-cell totals-cell\" *ngFor=\"let run of matrixData\">\n <div class=\"cell-eval-stack totals-stack\">\n <!-- Status totals -->\n <span class=\"totals-status\" *ngIf=\"evalPreferences.showExecution\">\n <span class=\"pass-count\">{{ getRunPassedCount(run) }}/{{ getRunTotalCount(run) }}</span>\n </span>\n <!-- Human totals -->\n <span class=\"totals-human\" *ngIf=\"evalPreferences.showHuman\">\n <span class=\"avg-label\" *ngIf=\"getRunHumanAvg(run) != null\">{{ getRunHumanAvg(run)?.toFixed(1) }}</span>\n <span class=\"count-label\">({{ getRunHumanCount(run) }})</span>\n </span>\n <!-- Auto totals -->\n <span class=\"totals-auto\" *ngIf=\"evalPreferences.showAuto\">\n <span class=\"avg-label\" *ngIf=\"getRunAutoAvg(run) != null\">{{ (getRunAutoAvg(run)! * 100).toFixed(0) }}%</span>\n <span class=\"count-label\">({{ getRunAutoCount(run) }})</span>\n </span>\n </div>\n </td>\n <td class=\"spacer-cell\"></td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n\n <!-- Empty Matrix State -->\n <div class=\"empty-state\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-th\"></i>\n </div>\n <h4>No Matrix Data</h4>\n <p>No suite runs match the current filters.</p>\n </div>\n </ng-container>\n\n <!-- Chart View -->\n <ng-container *ngIf=\"analyticsView === 'chart'\">\n <!-- Loading Chart -->\n <div class=\"loading-state\" *ngIf=\"loadingMatrix\">\n <mj-loading text=\"Loading chart data...\"></mj-loading>\n </div>\n\n <!-- Chart Content -->\n <div class=\"chart-section\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length > 0\">\n <div class=\"chart-header\">\n <h3><i class=\"fas fa-project-diagram\"></i> Test Results Flow</h3>\n <div class=\"chart-legend\">\n <span class=\"legend-item chart-passed\"><i class=\"fas fa-check\"></i> Passed</span>\n <span class=\"legend-item chart-failed\"><i class=\"fas fa-times\"></i> Failed</span>\n <span class=\"legend-item chart-error\"><i class=\"fas fa-exclamation\"></i> Error</span>\n <span class=\"legend-item chart-skipped\"><i class=\"fas fa-forward\"></i> Skipped</span>\n </div>\n </div>\n\n <div class=\"chart-container\">\n <div #chartContainer class=\"d3-chart\"></div>\n </div>\n\n <div class=\"chart-info\">\n <i class=\"fas fa-info-circle\"></i>\n Interactive visualization showing test results across {{ matrixData.length }} runs.\n Hover over elements for details, click nodes to navigate.\n </div>\n </div>\n\n <!-- Empty Chart State -->\n <div class=\"empty-state\" *ngIf=\"!loadingMatrix && matrixLoaded && matrixData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-project-diagram\"></i>\n </div>\n <h4>No Chart Data</h4>\n <p>No suite runs match the current filters.</p>\n </div>\n </ng-container>\n </ng-container>\n\n <!-- Empty State -->\n <div class=\"empty-state\" *ngIf=\"!loadingAnalytics && analyticsLoaded && analyticsData.length === 0\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-chart-line\"></i>\n </div>\n <h4>No Analytics Data</h4>\n <p>Run this suite to start collecting analytics data.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n\n <!-- Compare Tab -->\n <div class=\"compare-tab\" *ngIf=\"activeTab === 'compare'\">\n <!-- Run Selection -->\n <div class=\"compare-selection\">\n <div class=\"compare-run-selector\">\n <h4>Run A (Baseline)</h4>\n <div class=\"run-selector-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-selector-item\"\n *ngFor=\"let run of suiteRuns\"\n [class.selected]=\"compareRunA?.ID === run.ID\"\n (click)=\"selectCompareRunA(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n <div class=\"selector-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-mini\" *ngFor=\"let tag of getRunTags(run).slice(0, 2)\">{{ tag }}</span>\n </div>\n </div>\n </div>\n <div class=\"selected-run-preview\" *ngIf=\"compareRunA\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunA = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunA.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunA).toFixed(1) }}%</span>\n </div>\n </div>\n </div>\n\n <div class=\"compare-vs\"><i class=\"fas fa-exchange-alt\"></i></div>\n\n <div class=\"compare-run-selector\">\n <h4>Run B (Compare)</h4>\n <div class=\"run-selector-list\" *ngIf=\"!loadingRuns && suiteRuns.length > 0\">\n <div class=\"run-selector-item\"\n *ngFor=\"let run of suiteRuns\"\n [class.selected]=\"compareRunB?.ID === run.ID\"\n [class.disabled]=\"compareRunA?.ID === run.ID\"\n (click)=\"compareRunA?.ID !== run.ID && selectCompareRunB(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n <div class=\"selector-tags\" *ngIf=\"getRunTags(run).length > 0\">\n <span class=\"tag-mini\" *ngFor=\"let tag of getRunTags(run).slice(0, 2)\">{{ tag }}</span>\n </div>\n </div>\n </div>\n <div class=\"selected-run-preview\" *ngIf=\"compareRunB\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunB = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunB.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunB).toFixed(1) }}%</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Comparison Results -->\n <div class=\"compare-results\" *ngIf=\"compareRunA && compareRunB && !loadingCompare\">\n <!-- Summary Cards -->\n <div class=\"compare-summary\">\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Pass Rate Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getComparePassRateDiff()! > 0, 'negative': getComparePassRateDiff()! < 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getComparePassRateDiff()! > 0, 'fa-arrow-down': getComparePassRateDiff()! < 0, 'fa-minus': getComparePassRateDiff() === 0}\"></i>\n {{ getComparePassRateDiff()! > 0 ? '+' : '' }}{{ getComparePassRateDiff()?.toFixed(1) }}%\n </div>\n </div>\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Duration Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getCompareDurationDiff()! < 0, 'negative': getCompareDurationDiff()! > 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-down': getCompareDurationDiff()! < 0, 'fa-arrow-up': getCompareDurationDiff()! > 0, 'fa-minus': getCompareDurationDiff() === 0}\"></i>\n {{ formatDuration(getAbsCompareDurationDiff()) }}\n </div>\n </div>\n <div class=\"compare-summary-card improved\">\n <div class=\"summary-label\">Improved</div>\n <div class=\"summary-value\">{{ getCompareImprovedCount() }}</div>\n </div>\n <div class=\"compare-summary-card regressed\">\n <div class=\"summary-label\">Regressed</div>\n <div class=\"summary-value\">{{ getCompareRegressedCount() }}</div>\n </div>\n </div>\n\n <!-- Detailed Comparison Table -->\n <div class=\"compare-table-section\">\n <h3><i class=\"fas fa-list\"></i> Test-by-Test Comparison</h3>\n <div class=\"compare-table-wrapper\">\n <table class=\"compare-table\">\n <thead>\n <tr>\n <th>Test</th>\n <th>Run A Status</th>\n <th>Run B Status</th>\n <th>Score Diff</th>\n <th>Duration Diff</th>\n <th>Change</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let result of compareResults\" [ngClass]=\"{'improved': result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed', 'regressed': result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed'}\">\n <td class=\"test-name-cell\">{{ result.testName }}</td>\n <td>\n <span class=\"status-chip\" *ngIf=\"result.runA\" [ngClass]=\"'status-' + result.runA.status.toLowerCase()\">{{ result.runA.status }}</span>\n <span class=\"status-chip status-missing\" *ngIf=\"!result.runA\">N/A</span>\n </td>\n <td>\n <span class=\"status-chip\" *ngIf=\"result.runB\" [ngClass]=\"'status-' + result.runB.status.toLowerCase()\">{{ result.runB.status }}</span>\n <span class=\"status-chip status-missing\" *ngIf=\"!result.runB\">N/A</span>\n </td>\n <td>\n <span *ngIf=\"result.scoreDiff != null\" [ngClass]=\"{'positive': result.scoreDiff > 0, 'negative': result.scoreDiff < 0}\">\n {{ result.scoreDiff > 0 ? '+' : '' }}{{ (result.scoreDiff * 100).toFixed(1) }}%\n </span>\n <span *ngIf=\"result.scoreDiff == null\" class=\"muted\">-</span>\n </td>\n <td>\n <span *ngIf=\"result.durationDiff != null\" [ngClass]=\"{'positive': result.durationDiff < 0, 'negative': result.durationDiff > 0}\">\n {{ result.durationDiff > 0 ? '+' : '' }}{{ result.durationDiff.toFixed(1) }}s\n </span>\n <span *ngIf=\"result.durationDiff == null\" class=\"muted\">-</span>\n </td>\n <td>\n <span class=\"change-indicator improved\" *ngIf=\"result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed'\">\n <i class=\"fas fa-arrow-up\"></i> Fixed\n </span>\n <span class=\"change-indicator regressed\" *ngIf=\"result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed'\">\n <i class=\"fas fa-arrow-down\"></i> Broke\n </span>\n <span class=\"change-indicator unchanged\" *ngIf=\"!result.statusChanged\">\n <i class=\"fas fa-minus\"></i>\n </span>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Loading State for Compare -->\n <div class=\"loading-state\" *ngIf=\"loadingCompare\">\n <mj-loading text=\"Loading comparison data...\"></mj-loading>\n </div>\n\n <!-- Empty State -->\n <div class=\"compare-empty\" *ngIf=\"!compareRunA || !compareRunB\">\n <div class=\"compare-empty-icon\">\n <i class=\"fas fa-balance-scale\"></i>\n </div>\n <h4>Select Two Runs to Compare</h4>\n <p>Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison.</p>\n </div>\n\n <!-- No Runs State -->\n <div class=\"empty-state\" *ngIf=\"runsLoaded && suiteRuns.length < 2\">\n <div class=\"empty-icon\">\n <i class=\"fas fa-balance-scale\"></i>\n </div>\n <h4>Not Enough Runs to Compare</h4>\n <p>You need at least 2 suite runs to use the comparison feature.</p>\n <button kendoButton (click)=\"runSuite()\" themeColor=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite Now\n </button>\n </div>\n </div>\n </div>\n\n <!-- Keyboard Shortcuts Toggle Button -->\n <button class=\"shortcuts-toggle\" (click)=\"toggleShortcuts()\" [title]=\"showShortcuts ? 'Hide keyboard shortcuts' : 'Show keyboard shortcuts'\">\n <i class=\"fas fa-keyboard\"></i>\n </button>\n\n <!-- Keyboard Shortcuts Hint (Desktop Only) -->\n <div class=\"keyboard-shortcuts\" *ngIf=\"showShortcuts\">\n <div class=\"shortcuts-header\">\n <i class=\"fas fa-keyboard\"></i>\n Shortcuts\n <button class=\"shortcuts-close\" (click)=\"toggleShortcuts()\" title=\"Hide shortcuts\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n <div class=\"shortcut-list\">\n <div class=\"shortcut-item\">\n <span>Refresh</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>R</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Run Suite</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>Enter</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Switch Tabs</span>\n <span class=\"shortcut-keys\"><kbd>1</kbd>-<kbd>5</kbd></span>\n </div>\n </div>\n </div>\n</div>\n", styles: ["/* ===========================\n Test Suite Form - World-Class UX\n =========================== */\n\n/* CSS Custom Properties for Theming - using :host for Angular encapsulation */\n:host {\n --test-primary: #2563eb;\n --test-primary-light: #3b82f6;\n --test-success: #10b981;\n --test-error: #ef4444;\n --test-warning: #f59e0b;\n --test-disabled: #6b7280;\n --test-bg: #f8fafc;\n --test-surface: #ffffff;\n --test-border: #e2e8f0;\n --test-text: #1e293b;\n --test-text-secondary: #64748b;\n --test-text-muted: #94a3b8;\n --test-radius-sm: 6px;\n --test-radius-md: 10px;\n --test-radius-lg: 16px;\n --test-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);\n --test-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);\n --test-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n --test-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: block;\n height: 100%;\n}\n\n.test-suite-form {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--test-bg);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* Header */\n.suite-header {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n padding: 20px;\n}\n\n.header-content {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n gap: 16px;\n}\n\n.header-left {\n display: flex;\n gap: 16px;\n flex: 1;\n min-width: 0;\n}\n\n.suite-icon {\n width: 56px;\n height: 56px;\n border-radius: var(--test-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 24px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-md);\n transition: var(--test-transition);\n}\n\n.suite-icon:hover { transform: scale(1.05); }\n\n.suite-info { flex: 1; min-width: 0; }\n\n.suite-info h1 {\n margin: 0 0 8px 0;\n font-size: clamp(18px, 4vw, 24px);\n font-weight: 700;\n color: var(--test-text);\n word-wrap: break-word;\n}\n\n.suite-meta {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border-radius: 20px;\n color: white;\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-badge.status-active { background: linear-gradient(135deg, var(--test-success) 0%, #059669 100%); }\n.status-badge.status-disabled { background: linear-gradient(135deg, var(--test-disabled) 0%, #4b5563 100%); }\n.status-badge.status-pending { background: linear-gradient(135deg, var(--test-warning) 0%, #d97706 100%); }\n\n.status-badge-inline {\n display: inline-flex;\n padding: 2px 10px;\n border-radius: 10px;\n color: white;\n font-size: 11px;\n font-weight: 600;\n}\n\n.status-badge-inline.status-active { background: var(--test-success); }\n.status-badge-inline.status-disabled { background: var(--test-disabled); }\n.status-badge-inline.status-pending { background: var(--test-warning); }\n\n.test-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--test-text-secondary);\n padding: 4px 10px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.header-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.suite-description {\n padding: 16px;\n background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.suite-description p {\n margin: 0;\n color: var(--test-text-secondary);\n line-height: 1.6;\n font-size: 14px;\n}\n\n/* Tabs */\n.tabs-container {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n position: sticky;\n top: 0;\n z-index: 10;\n}\n\n.tabs {\n display: flex;\n padding: 0 20px;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n.tabs::-webkit-scrollbar { display: none; }\n\n.tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 18px;\n border: none;\n background: transparent;\n border-bottom: 3px solid transparent;\n color: var(--test-text-secondary);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n white-space: nowrap;\n}\n\n.tab:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.tab.active {\n color: var(--test-primary);\n border-bottom-color: var(--test-primary);\n font-weight: 600;\n}\n\n.tab-badge {\n background: var(--test-border);\n color: var(--test-text-secondary);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.tab.active .tab-badge {\n background: rgba(37, 99, 235, 0.15);\n color: var(--test-primary);\n}\n\n.tab-shortcut {\n font-size: 10px;\n color: var(--test-text-muted);\n background: var(--test-bg);\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n}\n\n/* Tab Content */\n.tab-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n/* Overview Tab */\n.overview-tab {\n display: flex;\n flex-direction: column;\n gap: 20px;\n animation: fadeIn 0.3s ease-out;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.info-section, .config-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.info-section h3, .config-section h3 {\n margin: 0 0 20px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.info-section h3 i, .config-section h3 i {\n color: var(--test-primary);\n}\n\n.info-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n}\n\n.info-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.info-label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.info-value {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 500;\n}\n\n.config-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n}\n\n.config-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.config-item label {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.config-input {\n padding: 10px 14px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.config-input:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);\n}\n\n.config-hint {\n font-size: 11px;\n color: var(--test-text-muted);\n}\n\n/* Tests Tab */\n.tests-tab, .runs-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.loading-state { padding: 0; }\n\n.skeleton-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skeleton-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.skeleton-sequence {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-icon {\n width: 40px;\n height: 40px;\n border-radius: var(--test-radius-md);\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.skeleton-line {\n height: 14px;\n border-radius: 4px;\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-line.wide { width: 70%; }\n.skeleton-line.narrow { width: 40%; }\n\n@keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n.tests-list, .runs-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.test-item, .run-item {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.test-item:hover, .run-item:hover {\n background: rgba(37, 99, 235, 0.05);\n border-color: var(--test-primary-light);\n transform: translateX(4px);\n}\n\n.test-sequence {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n font-size: 14px;\n font-weight: 700;\n color: var(--test-text-secondary);\n flex-shrink: 0;\n}\n\n.test-icon, .run-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--test-radius-md);\n color: white;\n font-size: 18px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-sm);\n}\n\n.test-icon {\n background: linear-gradient(135deg, var(--test-primary) 0%, #1d4ed8 100%);\n}\n\n.test-content, .run-content { flex: 1; min-width: 0; }\n\n.test-name, .run-header {\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n margin-bottom: 4px;\n}\n\n.run-header {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.run-id { font-weight: 600; }\n\n.run-status {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.test-status, .run-meta {\n display: flex;\n gap: 12px;\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.test-status span, .run-meta span {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.test-item > i, .run-item > i {\n color: var(--test-text-muted);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.test-item:hover > i, .run-item:hover > i {\n color: var(--test-primary);\n transform: translateX(2px);\n}\n\n/* Empty States */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n}\n\n.empty-icon {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin-bottom: 20px;\n}\n\n.empty-icon i {\n font-size: 36px;\n color: var(--test-text-muted);\n}\n\n.empty-state h4 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.empty-state p {\n margin: 0 0 20px 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 300px;\n}\n\n/* Keyboard Shortcuts */\n/* Keyboard shortcuts toggle button - visible when shortcuts are hidden */\n.shortcuts-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n box-shadow: var(--test-shadow-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--test-text-secondary);\n font-size: 14px;\n z-index: 99;\n transition: var(--test-transition);\n opacity: 0.7;\n}\n\n.shortcuts-toggle:hover {\n opacity: 1;\n transform: scale(1.1);\n color: var(--test-primary);\n border-color: var(--test-primary);\n}\n\n.keyboard-shortcuts {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n padding: 12px 16px;\n box-shadow: var(--test-shadow-lg);\n font-size: 12px;\n z-index: 100;\n max-width: 260px;\n}\n\n.shortcuts-header {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text);\n}\n\n.shortcuts-close {\n margin-left: auto;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--test-text-muted);\n font-size: 12px;\n padding: 2px 4px;\n border-radius: 4px;\n transition: var(--test-transition);\n}\n\n.shortcuts-close:hover {\n color: var(--test-text);\n background: var(--test-border);\n}\n\n.shortcut-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.shortcut-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: var(--test-text-secondary);\n}\n\n.shortcut-keys {\n display: flex;\n gap: 4px;\n}\n\n.shortcut-keys kbd {\n background: var(--test-bg);\n border: 1px solid var(--test-border);\n border-radius: 4px;\n padding: 2px 6px;\n font-size: 11px;\n color: var(--test-text);\n}\n\n/* Responsive */\n@media (max-width: 1024px) {\n .keyboard-shortcuts, .shortcuts-toggle { display: none; }\n}\n\n@media (max-width: 768px) {\n .suite-header { padding: 16px; }\n\n .header-content {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions {\n width: 100%;\n justify-content: stretch;\n }\n\n .header-actions button { flex: 1; }\n\n .tab-shortcut { display: none; }\n\n .info-grid { grid-template-columns: 1fr; }\n\n .test-item, .run-item { padding: 14px; }\n}\n\n@media (max-width: 480px) {\n .suite-icon {\n width: 40px;\n height: 40px;\n font-size: 18px;\n }\n\n .suite-info h1 { font-size: 16px; }\n\n .tab-badge { display: none; }\n\n .test-sequence { display: none; }\n}\n\n@media (hover: none) and (pointer: coarse) {\n .test-item:active, .run-item:active {\n background: rgba(37, 99, 235, 0.1);\n transform: scale(0.98);\n }\n\n .tab { min-height: 48px; }\n .test-item, .run-item { min-height: 64px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media print {\n .header-actions, .tabs-container, .keyboard-shortcuts {\n display: none !important;\n }\n}\n\n/* ===========================\n Tags UI\n =========================== */\n.run-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.tag-chip {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);\n border: 1px solid #bfdbfe;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 500;\n color: #1d4ed8;\n}\n\n.tag-mini {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n background: #f1f5f9;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n color: #64748b;\n}\n\n.tag-more {\n font-size: 10px;\n color: #94a3b8;\n font-weight: 500;\n}\n\n/* Evaluation metrics row for Runs list */\n.run-eval-metrics {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n\n.eval-metric {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 11px;\n font-weight: 500;\n}\n\n.eval-metric.status {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.eval-metric.status.status-completed {\n background: #dcfce7;\n color: #166534;\n}\n\n.eval-metric.status.status-failed {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.eval-metric.status.status-running {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.eval-metric.status.status-pending {\n background: #fef3c7;\n color: #92400e;\n}\n\n.eval-metric.human {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n color: #92400e;\n}\n\n.eval-metric.human .eval-pending {\n font-size: 9px;\n color: #d97706;\n}\n\n.eval-metric.auto {\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n}\n\n.eval-metric.auto.high {\n background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);\n border: 1px solid #86efac;\n color: #166534;\n}\n\n.eval-metric.auto.medium {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #fcd34d;\n color: #92400e;\n}\n\n.eval-metric.auto.low {\n background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);\n border: 1px solid #fca5a5;\n color: #991b1b;\n}\n\n.tag-cell {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.tag-chip-table {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ===========================\n Analytics Tab\n =========================== */\n.analytics-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n/* Collapsible filters */\n.analytics-filters {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-bottom: 16px;\n box-shadow: var(--test-shadow-sm);\n overflow: hidden;\n}\n\n.analytics-filters.collapsed {\n margin-bottom: 12px;\n}\n\n.filters-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n transition: background 0.15s ease;\n}\n\n.filters-header:hover {\n background: var(--test-bg);\n}\n\n.filters-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--test-text-secondary);\n}\n\n.filters-title i {\n color: var(--test-primary);\n}\n\n.filter-summary {\n font-weight: 400;\n color: var(--test-text-muted);\n margin-left: 8px;\n}\n\n.filters-header > i {\n color: var(--test-text-muted);\n font-size: 12px;\n}\n\n.filters-content {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 16px 16px 16px;\n border-top: 1px solid var(--test-border);\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.filter-group label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.filter-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.filter-btn {\n padding: 8px 16px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.filter-btn:hover {\n border-color: var(--test-primary);\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.filter-btn.active {\n background: var(--test-primary);\n border-color: var(--test-primary);\n color: white;\n}\n\n.filter-btn.tag-btn {\n padding: 6px 12px;\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.filter-btn.tag-btn i.fa-check {\n font-size: 10px;\n}\n\n.filter-btn.tag-btn.all-tags-btn {\n font-weight: 600;\n}\n\n.filter-btn.tag-btn.all-tags-btn i {\n font-size: 11px;\n}\n\n.filter-hint {\n font-weight: 400;\n font-size: 10px;\n color: var(--test-primary);\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-filters {\n max-height: 120px;\n overflow-y: auto;\n}\n\n/* KPI Cards */\n.analytics-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card {\n display: flex;\n align-items: center;\n gap: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n transition: var(--test-transition);\n}\n\n.kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: var(--test-shadow-md);\n}\n\n.kpi-icon {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #eff6ff;\n border-radius: var(--test-radius-md);\n color: var(--test-primary);\n font-size: 20px;\n}\n\n.kpi-icon.success {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-icon.info {\n background: #f0f9ff;\n color: #0ea5e9;\n}\n\n.kpi-icon.warning {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.kpi-content {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n line-height: 1.2;\n}\n\n.kpi-label {\n font-size: 12px;\n color: var(--test-text-secondary);\n margin-top: 2px;\n}\n\n.kpi-trend {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n margin-top: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n background: #f1f5f9;\n color: var(--test-text-secondary);\n}\n\n.kpi-trend.trend-up {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.kpi-trend.trend-down {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n/* Analytics Table */\n.analytics-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.analytics-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.analytics-table-section h3 i {\n color: var(--test-primary);\n}\n\n.analytics-table-wrapper {\n overflow-x: auto;\n margin: 0 -24px;\n padding: 0 24px;\n}\n\n.analytics-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.analytics-table th {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.analytics-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.analytics-table tbody tr.clickable-row {\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.analytics-table tbody tr.clickable-row:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.status-chip {\n display: inline-flex;\n align-items: center;\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-chip.status-completed { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-passed { background: #ecfdf5; color: var(--test-success); }\n.status-chip.status-failed { background: #fef2f2; color: var(--test-error); }\n.status-chip.status-error { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-running { background: #eff6ff; color: var(--test-primary); }\n.status-chip.status-pending { background: #f5f3ff; color: #8b5cf6; }\n.status-chip.status-cancelled { background: #f1f5f9; color: var(--test-disabled); }\n.status-chip.status-missing { background: #f1f5f9; color: var(--test-text-muted); }\n.status-chip.status-timeout { background: #fffbeb; color: var(--test-warning); }\n.status-chip.status-skipped { background: #f1f5f9; color: var(--test-disabled); }\n\n.pass-rate-cell {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 100px;\n}\n\n.pass-rate-bar {\n height: 6px;\n border-radius: 3px;\n background: var(--test-success);\n transition: width 0.3s ease;\n max-width: 60px;\n}\n\n.pass-rate-bar.medium { background: var(--test-warning); }\n.pass-rate-bar.low { background: var(--test-error); }\n\n/* Analytics Sub-navigation */\n.analytics-subnav {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 8px;\n margin-bottom: 20px;\n box-shadow: var(--test-shadow-sm);\n display: inline-flex;\n}\n\n.subnav-tabs {\n display: flex;\n gap: 4px;\n background: var(--test-bg);\n border-radius: var(--test-radius-md);\n padding: 4px;\n}\n\n.subnav-tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--test-radius-sm);\n background: transparent;\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.subnav-tab:hover {\n color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.subnav-tab.active {\n background: var(--test-surface);\n color: var(--test-primary);\n box-shadow: var(--test-shadow-sm);\n}\n\n.subnav-tab i {\n font-size: 14px;\n}\n\n/* Matrix View */\n.matrix-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 16px;\n box-shadow: var(--test-shadow-sm);\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 280px);\n min-height: 300px;\n}\n\n.matrix-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--test-border);\n flex-shrink: 0;\n}\n\n.matrix-header-right {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.matrix-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.matrix-header h3 i {\n color: var(--test-primary);\n}\n\n.matrix-run-count {\n font-size: 12px;\n color: var(--test-text-muted);\n font-weight: 500;\n}\n\n/* Matrix filter input */\n.matrix-filter-input {\n display: flex;\n align-items: center;\n gap: 8px;\n background: #f8fafc;\n border: 1px solid var(--test-border);\n border-radius: 6px;\n padding: 4px 10px;\n min-width: 180px;\n}\n\n.matrix-filter-input i.fa-search {\n color: #94a3b8;\n font-size: 12px;\n}\n\n.matrix-filter-input .filter-input {\n border: none;\n background: transparent;\n outline: none;\n font-size: 12px;\n color: var(--test-text);\n width: 100%;\n}\n\n.matrix-filter-input .filter-input::placeholder {\n color: #94a3b8;\n}\n\n.clear-filter-btn {\n border: none;\n background: none;\n padding: 2px 4px;\n cursor: pointer;\n color: #94a3b8;\n font-size: 10px;\n border-radius: 3px;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn:hover {\n background: #e2e8f0;\n color: #64748b;\n}\n\n/* Scrollable matrix container with fixed height */\n.matrix-scroll-container {\n flex: 1;\n overflow: auto;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n}\n\n.test-matrix {\n display: table;\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.test-matrix thead {\n display: table-header-group;\n}\n\n.test-matrix thead tr {\n display: table-row;\n}\n\n.test-matrix tbody {\n display: table-row-group;\n}\n\n.test-matrix tbody tr {\n display: table-row;\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.test-matrix tbody tr:hover {\n background-color: rgba(59, 130, 246, 0.05);\n}\n\n.test-matrix tbody tr.row-selected {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix tbody tr.row-selected td {\n border-top: 1px solid rgba(59, 130, 246, 0.3);\n border-bottom: 1px solid rgba(59, 130, 246, 0.3);\n}\n\n.test-matrix tbody tr.row-selected .seq-cell,\n.test-matrix tbody tr.row-selected .test-name-cell {\n background-color: rgba(59, 130, 246, 0.12) !important;\n}\n\n.test-matrix tfoot {\n display: table-footer-group;\n}\n\n.test-matrix th,\n.test-matrix td {\n display: table-cell;\n border: 1px solid var(--test-border);\n padding: 8px 12px;\n text-align: center;\n vertical-align: middle;\n}\n\n.test-matrix th {\n background: var(--test-bg);\n font-weight: 600;\n font-size: 11px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 3px solid #475569 !important;\n}\n\n/* Sequence column - shared styles */\n.test-matrix .seq-header,\n.test-matrix .seq-cell {\n width: 36px;\n min-width: 36px;\n max-width: 36px;\n text-align: center;\n position: sticky;\n left: 0;\n font-size: 11px;\n color: #64748b;\n border-right: 1px solid var(--test-border);\n padding: 6px 4px !important;\n}\n\n/* Seq header - sticky top AND left, highest z-index */\n.test-matrix .seq-header {\n cursor: pointer;\n font-weight: 600;\n background: var(--test-bg);\n z-index: 12; /* Higher than other headers */\n top: 0;\n}\n\n/* Seq body cells - sticky left only, lower z-index */\n.test-matrix .seq-cell {\n background: var(--test-surface);\n z-index: 2;\n}\n\n.test-matrix .seq-header i {\n font-size: 9px;\n margin-left: 2px;\n opacity: 0.6;\n}\n\n.test-matrix .seq-header:hover {\n background: #f1f5f9;\n}\n\n.test-matrix .test-name-header {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-bg);\n z-index: 11;\n border-right: 2px solid var(--test-border);\n cursor: pointer;\n}\n\n.test-matrix .test-name-header i {\n font-size: 9px;\n margin-left: 4px;\n opacity: 0.6;\n}\n\n.test-matrix .test-name-header:hover {\n background: #f1f5f9;\n}\n\n.test-matrix .run-header {\n min-width: 120px;\n width: 120px;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n/* Spacer column absorbs extra width */\n.test-matrix .spacer-header,\n.test-matrix .spacer-cell {\n width: 100%;\n min-width: 20px;\n background: var(--test-bg);\n border: none;\n}\n\n.test-matrix .spacer-cell {\n background: var(--test-surface);\n}\n\n.test-matrix .run-header:hover {\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-header-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.run-date {\n font-size: 12px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.run-pass-rate {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.run-pass-rate.high {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.run-pass-rate.medium {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.run-pass-rate.low {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.run-tags {\n display: flex;\n gap: 4px;\n}\n\n.tag-tiny {\n font-size: 9px;\n padding: 2px 6px;\n background: #eff6ff;\n color: var(--test-primary);\n border-radius: 8px;\n white-space: nowrap;\n max-width: 60px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Matrix column header tags - emphasized */\n.run-tags-header {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n justify-content: center;\n margin-bottom: 4px;\n}\n\n.tag-chip-header {\n display: inline-block;\n padding: 3px 8px;\n background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);\n border: 1px solid #93c5fd;\n color: #1d4ed8;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 70px;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-more-header {\n font-size: 9px;\n color: var(--test-text-secondary);\n padding: 2px 4px;\n}\n\n.test-matrix .test-name-cell {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-surface);\n z-index: 2;\n border-right: 2px solid var(--test-border);\n padding: 6px 10px !important;\n}\n\n.test-name {\n display: block;\n white-space: nowrap;\n font-weight: 500;\n color: var(--test-text);\n font-size: 12px;\n}\n\n.result-cell {\n position: relative;\n min-width: 120px;\n width: 120px;\n transition: var(--test-transition);\n}\n\n.result-cell.clickable {\n cursor: pointer;\n}\n\n.result-cell.clickable:hover {\n transform: scale(1.1);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n z-index: 5;\n}\n\n.result-cell i {\n font-size: 14px;\n}\n\n/* Completed cells have neutral white background - pills tell the story */\n.result-cell.cell-passed,\n.result-cell.cell-failed,\n.result-cell.cell-error,\n.result-cell.cell-timeout,\n.result-cell.cell-running,\n.result-cell.cell-pending {\n background: #ffffff;\n color: var(--test-text);\n}\n\n/* Skipped/not-run cells get hatched background */\n.result-cell.cell-skipped {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: var(--test-disabled);\n}\n\n.result-cell.cell-none {\n background: #f8fafc;\n color: var(--test-text-muted);\n}\n\n.cell-score {\n display: block;\n font-size: 10px;\n font-weight: 600;\n margin-top: 2px;\n}\n\n/* Matrix cell evaluation stack - shows multiple eval types horizontally */\n.cell-eval-stack {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-wrap: nowrap;\n}\n\n.result-cell.multi-eval {\n min-width: 120px;\n width: auto;\n}\n\n/* Add timeout status icon */\n.cell-status.status-timeout {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-status.status-failed {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-status.status-error {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-status.status-skipped {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running {\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-status.status-pending {\n background: #f3e8ff;\n color: #7c3aed;\n}\n\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: #fef3c7;\n color: #d97706;\n font-size: 10px;\n}\n\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 600;\n background: #dbeafe;\n color: #1d4ed8;\n}\n\n.cell-auto.high {\n background: #dcfce7;\n color: #166534;\n}\n\n.cell-auto.medium {\n background: #fef3c7;\n color: #92400e;\n}\n\n.cell-auto.low {\n background: #fee2e2;\n color: #991b1b;\n}\n\n.cell-none-indicator {\n color: var(--test-text-muted);\n opacity: 0.5;\n}\n\n.matrix-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.matrix-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Chart View\n =========================== */\n.chart-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.chart-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.chart-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.chart-header h3 i {\n color: var(--test-primary);\n}\n\n.chart-legend {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.chart-legend .legend-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.chart-legend .legend-item.chart-passed {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.chart-legend .legend-item.chart-failed {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.chart-legend .legend-item.chart-error {\n background: #fffbeb;\n color: var(--test-warning);\n}\n\n.chart-legend .legend-item.chart-skipped {\n background: #f1f5f9;\n color: var(--test-disabled);\n}\n\n.chart-container {\n min-height: 500px;\n position: relative;\n overflow: hidden;\n background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.d3-chart {\n width: 100%;\n height: 500px;\n}\n\n.d3-chart svg {\n width: 100%;\n height: 100%;\n}\n\n/* D3 Chart Node Styles */\n.d3-chart .node {\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.d3-chart .node:hover {\n transform: scale(1.05);\n}\n\n.d3-chart .node-label {\n font-size: 11px;\n font-weight: 500;\n fill: var(--test-text);\n pointer-events: none;\n}\n\n.d3-chart .link {\n fill: none;\n stroke-opacity: 0.4;\n transition: stroke-opacity 0.2s ease;\n}\n\n.d3-chart .link:hover {\n stroke-opacity: 0.8;\n}\n\n.d3-chart .tooltip {\n position: absolute;\n padding: 10px 14px;\n background: rgba(30, 41, 59, 0.95);\n color: white;\n border-radius: 8px;\n font-size: 12px;\n pointer-events: none;\n z-index: 100;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n max-width: 250px;\n}\n\n.d3-chart .tooltip-title {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.d3-chart .tooltip-value {\n opacity: 0.8;\n}\n\n.chart-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n margin-top: 16px;\n}\n\n.chart-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Compare Tab\n =========================== */\n.compare-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-selection {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.compare-run-selector {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-run-selector h4 {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-selector-list {\n max-height: 200px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.run-selector-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.run-selector-item:hover {\n border-color: var(--test-primary);\n background: rgba(37, 99, 235, 0.05);\n}\n\n.run-selector-item.selected {\n border-color: var(--test-primary);\n background: #eff6ff;\n}\n\n.run-selector-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.selector-status {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.selector-content {\n flex: 1;\n min-width: 0;\n}\n\n.selector-date {\n font-size: 12px;\n font-weight: 500;\n color: var(--test-text);\n}\n\n.selector-rate {\n font-size: 11px;\n color: var(--test-text-secondary);\n}\n\n.selector-tags {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.selected-run-preview {\n padding: 12px;\n background: #f8fafc;\n border-radius: var(--test-radius-sm);\n border: 1px solid var(--test-border);\n}\n\n.preview-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.preview-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n}\n\n.clear-btn {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: var(--test-error);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.clear-btn:hover { text-decoration: underline; }\n\n.preview-details {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--test-text);\n}\n\n.preview-rate {\n font-weight: 600;\n color: var(--test-success);\n}\n\n.compare-vs {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--test-bg);\n border-radius: 50%;\n color: var(--test-text-muted);\n font-size: 16px;\n align-self: center;\n margin-top: 60px;\n}\n\n/* Compare Results */\n.compare-results {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-summary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.compare-summary-card {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n text-align: center;\n}\n\n.compare-summary-card .summary-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n margin-bottom: 8px;\n}\n\n.compare-summary-card .summary-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n\n.compare-summary-card .summary-value.positive { color: var(--test-success); }\n.compare-summary-card .summary-value.negative { color: var(--test-error); }\n\n.compare-summary-card.improved {\n border-left: 4px solid var(--test-success);\n}\n\n.compare-summary-card.improved .summary-value { color: var(--test-success); }\n\n.compare-summary-card.regressed {\n border-left: 4px solid var(--test-error);\n}\n\n.compare-summary-card.regressed .summary-value { color: var(--test-error); }\n\n/* Compare Table */\n.compare-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.compare-table-section h3 i {\n color: var(--test-primary);\n}\n\n.compare-table-wrapper {\n overflow-x: auto;\n}\n\n.compare-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.compare-table th {\n text-align: left;\n padding: 12px 16px;\n background: #f8fafc;\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.compare-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.compare-table tbody tr.improved {\n background: rgba(16, 185, 129, 0.05);\n}\n\n.compare-table tbody tr.regressed {\n background: rgba(239, 68, 68, 0.05);\n}\n\n.test-name-cell {\n font-weight: 500;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.positive { color: var(--test-success); }\n.negative { color: var(--test-error); }\n.muted { color: var(--test-text-muted); }\n\n.change-indicator {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.change-indicator.improved {\n background: #ecfdf5;\n color: var(--test-success);\n}\n\n.change-indicator.regressed {\n background: #fef2f2;\n color: var(--test-error);\n}\n\n.change-indicator.unchanged {\n background: #f1f5f9;\n color: var(--test-text-muted);\n}\n\n/* Compare Empty State */\n.compare-empty {\n text-align: center;\n padding: 60px 24px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n margin-top: 24px;\n}\n\n.compare-empty-icon {\n width: 80px;\n height: 80px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n margin: 0 auto 20px;\n}\n\n.compare-empty-icon i {\n font-size: 32px;\n color: var(--test-text-muted);\n}\n\n.compare-empty h4 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.compare-empty p {\n margin: 0;\n font-size: 14px;\n color: var(--test-text-secondary);\n max-width: 400px;\n margin: 0 auto;\n}\n\n/* Small empty state variant */\n.empty-state.small {\n padding: 32px 16px;\n}\n\n.empty-state.small p {\n margin: 0;\n}\n\n/* ===========================\n Responsive Analytics/Compare\n =========================== */\n@media (max-width: 1024px) {\n .compare-selection {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n\n .compare-vs {\n margin: 0;\n align-self: center;\n justify-self: center;\n }\n\n .analytics-kpis {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 768px) {\n .filter-buttons {\n flex-direction: column;\n }\n\n .filter-btn {\n width: 100%;\n text-align: center;\n }\n\n .analytics-kpis {\n grid-template-columns: 1fr;\n }\n\n .kpi-card {\n padding: 16px;\n }\n\n .kpi-value {\n font-size: 20px;\n }\n\n .compare-summary {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .analytics-table-wrapper,\n .compare-table-wrapper {\n margin: 0 -20px;\n padding: 0 20px;\n }\n\n .run-selector-list {\n max-height: 150px;\n }\n}\n\n@media (max-width: 480px) {\n .compare-summary {\n grid-template-columns: 1fr;\n }\n\n .compare-summary-card .summary-value {\n font-size: 20px;\n }\n\n .analytics-table th,\n .analytics-table td,\n .compare-table th,\n .compare-table td {\n padding: 10px 12px;\n font-size: 12px;\n }\n}\n\n/* ===========================\n Matrix Evaluation Indicators\n =========================== */\n\n/* Human Feedback Indicators */\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-human.no-feedback {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-human.no-feedback i {\n font-size: 11px;\n}\n\n.cell-human.has-feedback {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-human.has-feedback i {\n font-size: 9px;\n}\n\n.cell-human.has-feedback .rating-value {\n font-weight: 700;\n font-size: 11px;\n}\n\n/* Human rating color coding: red \u22644, yellow 5-6, light-green 7-8, green 9-10 */\n.cell-human.rating-low {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-human.rating-medium {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-human.rating-good {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-human.rating-excellent {\n background: #dcfce7;\n color: #16a34a;\n}\n\n/* Auto Score Indicators */\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-auto.no-score {\n background: #f1f5f9;\n color: #94a3b8;\n}\n\n.cell-auto.no-score i {\n font-size: 11px;\n}\n\n.cell-auto.has-score {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-auto.has-score i {\n font-size: 9px;\n}\n\n.cell-auto.has-score .score-value {\n font-weight: 700;\n font-size: 10px;\n}\n\n/* Auto score color coding (0-100%): red <50, yellow 50-69, light-green 70-84, green 85+ */\n.cell-auto.score-low {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-auto.score-medium {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-auto.score-good {\n background: #d1fae5;\n color: #059669;\n}\n\n.cell-auto.score-excellent {\n background: #dcfce7;\n color: #16a34a;\n}\n\n/* Status indicators in matrix cells */\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: #dcfce7;\n color: #16a34a;\n}\n\n.cell-status.status-failed {\n background: #fee2e2;\n color: #dc2626;\n}\n\n.cell-status.status-error {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-timeout {\n background: #fef3c7;\n color: #d97706;\n}\n\n.cell-status.status-skipped,\n.cell-status.status-pending {\n background: #f1f5f9;\n color: #64748b;\n}\n\n.cell-status.status-running {\n background: #dbeafe;\n color: #2563eb;\n}\n\n/* Not-run / Skipped cells with hatch pattern */\n.result-cell.cell-not-run {\n background: repeating-linear-gradient(\n 45deg,\n #f8fafc,\n #f8fafc 4px,\n #e2e8f0 4px,\n #e2e8f0 8px\n );\n color: #94a3b8;\n}\n\n.result-cell.cell-not-run .cell-eval-stack {\n opacity: 0.6;\n}\n\n.cell-not-run-indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n color: #94a3b8;\n font-size: 11px;\n}\n\n/* ===========================\n Matrix Totals Footer Row\n =========================== */\n.test-matrix tfoot {\n position: sticky;\n bottom: 0;\n z-index: 2;\n}\n\n.totals-row {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n border-top: 2px solid var(--test-border);\n}\n\n.totals-row td {\n padding: 10px 12px;\n font-weight: 600;\n}\n\n.totals-row .totals-label {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n font-size: 12px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.totals-row .totals-cell {\n background: linear-gradient(to bottom, #f8fafc, #f1f5f9);\n}\n\n.totals-stack {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.totals-status,\n.totals-human,\n.totals-auto {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 10px;\n white-space: nowrap;\n}\n\n.totals-status {\n background: #e0f2fe;\n color: #0369a1;\n}\n\n.totals-status .pass-count {\n font-weight: 700;\n}\n\n.totals-human {\n background: #fef3c7;\n color: #92400e;\n}\n\n.totals-human .avg-label {\n font-weight: 700;\n}\n\n.totals-human .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n\n.totals-auto {\n background: #dbeafe;\n color: #1e40af;\n}\n\n.totals-auto .avg-label {\n font-weight: 700;\n}\n\n.totals-auto .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n"] }]
|
|
3458
|
+
}], () => [{ type: i0.ElementRef }, { type: i1.SharedService }, { type: i2.Router }, { type: i2.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: i3.TestingDialogService }, { type: i3.EvaluationPreferencesService }, { type: i0.ViewContainerRef }], { chartContainer: [{
|
|
3459
|
+
type: ViewChild,
|
|
3460
|
+
args: ['chartContainer']
|
|
3461
|
+
}], handleKeyboardShortcut: [{
|
|
3462
|
+
type: HostListener,
|
|
3463
|
+
args: ['document:keydown', ['$event']]
|
|
3464
|
+
}] }); })();
|
|
3465
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestSuiteFormComponentExtended, { className: "TestSuiteFormComponentExtended", filePath: "src/lib/custom/Tests/test-suite-form.component.ts", lineNumber: 30 }); })();
|
|
358
3466
|
export function LoadTestSuiteFormComponentExtended() { }
|
|
359
3467
|
LoadTestSuiteFormComponentExtended();
|
|
360
3468
|
//# sourceMappingURL=test-suite-form.component.js.map
|