decision_agent 0.1.4 → 0.1.6
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.
- checksums.yaml +4 -4
- data/README.md +84 -233
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +46 -10
- data/lib/decision_agent/agent.rb +5 -3
- data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
- data/lib/decision_agent/auth/authenticator.rb +127 -0
- data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
- data/lib/decision_agent/auth/password_reset_token.rb +33 -0
- data/lib/decision_agent/auth/permission.rb +29 -0
- data/lib/decision_agent/auth/permission_checker.rb +43 -0
- data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
- data/lib/decision_agent/auth/rbac_config.rb +51 -0
- data/lib/decision_agent/auth/role.rb +56 -0
- data/lib/decision_agent/auth/session.rb +33 -0
- data/lib/decision_agent/auth/session_manager.rb +57 -0
- data/lib/decision_agent/auth/user.rb +70 -0
- data/lib/decision_agent/context.rb +24 -4
- data/lib/decision_agent/decision.rb +10 -3
- data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
- data/lib/decision_agent/dsl/schema_validator.rb +8 -1
- data/lib/decision_agent/errors.rb +38 -0
- data/lib/decision_agent/evaluation.rb +10 -3
- data/lib/decision_agent/evaluation_validator.rb +8 -13
- data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
- data/lib/decision_agent/monitoring/metrics_collector.rb +17 -5
- data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
- data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
- data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
- data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
- data/lib/decision_agent/testing/test_scenario.rb +42 -0
- data/lib/decision_agent/version.rb +10 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
- data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
- data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
- data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
- data/lib/decision_agent/web/public/app.js +184 -29
- data/lib/decision_agent/web/public/batch_testing.html +640 -0
- data/lib/decision_agent/web/public/index.html +37 -9
- data/lib/decision_agent/web/public/login.html +298 -0
- data/lib/decision_agent/web/public/users.html +679 -0
- data/lib/decision_agent/web/server.rb +873 -7
- data/lib/decision_agent.rb +52 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
- data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
- data/spec/ab_testing/ab_test_manager_spec.rb +282 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
- data/spec/ab_testing/storage/adapter_spec.rb +64 -0
- data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
- data/spec/advanced_operators_spec.rb +1003 -0
- data/spec/agent_spec.rb +40 -0
- data/spec/audit_adapters_spec.rb +18 -0
- data/spec/auth/access_audit_logger_spec.rb +394 -0
- data/spec/auth/authenticator_spec.rb +112 -0
- data/spec/auth/password_reset_spec.rb +294 -0
- data/spec/auth/permission_checker_spec.rb +207 -0
- data/spec/auth/permission_spec.rb +73 -0
- data/spec/auth/rbac_adapter_spec.rb +550 -0
- data/spec/auth/rbac_config_spec.rb +82 -0
- data/spec/auth/role_spec.rb +51 -0
- data/spec/auth/session_manager_spec.rb +172 -0
- data/spec/auth/session_spec.rb +112 -0
- data/spec/auth/user_spec.rb +130 -0
- data/spec/context_spec.rb +43 -0
- data/spec/decision_agent_spec.rb +96 -0
- data/spec/decision_spec.rb +423 -0
- data/spec/dsl/condition_evaluator_spec.rb +774 -0
- data/spec/evaluation_spec.rb +364 -0
- data/spec/evaluation_validator_spec.rb +165 -0
- data/spec/examples.txt +1542 -612
- data/spec/monitoring/metrics_collector_spec.rb +220 -2
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +153 -1
- data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
- data/spec/performance_optimizations_spec.rb +486 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/testing/batch_test_importer_spec.rb +693 -0
- data/spec/testing/batch_test_runner_spec.rb +307 -0
- data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
- data/spec/testing/test_result_comparator_spec.rb +392 -0
- data/spec/testing/test_scenario_spec.rb +113 -0
- data/spec/versioning/adapter_spec.rb +156 -0
- data/spec/versioning_spec.rb +253 -0
- data/spec/web/middleware/auth_middleware_spec.rb +133 -0
- data/spec/web/middleware/permission_middleware_spec.rb +247 -0
- data/spec/web_ui_rack_spec.rb +1705 -0
- metadata +99 -6
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>DecisionAgent Batch Testing</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
<style>
|
|
9
|
+
.batch-testing-container {
|
|
10
|
+
max-width: 1400px;
|
|
11
|
+
margin: 0 auto;
|
|
12
|
+
padding: 20px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.step-section {
|
|
16
|
+
background: var(--panel-bg);
|
|
17
|
+
border-radius: 10px;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
margin-bottom: 20px;
|
|
20
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.step-header {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
margin-bottom: 15px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.step-number {
|
|
30
|
+
background: var(--primary-color);
|
|
31
|
+
color: white;
|
|
32
|
+
width: 30px;
|
|
33
|
+
height: 30px;
|
|
34
|
+
border-radius: 50%;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
font-weight: bold;
|
|
39
|
+
margin-right: 10px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.file-upload-area {
|
|
43
|
+
border: 2px dashed var(--border-color);
|
|
44
|
+
border-radius: 8px;
|
|
45
|
+
padding: 40px;
|
|
46
|
+
text-align: center;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
transition: all 0.3s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.file-upload-area:hover {
|
|
52
|
+
border-color: var(--primary-color);
|
|
53
|
+
background: var(--hover-bg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.file-upload-area.dragover {
|
|
57
|
+
border-color: var(--primary-color);
|
|
58
|
+
background: var(--hover-bg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.results-table {
|
|
62
|
+
width: 100%;
|
|
63
|
+
border-collapse: collapse;
|
|
64
|
+
margin-top: 15px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.results-table th,
|
|
68
|
+
.results-table td {
|
|
69
|
+
padding: 10px;
|
|
70
|
+
text-align: left;
|
|
71
|
+
border-bottom: 1px solid var(--border-color);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.results-table th {
|
|
75
|
+
background: var(--hover-bg);
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.status-badge {
|
|
80
|
+
display: inline-block;
|
|
81
|
+
padding: 4px 12px;
|
|
82
|
+
border-radius: 12px;
|
|
83
|
+
font-size: 0.85rem;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.status-imported {
|
|
88
|
+
background: #dbeafe;
|
|
89
|
+
color: #1e40af;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.status-running {
|
|
93
|
+
background: #fef3c7;
|
|
94
|
+
color: #92400e;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.status-completed {
|
|
98
|
+
background: #d1fae5;
|
|
99
|
+
color: #065f46;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.status-failed {
|
|
103
|
+
background: #fee2e2;
|
|
104
|
+
color: #991b1b;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.progress-bar {
|
|
108
|
+
width: 100%;
|
|
109
|
+
height: 20px;
|
|
110
|
+
background: var(--border-color);
|
|
111
|
+
border-radius: 10px;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
margin-top: 10px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.progress-fill {
|
|
117
|
+
height: 100%;
|
|
118
|
+
background: var(--primary-color);
|
|
119
|
+
transition: width 0.3s;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.metrics-grid {
|
|
123
|
+
display: grid;
|
|
124
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
125
|
+
gap: 15px;
|
|
126
|
+
margin-top: 15px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.metric-card {
|
|
130
|
+
background: var(--hover-bg);
|
|
131
|
+
padding: 15px;
|
|
132
|
+
border-radius: 8px;
|
|
133
|
+
text-align: center;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.metric-value {
|
|
137
|
+
font-size: 2rem;
|
|
138
|
+
font-weight: bold;
|
|
139
|
+
color: var(--primary-color);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.metric-label {
|
|
143
|
+
font-size: 0.9rem;
|
|
144
|
+
color: var(--text-secondary);
|
|
145
|
+
margin-top: 5px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.hidden {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.error-message {
|
|
153
|
+
background: #fee2e2;
|
|
154
|
+
color: #991b1b;
|
|
155
|
+
padding: 12px;
|
|
156
|
+
border-radius: 6px;
|
|
157
|
+
margin-top: 10px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.success-message {
|
|
161
|
+
background: #d1fae5;
|
|
162
|
+
color: #065f46;
|
|
163
|
+
padding: 12px;
|
|
164
|
+
border-radius: 6px;
|
|
165
|
+
margin-top: 10px;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<div class="batch-testing-container">
|
|
171
|
+
<header>
|
|
172
|
+
<h1>🧪 DecisionAgent Batch Testing</h1>
|
|
173
|
+
<p class="subtitle">Test your decision rules against multiple scenarios</p>
|
|
174
|
+
</header>
|
|
175
|
+
|
|
176
|
+
<!-- Step 1: Upload Test Scenarios -->
|
|
177
|
+
<div class="step-section">
|
|
178
|
+
<div class="step-header">
|
|
179
|
+
<div class="step-number">1</div>
|
|
180
|
+
<h2>Upload Test Scenarios</h2>
|
|
181
|
+
</div>
|
|
182
|
+
<p>Upload a CSV or Excel file with your test scenarios. Required columns: <code>id</code>, context fields. Optional: <code>expected_decision</code>, <code>expected_confidence</code></p>
|
|
183
|
+
|
|
184
|
+
<div class="file-upload-area" id="uploadArea">
|
|
185
|
+
<p>📁 Drag and drop your CSV/Excel file here, or click to browse</p>
|
|
186
|
+
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls" style="display: none;">
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div id="uploadStatus" class="hidden"></div>
|
|
190
|
+
<div id="uploadProgress" class="hidden">
|
|
191
|
+
<div class="progress-bar">
|
|
192
|
+
<div class="progress-fill" id="progressFill" style="width: 0%"></div>
|
|
193
|
+
</div>
|
|
194
|
+
<p id="progressText" style="text-align: center; margin-top: 10px;">0%</p>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Step 2: Configure Rules -->
|
|
199
|
+
<div class="step-section">
|
|
200
|
+
<div class="step-header">
|
|
201
|
+
<div class="step-number">2</div>
|
|
202
|
+
<h2>Configure Rules</h2>
|
|
203
|
+
</div>
|
|
204
|
+
<p>Paste your rules JSON or import from file</p>
|
|
205
|
+
|
|
206
|
+
<div class="form-group">
|
|
207
|
+
<label for="rulesJson">Rules JSON:</label>
|
|
208
|
+
<textarea id="rulesJson" class="input" rows="10" placeholder='{"version": "1.0", "ruleset": "my_ruleset", "rules": [...]}'></textarea>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="actions">
|
|
212
|
+
<label for="importRulesFile" class="btn btn-secondary">📁 Import Rules JSON
|
|
213
|
+
<input type="file" id="importRulesFile" accept=".json" style="display: none;">
|
|
214
|
+
</label>
|
|
215
|
+
<button class="btn btn-secondary" id="validateRulesBtn">✓ Validate Rules</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<!-- Step 3: Run Tests -->
|
|
220
|
+
<div class="step-section">
|
|
221
|
+
<div class="step-header">
|
|
222
|
+
<div class="step-number">3</div>
|
|
223
|
+
<h2>Run Batch Test</h2>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div class="form-group">
|
|
227
|
+
<label>
|
|
228
|
+
<input type="checkbox" id="parallelExecution" checked> Parallel execution
|
|
229
|
+
</label>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="form-group">
|
|
232
|
+
<label for="threadCount">Thread count:</label>
|
|
233
|
+
<input type="number" id="threadCount" value="4" min="1" max="16" class="input" style="width: 100px;">
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<button class="btn btn-primary" id="runTestBtn" disabled>▶ Run Batch Test</button>
|
|
237
|
+
<button class="btn btn-secondary" id="resumeTestBtn" disabled>▶ Resume Test</button>
|
|
238
|
+
|
|
239
|
+
<div id="runStatus" class="hidden"></div>
|
|
240
|
+
<div id="runProgress" class="hidden">
|
|
241
|
+
<div class="progress-bar">
|
|
242
|
+
<div class="progress-fill" id="runProgressFill" style="width: 0%"></div>
|
|
243
|
+
</div>
|
|
244
|
+
<p id="runProgressText" style="text-align: center; margin-top: 10px;">0%</p>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<!-- Step 4: Results -->
|
|
249
|
+
<div class="step-section" id="resultsSection" style="display: none;">
|
|
250
|
+
<div class="step-header">
|
|
251
|
+
<div class="step-number">4</div>
|
|
252
|
+
<h2>Test Results</h2>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div id="testInfo">
|
|
256
|
+
<p><strong>Test ID:</strong> <span id="testId"></span></p>
|
|
257
|
+
<p><strong>Status:</strong> <span id="testStatus" class="status-badge"></span></p>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<!-- Statistics -->
|
|
261
|
+
<div id="statisticsSection" class="hidden">
|
|
262
|
+
<h3>Statistics</h3>
|
|
263
|
+
<div class="metrics-grid" id="statisticsGrid"></div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<!-- Comparison Results -->
|
|
267
|
+
<div id="comparisonSection" class="hidden">
|
|
268
|
+
<h3>Comparison Results</h3>
|
|
269
|
+
<div class="metrics-grid" id="comparisonGrid"></div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- Coverage Report -->
|
|
273
|
+
<div id="coverageSection" class="hidden">
|
|
274
|
+
<h3>Coverage Report</h3>
|
|
275
|
+
<div class="metrics-grid" id="coverageGrid"></div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<!-- Results Table -->
|
|
279
|
+
<div id="resultsTableSection" class="hidden">
|
|
280
|
+
<h3>Detailed Results</h3>
|
|
281
|
+
<div style="max-height: 400px; overflow-y: auto;">
|
|
282
|
+
<table class="results-table">
|
|
283
|
+
<thead>
|
|
284
|
+
<tr>
|
|
285
|
+
<th>Scenario ID</th>
|
|
286
|
+
<th>Decision</th>
|
|
287
|
+
<th>Confidence</th>
|
|
288
|
+
<th>Expected Decision</th>
|
|
289
|
+
<th>Expected Confidence</th>
|
|
290
|
+
<th>Match</th>
|
|
291
|
+
<th>Execution Time (ms)</th>
|
|
292
|
+
</tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody id="resultsTableBody"></tbody>
|
|
295
|
+
</table>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<script>
|
|
302
|
+
// Helper function to get the base path for API calls
|
|
303
|
+
function getBasePath() {
|
|
304
|
+
const baseTag = document.querySelector('base');
|
|
305
|
+
if (baseTag && baseTag.href) {
|
|
306
|
+
try {
|
|
307
|
+
const baseUrl = new URL(baseTag.href, window.location.href);
|
|
308
|
+
let path = baseUrl.pathname;
|
|
309
|
+
if (path && !path.endsWith('/')) {
|
|
310
|
+
path += '/';
|
|
311
|
+
}
|
|
312
|
+
return path;
|
|
313
|
+
} catch (e) {
|
|
314
|
+
if (baseTag.href.startsWith('/')) {
|
|
315
|
+
const match = baseTag.href.match(/^(https?:\/\/[^\/]+)?(\/.*?)\/?$/);
|
|
316
|
+
if (match && match[2]) {
|
|
317
|
+
return match[2].endsWith('/') ? match[2] : match[2] + '/';
|
|
318
|
+
}
|
|
319
|
+
} else if (baseTag.href.startsWith('./')) {
|
|
320
|
+
const pathname = window.location.pathname;
|
|
321
|
+
return pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const pathname = window.location.pathname;
|
|
326
|
+
if (pathname.includes('/decision_agent')) {
|
|
327
|
+
const match = pathname.match(/^(\/.*?\/decision_agent)\/?/);
|
|
328
|
+
if (match) {
|
|
329
|
+
return match[1].endsWith('/') ? match[1] : match[1] + '/';
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const dirPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
333
|
+
return dirPath || '/';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const basePath = getBasePath();
|
|
337
|
+
let currentTestId = null;
|
|
338
|
+
let currentScenarios = null;
|
|
339
|
+
|
|
340
|
+
// File upload
|
|
341
|
+
const uploadArea = document.getElementById('uploadArea');
|
|
342
|
+
const fileInput = document.getElementById('fileInput');
|
|
343
|
+
const uploadStatus = document.getElementById('uploadStatus');
|
|
344
|
+
const uploadProgress = document.getElementById('uploadProgress');
|
|
345
|
+
const progressFill = document.getElementById('progressFill');
|
|
346
|
+
const progressText = document.getElementById('progressText');
|
|
347
|
+
|
|
348
|
+
uploadArea.addEventListener('click', () => fileInput.click());
|
|
349
|
+
uploadArea.addEventListener('dragover', (e) => {
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
uploadArea.classList.add('dragover');
|
|
352
|
+
});
|
|
353
|
+
uploadArea.addEventListener('dragleave', () => {
|
|
354
|
+
uploadArea.classList.remove('dragover');
|
|
355
|
+
});
|
|
356
|
+
uploadArea.addEventListener('drop', (e) => {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
uploadArea.classList.remove('dragover');
|
|
359
|
+
const files = e.dataTransfer.files;
|
|
360
|
+
if (files.length > 0) {
|
|
361
|
+
handleFileUpload(files[0]);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
fileInput.addEventListener('change', (e) => {
|
|
365
|
+
if (e.target.files.length > 0) {
|
|
366
|
+
handleFileUpload(e.target.files[0]);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
function handleFileUpload(file) {
|
|
371
|
+
const formData = new FormData();
|
|
372
|
+
formData.append('file', file);
|
|
373
|
+
|
|
374
|
+
uploadStatus.className = 'hidden';
|
|
375
|
+
uploadProgress.classList.remove('hidden');
|
|
376
|
+
progressFill.style.width = '0%';
|
|
377
|
+
progressText.textContent = 'Uploading...';
|
|
378
|
+
|
|
379
|
+
fetch(`${basePath}api/testing/batch/import`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
body: formData
|
|
382
|
+
})
|
|
383
|
+
.then(response => response.json())
|
|
384
|
+
.then(data => {
|
|
385
|
+
if (data.error) {
|
|
386
|
+
uploadStatus.className = 'error-message';
|
|
387
|
+
uploadStatus.textContent = `Error: ${data.error}`;
|
|
388
|
+
uploadProgress.classList.add('hidden');
|
|
389
|
+
} else {
|
|
390
|
+
currentTestId = data.test_id;
|
|
391
|
+
currentScenarios = data.scenarios_count;
|
|
392
|
+
uploadStatus.className = 'success-message';
|
|
393
|
+
uploadStatus.textContent = `Successfully imported ${data.scenarios_count} scenarios. Test ID: ${data.test_id}`;
|
|
394
|
+
uploadProgress.classList.add('hidden');
|
|
395
|
+
document.getElementById('runTestBtn').disabled = false;
|
|
396
|
+
|
|
397
|
+
if (data.errors && data.errors.length > 0) {
|
|
398
|
+
uploadStatus.textContent += `\nWarnings: ${data.errors.join(', ')}`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
.catch(error => {
|
|
403
|
+
uploadStatus.className = 'error-message';
|
|
404
|
+
uploadStatus.textContent = `Error: ${error.message}`;
|
|
405
|
+
uploadProgress.classList.add('hidden');
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Import rules JSON
|
|
410
|
+
document.getElementById('importRulesFile').addEventListener('change', (e) => {
|
|
411
|
+
const file = e.target.files[0];
|
|
412
|
+
if (file) {
|
|
413
|
+
const reader = new FileReader();
|
|
414
|
+
reader.onload = (e) => {
|
|
415
|
+
try {
|
|
416
|
+
const json = JSON.parse(e.target.result);
|
|
417
|
+
document.getElementById('rulesJson').value = JSON.stringify(json, null, 2);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
alert('Invalid JSON file: ' + error.message);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
reader.readAsText(file);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Validate rules
|
|
427
|
+
document.getElementById('validateRulesBtn').addEventListener('click', () => {
|
|
428
|
+
const rulesJson = document.getElementById('rulesJson').value;
|
|
429
|
+
try {
|
|
430
|
+
JSON.parse(rulesJson);
|
|
431
|
+
fetch(`${basePath}api/validate`, {
|
|
432
|
+
method: 'POST',
|
|
433
|
+
headers: { 'Content-Type': 'application/json' },
|
|
434
|
+
body: JSON.stringify(JSON.parse(rulesJson))
|
|
435
|
+
})
|
|
436
|
+
.then(response => response.json())
|
|
437
|
+
.then(data => {
|
|
438
|
+
if (data.valid) {
|
|
439
|
+
alert('Rules are valid!');
|
|
440
|
+
} else {
|
|
441
|
+
alert('Validation errors: ' + data.errors.join(', '));
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
} catch (error) {
|
|
445
|
+
alert('Invalid JSON: ' + error.message);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Run test
|
|
450
|
+
document.getElementById('runTestBtn').addEventListener('click', () => {
|
|
451
|
+
if (!currentTestId) {
|
|
452
|
+
alert('Please upload test scenarios first');
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const rulesJson = document.getElementById('rulesJson').value;
|
|
457
|
+
if (!rulesJson.trim()) {
|
|
458
|
+
alert('Please provide rules JSON');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
JSON.parse(rulesJson);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
alert('Invalid JSON: ' + error.message);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const runStatus = document.getElementById('runStatus');
|
|
470
|
+
const runProgress = document.getElementById('runProgress');
|
|
471
|
+
const runProgressFill = document.getElementById('runProgressFill');
|
|
472
|
+
const runProgressText = document.getElementById('runProgressText');
|
|
473
|
+
|
|
474
|
+
runStatus.className = 'hidden';
|
|
475
|
+
runProgress.classList.remove('hidden');
|
|
476
|
+
runProgressFill.style.width = '0%';
|
|
477
|
+
runProgressText.textContent = 'Starting...';
|
|
478
|
+
|
|
479
|
+
const options = {
|
|
480
|
+
parallel: document.getElementById('parallelExecution').checked,
|
|
481
|
+
thread_count: parseInt(document.getElementById('threadCount').value) || 4
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
fetch(`${basePath}api/testing/batch/run`, {
|
|
485
|
+
method: 'POST',
|
|
486
|
+
headers: { 'Content-Type': 'application/json' },
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
test_id: currentTestId,
|
|
489
|
+
rules: JSON.parse(rulesJson),
|
|
490
|
+
options: options
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
.then(response => response.json())
|
|
494
|
+
.then(data => {
|
|
495
|
+
if (data.error) {
|
|
496
|
+
runStatus.className = 'error-message';
|
|
497
|
+
runStatus.textContent = `Error: ${data.error}`;
|
|
498
|
+
runProgress.classList.add('hidden');
|
|
499
|
+
} else {
|
|
500
|
+
runStatus.className = 'success-message';
|
|
501
|
+
runStatus.textContent = 'Test completed successfully!';
|
|
502
|
+
runProgress.classList.add('hidden');
|
|
503
|
+
loadResults(currentTestId);
|
|
504
|
+
}
|
|
505
|
+
})
|
|
506
|
+
.catch(error => {
|
|
507
|
+
runStatus.className = 'error-message';
|
|
508
|
+
runStatus.textContent = `Error: ${error.message}`;
|
|
509
|
+
runProgress.classList.add('hidden');
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// Load results
|
|
514
|
+
function loadResults(testId) {
|
|
515
|
+
fetch(`${basePath}api/testing/batch/${testId}/results`)
|
|
516
|
+
.then(response => response.json())
|
|
517
|
+
.then(data => {
|
|
518
|
+
if (data.error) {
|
|
519
|
+
alert('Error loading results: ' + data.error);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
document.getElementById('resultsSection').style.display = 'block';
|
|
524
|
+
document.getElementById('testId').textContent = data.test_id;
|
|
525
|
+
document.getElementById('testStatus').textContent = data.status;
|
|
526
|
+
document.getElementById('testStatus').className = 'status-badge status-' + data.status;
|
|
527
|
+
|
|
528
|
+
// Statistics
|
|
529
|
+
if (data.statistics) {
|
|
530
|
+
const statsGrid = document.getElementById('statisticsGrid');
|
|
531
|
+
statsGrid.innerHTML = `
|
|
532
|
+
<div class="metric-card">
|
|
533
|
+
<div class="metric-value">${data.statistics.total}</div>
|
|
534
|
+
<div class="metric-label">Total Tests</div>
|
|
535
|
+
</div>
|
|
536
|
+
<div class="metric-card">
|
|
537
|
+
<div class="metric-value">${data.statistics.successful}</div>
|
|
538
|
+
<div class="metric-label">Successful</div>
|
|
539
|
+
</div>
|
|
540
|
+
<div class="metric-card">
|
|
541
|
+
<div class="metric-value">${data.statistics.failed}</div>
|
|
542
|
+
<div class="metric-label">Failed</div>
|
|
543
|
+
</div>
|
|
544
|
+
<div class="metric-card">
|
|
545
|
+
<div class="metric-value">${(data.statistics.success_rate * 100).toFixed(1)}%</div>
|
|
546
|
+
<div class="metric-label">Success Rate</div>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="metric-card">
|
|
549
|
+
<div class="metric-value">${data.statistics.avg_execution_time_ms.toFixed(2)}ms</div>
|
|
550
|
+
<div class="metric-label">Avg Execution Time</div>
|
|
551
|
+
</div>
|
|
552
|
+
`;
|
|
553
|
+
document.getElementById('statisticsSection').classList.remove('hidden');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Comparison
|
|
557
|
+
if (data.comparison) {
|
|
558
|
+
const compGrid = document.getElementById('comparisonGrid');
|
|
559
|
+
compGrid.innerHTML = `
|
|
560
|
+
<div class="metric-card">
|
|
561
|
+
<div class="metric-value">${(data.comparison.accuracy_rate * 100).toFixed(1)}%</div>
|
|
562
|
+
<div class="metric-label">Accuracy Rate</div>
|
|
563
|
+
</div>
|
|
564
|
+
<div class="metric-card">
|
|
565
|
+
<div class="metric-value">${data.comparison.matches}</div>
|
|
566
|
+
<div class="metric-label">Matches</div>
|
|
567
|
+
</div>
|
|
568
|
+
<div class="metric-card">
|
|
569
|
+
<div class="metric-value">${data.comparison.mismatches}</div>
|
|
570
|
+
<div class="metric-label">Mismatches</div>
|
|
571
|
+
</div>
|
|
572
|
+
`;
|
|
573
|
+
document.getElementById('comparisonSection').classList.remove('hidden');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Results table
|
|
577
|
+
if (data.results && data.results.length > 0) {
|
|
578
|
+
const tbody = document.getElementById('resultsTableBody');
|
|
579
|
+
tbody.innerHTML = '';
|
|
580
|
+
data.results.forEach(result => {
|
|
581
|
+
const row = tbody.insertRow();
|
|
582
|
+
row.insertCell(0).textContent = result.scenario_id;
|
|
583
|
+
row.insertCell(1).textContent = result.decision || 'N/A';
|
|
584
|
+
row.insertCell(2).textContent = result.confidence ? result.confidence.toFixed(3) : 'N/A';
|
|
585
|
+
row.insertCell(3).textContent = 'N/A'; // Expected decision
|
|
586
|
+
row.insertCell(4).textContent = 'N/A'; // Expected confidence
|
|
587
|
+
row.insertCell(5).textContent = result.success ? '✓' : '✗';
|
|
588
|
+
row.insertCell(6).textContent = result.execution_time_ms.toFixed(2);
|
|
589
|
+
});
|
|
590
|
+
document.getElementById('resultsTableSection').classList.remove('hidden');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Load coverage
|
|
594
|
+
loadCoverage(testId);
|
|
595
|
+
})
|
|
596
|
+
.catch(error => {
|
|
597
|
+
alert('Error loading results: ' + error.message);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Load coverage
|
|
602
|
+
function loadCoverage(testId) {
|
|
603
|
+
fetch(`${basePath}api/testing/batch/${testId}/coverage`)
|
|
604
|
+
.then(response => response.json())
|
|
605
|
+
.then(data => {
|
|
606
|
+
if (data.error) {
|
|
607
|
+
return; // Coverage not available
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (data.coverage) {
|
|
611
|
+
const covGrid = document.getElementById('coverageGrid');
|
|
612
|
+
covGrid.innerHTML = `
|
|
613
|
+
<div class="metric-card">
|
|
614
|
+
<div class="metric-value">${(data.coverage.coverage_percentage * 100).toFixed(1)}%</div>
|
|
615
|
+
<div class="metric-label">Coverage</div>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="metric-card">
|
|
618
|
+
<div class="metric-value">${data.coverage.covered_rules}</div>
|
|
619
|
+
<div class="metric-label">Covered Rules</div>
|
|
620
|
+
</div>
|
|
621
|
+
<div class="metric-card">
|
|
622
|
+
<div class="metric-value">${data.coverage.total_rules}</div>
|
|
623
|
+
<div class="metric-label">Total Rules</div>
|
|
624
|
+
</div>
|
|
625
|
+
<div class="metric-card">
|
|
626
|
+
<div class="metric-value">${data.coverage.untested_rules.length}</div>
|
|
627
|
+
<div class="metric-label">Untested Rules</div>
|
|
628
|
+
</div>
|
|
629
|
+
`;
|
|
630
|
+
document.getElementById('coverageSection').classList.remove('hidden');
|
|
631
|
+
}
|
|
632
|
+
})
|
|
633
|
+
.catch(() => {
|
|
634
|
+
// Coverage not available, ignore
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
</script>
|
|
638
|
+
</body>
|
|
639
|
+
</html>
|
|
640
|
+
|