Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,3 @@ bin/
# OS files
.DS_Store
Thumbs.db

# Snapshots and data directories
snapshots/
json-bench-data/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ json-bench/
go run ./runner/main.go -config ./config/mixed.yaml -clients ./config/clients.yaml -historic -storage-config ./config/storage-example.yaml

# View results
open results/report.html
open outputs/report.html
```

**NOTE:** `storage-example.yaml` works out of the box with the docker containers deployed in the compose file.
Expand Down
24 changes: 12 additions & 12 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
"devDependencies": {
"@axe-core/react": "^4.10.2",
"@playwright/test": "^1.53.2",
"@playwright/test": "^1.60.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
Expand Down
26 changes: 26 additions & 0 deletions dashboard/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defineConfig, devices } from '@playwright/test'

/**
* E2E tests assume the full docker stack is up:
* docker compose up -d --build
* and at least one benchmark run exists. The baseline spec seeds and
* cleans up its own data via the API, so re-running is idempotent.
*/
export default defineConfig({
testDir: './tests/e2e',
timeout: 60_000,
fullyParallel: false,
retries: 0,
reporter: [['list']],
use: {
baseURL: process.env.DASHBOARD_URL || 'http://localhost:8080',
extraHTTPHeaders: { Accept: 'application/json' },
trace: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
2 changes: 2 additions & 0 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { lazy } from 'react'

const Dashboard = lazy(() => import('./pages/Dashboard'))
const RunDetails = lazy(() => import('./pages/RunDetails'))
const TestDetail = lazy(() => import('./pages/TestDetail'))
const NotFound = lazy(() => import('./pages/NotFound'))

function App() {
Expand Down Expand Up @@ -45,6 +46,7 @@ function App() {
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/tests/:name" element={<TestDetail />} />
<Route path="/runs/:id" element={<RunDetails />} />
<Route path="*" element={<NotFound />} />
</Routes>
Expand Down
44 changes: 26 additions & 18 deletions dashboard/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import {
HistoricRun,
Baseline,
BaselineComparison,
BenchmarkResult,
TrendData,
TrendPoint,
Expand All @@ -16,8 +18,7 @@ import {
MetricQuery,
TimeSeriesMetric,
MethodMetricsData,
RunDetailsResponse,
ClientMetrics
RunDetailsResponse
} from '../types/api'

/**
Expand Down Expand Up @@ -317,21 +318,19 @@ class BenchmarkAPI {
*
* @returns Promise resolving to array of baseline runs
*/
async listBaselines(): Promise<HistoricRun[]> {
const response = await this.makeRequest<HistoricRun[]>({
async listBaselines(): Promise<Baseline[]> {
const response = await this.makeRequest<{ baselines: Baseline[], count: number }>({
method: 'GET',
url: '/api/baselines'
})
return response.data

return response.data.baselines || []
}

/**
* Sets a run as a baseline with a given name
*
* @param runId - The run ID to set as baseline
* @param name - The baseline name
* @returns Promise that resolves when baseline is set
* Sets a run as a baseline with a given name. The backend accepts both
* camelCase and snake_case for the run id; we send camelCase to match
* the rest of the JS API surface.
*/
async setBaseline(runId: string, name: string): Promise<void> {
await this.makeRequest({
Expand All @@ -342,15 +341,12 @@ class BenchmarkAPI {
}

/**
* Removes a baseline
*
* @param runId - The baseline run ID to remove
* @returns Promise that resolves when baseline is removed
* Removes a baseline by name.
*/
async removeBaseline(runId: string): Promise<void> {
async removeBaseline(baselineName: string): Promise<void> {
await this.makeRequest({
method: 'DELETE',
url: `/api/baselines/${encodeURIComponent(runId)}`
url: `/api/baselines/${encodeURIComponent(baselineName)}`
})
}

Expand All @@ -368,7 +364,19 @@ class BenchmarkAPI {
method: 'GET',
url: `/api/compare?run1=${encodeURIComponent(runId1)}&run2=${encodeURIComponent(runId2)}`
})


return response.data
}

/**
* Compares a run against a saved baseline. The backend returns deltas for
* overall + per-client metrics, plus a roll-up status and risk level.
*/
async compareToBaseline(runId: string, baselineName: string): Promise<BaselineComparison> {
const response = await this.makeRequest<BaselineComparison>({
method: 'GET',
url: `/api/baselines/${encodeURIComponent(baselineName)}/compare/${encodeURIComponent(runId)}`,
})
return response.data
}

Expand Down
21 changes: 18 additions & 3 deletions dashboard/src/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { createBenchmarkAPI, BenchmarkAPIError } from './client'
import type {
Baseline,
HistoricRun,
BenchmarkResult,
TrendData,
Expand Down Expand Up @@ -124,10 +125,10 @@ export function useClientTrends(client: string, days: number, enabled = true) {
}

/**
* Hook to fetch baseline runs
* Hook to fetch baselines
*/
export function useBaselines() {
return useQuery<HistoricRun[], BenchmarkAPIError>({
return useQuery<Baseline[], BenchmarkAPIError>({
queryKey: queryKeys.baselines(),
queryFn: () => api.listBaselines(),
staleTime: 5 * 60 * 1000, // 5 minutes
Expand Down Expand Up @@ -174,6 +175,20 @@ export function useComparison(runId1: string, runId2: string, enabled = true) {
})
}

/**
* Hook to compare a run against a saved baseline. Disabled until both ids set
* so the dropdown can be empty without firing the request.
*/
export function useBaselineComparison(runId: string, baselineName: string, enabled = true) {
return useQuery({
queryKey: ['baseline-comparison', runId, baselineName],
queryFn: () => api.compareToBaseline(runId, baselineName),
enabled: enabled && !!runId && !!baselineName,
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
})
}

/**
* Mutation hook to set a baseline
*/
Expand All @@ -199,7 +214,7 @@ export function useRemoveBaseline() {
const queryClient = useQueryClient()

return useMutation({
mutationFn: (runId: string) => api.removeBaseline(runId),
mutationFn: (baselineName: string) => api.removeBaseline(baselineName),
onSuccess: () => {
// Invalidate and refetch baselines
queryClient.invalidateQueries({ queryKey: queryKeys.baselines() })
Expand Down
Loading