@littlebearapps/platform-admin-sdk 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/dist/check-upgrade.d.ts +29 -0
- package/dist/check-upgrade.js +97 -0
- package/dist/index.js +59 -4
- package/dist/manifest.d.ts +2 -0
- package/dist/scaffold.js +5 -1
- package/dist/templates.d.ts +6 -1
- package/dist/templates.js +141 -3
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +21 -2
- package/package.json +1 -1
- package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
- package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
- package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
- package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
- package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
- package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
- package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
- package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
- package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
- package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
- package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
- package/templates/full/dashboard/src/pages/feedback.astro +365 -0
- package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
- package/templates/full/dashboard/src/pages/map.astro +561 -0
- package/templates/full/dashboard/src/pages/revenue.astro +72 -0
- package/templates/full/dashboard/src/pages/tests.astro +431 -0
- package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
- package/templates/full/scripts/ops/verify-account-total.ts +256 -0
- package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
- package/templates/full/tests/integration/r2-archive.test.ts +108 -0
- package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
- package/templates/shared/.github/workflows/validate-controls.yml +27 -0
- package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
- package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
- package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
- package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
- package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
- package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
- package/templates/shared/dashboard/src/components/Toast.astro +170 -0
- package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
- package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
- package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
- package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
- package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
- package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
- package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
- package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
- package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
- package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
- package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
- package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
- package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
- package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
- package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
- package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
- package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
- package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
- package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
- package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
- package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
- package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
- package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
- package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
- package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
- package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
- package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
- package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
- package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
- package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
- package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
- package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
- package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
- package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
- package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
- package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
- package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
- package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
- package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
- package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
- package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
- package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
- package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
- package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
- package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
- package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
- package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
- package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
- package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
- package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
- package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
- package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
- package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
- package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
- package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
- package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
- package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
- package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
- package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
- package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
- package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
- package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
- package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
- package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
- package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
- package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
- package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
- package/templates/standard/tests/integration/connectors.test.ts +241 -0
- package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
- package/templates/standard/tests/integration/ingestion.test.ts +211 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
---
|
|
2
|
+
import DashboardLayout from '../layouts/DashboardLayout.astro';
|
|
3
|
+
|
|
4
|
+
interface TestRun {
|
|
5
|
+
id: string;
|
|
6
|
+
project: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
duration_ms: number;
|
|
9
|
+
total_tests: number;
|
|
10
|
+
passed_tests: number;
|
|
11
|
+
failed_tests: number;
|
|
12
|
+
skipped_tests: number;
|
|
13
|
+
pass_rate: number;
|
|
14
|
+
branch: string | null;
|
|
15
|
+
commit_sha: string | null;
|
|
16
|
+
commit_message: string | null;
|
|
17
|
+
environment: string;
|
|
18
|
+
trigger: string;
|
|
19
|
+
kv_report_key: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface FlakyTest {
|
|
23
|
+
test_name: string;
|
|
24
|
+
failure_count: number;
|
|
25
|
+
total_count: number;
|
|
26
|
+
failure_rate: number;
|
|
27
|
+
last_failed_at: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Anomaly {
|
|
31
|
+
run_id: string;
|
|
32
|
+
project: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
pass_rate: number;
|
|
35
|
+
baseline_pass_rate: number;
|
|
36
|
+
drop_percentage: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const runtime = (Astro.locals as App.Locals | undefined)?.runtime;
|
|
40
|
+
const db = runtime?.env?.PLATFORM_DB;
|
|
41
|
+
const kv = runtime?.env?.PLATFORM_CACHE;
|
|
42
|
+
|
|
43
|
+
let recentRuns: TestRun[] = [];
|
|
44
|
+
let flakyTests: FlakyTest[] = [];
|
|
45
|
+
let anomalies: Anomaly[] = [];
|
|
46
|
+
let totalRuns = 0;
|
|
47
|
+
let avgPassRate = 0;
|
|
48
|
+
let totalTests = 0;
|
|
49
|
+
let flakyTestCount = 0;
|
|
50
|
+
|
|
51
|
+
if (db && kv) {
|
|
52
|
+
// Get recent test runs
|
|
53
|
+
const cachedRuns = await kv.get('tests:recent:runs');
|
|
54
|
+
if (cachedRuns) {
|
|
55
|
+
recentRuns = JSON.parse(cachedRuns);
|
|
56
|
+
} else {
|
|
57
|
+
const result = await db
|
|
58
|
+
.prepare(
|
|
59
|
+
`SELECT
|
|
60
|
+
id, project, timestamp, duration_ms, total_tests, passed_tests,
|
|
61
|
+
failed_tests, skipped_tests, pass_rate, branch, commit_sha,
|
|
62
|
+
commit_message, environment, trigger, kv_report_key
|
|
63
|
+
FROM runs
|
|
64
|
+
ORDER BY timestamp DESC
|
|
65
|
+
LIMIT 20`
|
|
66
|
+
)
|
|
67
|
+
.all<TestRun>();
|
|
68
|
+
|
|
69
|
+
recentRuns = result.results ?? [];
|
|
70
|
+
|
|
71
|
+
// Cache for 5 minutes
|
|
72
|
+
await kv.put('tests:recent:runs', JSON.stringify(recentRuns), {
|
|
73
|
+
expirationTtl: 300,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get flaky tests (failure rate > 20%)
|
|
78
|
+
const cachedFlaky = await kv.get('tests:flaky:all');
|
|
79
|
+
if (cachedFlaky) {
|
|
80
|
+
flakyTests = JSON.parse(cachedFlaky);
|
|
81
|
+
} else {
|
|
82
|
+
const result = await db
|
|
83
|
+
.prepare(
|
|
84
|
+
`SELECT
|
|
85
|
+
test_name,
|
|
86
|
+
failure_count,
|
|
87
|
+
total_count,
|
|
88
|
+
failure_rate,
|
|
89
|
+
last_failed_at
|
|
90
|
+
FROM test_flakiness
|
|
91
|
+
WHERE is_flaky = 1
|
|
92
|
+
ORDER BY failure_rate DESC
|
|
93
|
+
LIMIT 20`
|
|
94
|
+
)
|
|
95
|
+
.all<FlakyTest>();
|
|
96
|
+
|
|
97
|
+
flakyTests = result.results ?? [];
|
|
98
|
+
|
|
99
|
+
await kv.put('tests:flaky:all', JSON.stringify(flakyTests), {
|
|
100
|
+
expirationTtl: 300,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate anomalies (runs with >10% drop from 7-day baseline)
|
|
105
|
+
// For each recent run, compare to baseline
|
|
106
|
+
const anomaliesTemp: Anomaly[] = [];
|
|
107
|
+
|
|
108
|
+
for (const run of recentRuns.slice(0, 10)) {
|
|
109
|
+
const sevenDaysAgo = new Date(run.timestamp);
|
|
110
|
+
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
111
|
+
|
|
112
|
+
const baseline = await db
|
|
113
|
+
.prepare(
|
|
114
|
+
`SELECT AVG(pass_rate) as avg_pass_rate
|
|
115
|
+
FROM runs
|
|
116
|
+
WHERE project = ?
|
|
117
|
+
AND timestamp >= ?
|
|
118
|
+
AND timestamp < ?
|
|
119
|
+
AND id != ?`
|
|
120
|
+
)
|
|
121
|
+
.bind(run.project, sevenDaysAgo.toISOString(), run.timestamp, run.id)
|
|
122
|
+
.first<{ avg_pass_rate: number | null }>();
|
|
123
|
+
|
|
124
|
+
if (baseline && baseline.avg_pass_rate) {
|
|
125
|
+
const dropPercentage =
|
|
126
|
+
((baseline.avg_pass_rate - run.pass_rate) / baseline.avg_pass_rate) * 100;
|
|
127
|
+
|
|
128
|
+
if (dropPercentage > 10) {
|
|
129
|
+
anomaliesTemp.push({
|
|
130
|
+
run_id: run.id,
|
|
131
|
+
project: run.project,
|
|
132
|
+
timestamp: run.timestamp,
|
|
133
|
+
pass_rate: run.pass_rate,
|
|
134
|
+
baseline_pass_rate: baseline.avg_pass_rate,
|
|
135
|
+
drop_percentage: dropPercentage,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
anomalies = anomaliesTemp;
|
|
142
|
+
|
|
143
|
+
// Get summary statistics
|
|
144
|
+
const stats = await db
|
|
145
|
+
.prepare(
|
|
146
|
+
`SELECT
|
|
147
|
+
COUNT(*) as total_runs,
|
|
148
|
+
AVG(pass_rate) as avg_pass_rate,
|
|
149
|
+
SUM(total_tests) as total_tests
|
|
150
|
+
FROM runs`
|
|
151
|
+
)
|
|
152
|
+
.first<{ total_runs: number; avg_pass_rate: number; total_tests: number }>();
|
|
153
|
+
|
|
154
|
+
totalRuns = stats?.total_runs ?? 0;
|
|
155
|
+
avgPassRate = stats?.avg_pass_rate ?? 0;
|
|
156
|
+
totalTests = stats?.total_tests ?? 0;
|
|
157
|
+
|
|
158
|
+
flakyTestCount = flakyTests.length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatDate(dateString: string): string {
|
|
162
|
+
const date = new Date(dateString);
|
|
163
|
+
const now = new Date();
|
|
164
|
+
const diffMs = now.getTime() - date.getTime();
|
|
165
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
166
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
167
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
168
|
+
|
|
169
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
170
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
171
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
172
|
+
return date.toLocaleDateString();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function formatDuration(ms: number): string {
|
|
176
|
+
if (ms < 1000) return `${ms}ms`;
|
|
177
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
178
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getPassRateBadgeColor(passRate: number): string {
|
|
182
|
+
if (passRate >= 0.95) return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
|
|
183
|
+
if (passRate >= 0.8)
|
|
184
|
+
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
|
|
185
|
+
if (passRate >= 0.6)
|
|
186
|
+
return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300';
|
|
187
|
+
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getEnvironmentBadgeColor(environment: string): string {
|
|
191
|
+
if (environment === 'ci') return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
|
|
192
|
+
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';
|
|
193
|
+
}
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
<DashboardLayout title="Tests">
|
|
197
|
+
<div class="space-y-6">
|
|
198
|
+
<h2 class="text-3xl font-bold text-gray-900 dark:text-white">Test Results</h2>
|
|
199
|
+
|
|
200
|
+
<!-- Summary Cards -->
|
|
201
|
+
<div class="grid grid-cols-1 gap-6 md:grid-cols-4">
|
|
202
|
+
<div class="metric-card">
|
|
203
|
+
<div class="metric-title">Total Runs</div>
|
|
204
|
+
<div class="metric-value">{totalRuns}</div>
|
|
205
|
+
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">All time</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div class="metric-card">
|
|
209
|
+
<div class="metric-title">Average Pass Rate</div>
|
|
210
|
+
<div class="metric-value">{(avgPassRate * 100).toFixed(1)}%</div>
|
|
211
|
+
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">Across all runs</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div class="metric-card">
|
|
215
|
+
<div class="metric-title">Flaky Tests</div>
|
|
216
|
+
<div class="metric-value">{flakyTestCount}</div>
|
|
217
|
+
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">>20% failure rate</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div class="metric-card">
|
|
221
|
+
<div class="metric-title">Anomalies</div>
|
|
222
|
+
<div class="metric-value">{anomalies.length}</div>
|
|
223
|
+
<div class="mt-2 text-sm text-gray-600 dark:text-gray-400">Recent drops</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<!-- Anomaly Alerts (if any) -->
|
|
228
|
+
{
|
|
229
|
+
anomalies.length > 0 && (
|
|
230
|
+
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-6">
|
|
231
|
+
<h3 class="text-xl font-semibold text-red-900 dark:text-red-300 mb-4 flex items-center gap-2">
|
|
232
|
+
<span>🚨</span>
|
|
233
|
+
<span>Anomalies Detected ({anomalies.length})</span>
|
|
234
|
+
</h3>
|
|
235
|
+
|
|
236
|
+
<div class="space-y-3">
|
|
237
|
+
{anomalies.map((anomaly) => (
|
|
238
|
+
<div class="bg-white dark:bg-gray-800 border border-red-300 dark:border-red-700 rounded-lg p-4">
|
|
239
|
+
<div class="flex items-start justify-between">
|
|
240
|
+
<div class="flex-1">
|
|
241
|
+
<div class="flex items-center gap-2 mb-2">
|
|
242
|
+
<span class="font-mono text-sm text-gray-900 dark:text-white">
|
|
243
|
+
{anomaly.project}
|
|
244
|
+
</span>
|
|
245
|
+
<span class="px-2 py-1 rounded text-xs font-bold bg-red-500 text-white">
|
|
246
|
+
-{anomaly.drop_percentage.toFixed(1)}%
|
|
247
|
+
</span>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
|
251
|
+
<div>
|
|
252
|
+
Current:{' '}
|
|
253
|
+
<span class="font-semibold">{(anomaly.pass_rate * 100).toFixed(1)}%</span> |
|
|
254
|
+
Baseline:{' '}
|
|
255
|
+
<span class="font-semibold">
|
|
256
|
+
{(anomaly.baseline_pass_rate * 100).toFixed(1)}%
|
|
257
|
+
</span>
|
|
258
|
+
</div>
|
|
259
|
+
<div>{formatDate(anomaly.timestamp)}</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{recentRuns.find((r) => r.id === anomaly.run_id)?.kv_report_key && (
|
|
263
|
+
<div class="mt-2">
|
|
264
|
+
<a
|
|
265
|
+
href={`/api/test-reports/${anomaly.run_id}`}
|
|
266
|
+
target="_blank"
|
|
267
|
+
rel="noopener noreferrer"
|
|
268
|
+
class="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
|
269
|
+
>
|
|
270
|
+
View HTML Report →
|
|
271
|
+
</a>
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
))}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
<!-- Flaky Tests -->
|
|
284
|
+
{
|
|
285
|
+
flakyTests.length > 0 && (
|
|
286
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
287
|
+
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
|
288
|
+
Flaky Tests ({flakyTests.length})
|
|
289
|
+
</h3>
|
|
290
|
+
|
|
291
|
+
<div class="space-y-3">
|
|
292
|
+
{flakyTests.map((test) => (
|
|
293
|
+
<div class="border border-yellow-200 dark:border-yellow-700 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
|
294
|
+
<div class="flex items-start justify-between">
|
|
295
|
+
<div class="flex-1">
|
|
296
|
+
<div class="flex items-center gap-2 mb-2">
|
|
297
|
+
<span class="px-2 py-1 rounded text-xs font-bold bg-yellow-500 text-white">
|
|
298
|
+
{(test.failure_rate * 100).toFixed(1)}% failure rate
|
|
299
|
+
</span>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="text-sm font-mono text-gray-900 dark:text-white mb-2">
|
|
303
|
+
{test.test_name}
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="text-xs text-gray-600 dark:text-gray-400">
|
|
307
|
+
<span>{test.failure_count} failures</span>
|
|
308
|
+
<span class="mx-2">•</span>
|
|
309
|
+
<span>{test.total_count} total runs</span>
|
|
310
|
+
{test.last_failed_at && (
|
|
311
|
+
<>
|
|
312
|
+
<span class="mx-2">•</span>
|
|
313
|
+
<span>Last failed: {formatDate(test.last_failed_at)}</span>
|
|
314
|
+
</>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
))}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
<!-- Recent Test Runs -->
|
|
327
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
328
|
+
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Recent Test Runs</h3>
|
|
329
|
+
|
|
330
|
+
{
|
|
331
|
+
recentRuns.length === 0 ? (
|
|
332
|
+
<p class="text-gray-600 dark:text-gray-400">No test runs yet.</p>
|
|
333
|
+
) : (
|
|
334
|
+
<div class="overflow-x-auto">
|
|
335
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
336
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
337
|
+
<tr>
|
|
338
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
339
|
+
Project
|
|
340
|
+
</th>
|
|
341
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
342
|
+
Time
|
|
343
|
+
</th>
|
|
344
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
345
|
+
Pass Rate
|
|
346
|
+
</th>
|
|
347
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
348
|
+
Tests
|
|
349
|
+
</th>
|
|
350
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
351
|
+
Duration
|
|
352
|
+
</th>
|
|
353
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
354
|
+
Branch
|
|
355
|
+
</th>
|
|
356
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
357
|
+
Env
|
|
358
|
+
</th>
|
|
359
|
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
|
360
|
+
Report
|
|
361
|
+
</th>
|
|
362
|
+
</tr>
|
|
363
|
+
</thead>
|
|
364
|
+
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
365
|
+
{recentRuns.map((run) => (
|
|
366
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
367
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white">
|
|
368
|
+
{run.project}
|
|
369
|
+
</td>
|
|
370
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
|
371
|
+
{formatDate(run.timestamp)}
|
|
372
|
+
</td>
|
|
373
|
+
<td class="px-4 py-3 whitespace-nowrap">
|
|
374
|
+
<span
|
|
375
|
+
class={`px-2 py-1 rounded text-xs font-medium ${getPassRateBadgeColor(run.pass_rate)}`}
|
|
376
|
+
>
|
|
377
|
+
{(run.pass_rate * 100).toFixed(1)}%
|
|
378
|
+
</span>
|
|
379
|
+
</td>
|
|
380
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
|
381
|
+
<span class="text-green-600 dark:text-green-400">{run.passed_tests}</span>
|
|
382
|
+
<span class="mx-1">/</span>
|
|
383
|
+
<span>{run.total_tests}</span>
|
|
384
|
+
{run.failed_tests > 0 && (
|
|
385
|
+
<span class="ml-2 text-red-600 dark:text-red-400">
|
|
386
|
+
({run.failed_tests} failed)
|
|
387
|
+
</span>
|
|
388
|
+
)}
|
|
389
|
+
</td>
|
|
390
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
|
391
|
+
{formatDuration(run.duration_ms)}
|
|
392
|
+
</td>
|
|
393
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-mono text-gray-600 dark:text-gray-400">
|
|
394
|
+
{run.branch || '-'}
|
|
395
|
+
{run.commit_sha && (
|
|
396
|
+
<span class="ml-1 text-xs text-gray-500">
|
|
397
|
+
({run.commit_sha.substring(0, 7)})
|
|
398
|
+
</span>
|
|
399
|
+
)}
|
|
400
|
+
</td>
|
|
401
|
+
<td class="px-4 py-3 whitespace-nowrap">
|
|
402
|
+
<span
|
|
403
|
+
class={`px-2 py-1 rounded text-xs font-medium ${getEnvironmentBadgeColor(run.environment)}`}
|
|
404
|
+
>
|
|
405
|
+
{run.environment}
|
|
406
|
+
</span>
|
|
407
|
+
</td>
|
|
408
|
+
<td class="px-4 py-3 whitespace-nowrap text-sm">
|
|
409
|
+
{run.kv_report_key ? (
|
|
410
|
+
<a
|
|
411
|
+
href={`/api/test-reports/${run.id}`}
|
|
412
|
+
target="_blank"
|
|
413
|
+
rel="noopener noreferrer"
|
|
414
|
+
class="text-blue-600 dark:text-blue-400 hover:underline"
|
|
415
|
+
>
|
|
416
|
+
View
|
|
417
|
+
</a>
|
|
418
|
+
) : (
|
|
419
|
+
<span class="text-gray-400 dark:text-gray-600">-</span>
|
|
420
|
+
)}
|
|
421
|
+
</td>
|
|
422
|
+
</tr>
|
|
423
|
+
))}
|
|
424
|
+
</tbody>
|
|
425
|
+
</table>
|
|
426
|
+
</div>
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
</DashboardLayout>
|