Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0d7ca66
refactor: update get-usage response
ben-fornefeld Oct 23, 2025
4adc092
feat: redesign usage dashboard with interactive charts
ben-fornefeld Oct 24, 2025
b236248
feat: refactor usage charts to use time series data model
ben-fornefeld Oct 24, 2025
a17f363
feat: improve usage chart date ranges and labels
ben-fornefeld Oct 24, 2025
a0fd875
feat: simplify time range picker and improve date handling
ben-fornefeld Oct 24, 2025
f43cec1
feat: add brush selection to usage charts for timeframe control
ben-fornefeld Oct 24, 2025
bbc9f39
refactor: team metrics charts to use brush instead of datazoom as well
ben-fornefeld Oct 24, 2025
336afe4
add: dot on hover
ben-fornefeld Oct 24, 2025
811c8ff
chore: clean-up
ben-fornefeld Oct 24, 2025
9348ba4
add: quick range navigation
ben-fornefeld Oct 24, 2025
e24a295
feat: improve usage charts layout and time range controls
ben-fornefeld Oct 24, 2025
d7b4b94
feat: improve usage dashboard layout and responsiveness
ben-fornefeld Oct 24, 2025
c05a21d
wip: bar charts
ben-fornefeld Oct 24, 2025
ba86913
feat: enhance usage dashboard charts and layout
ben-fornefeld Oct 25, 2025
226f335
feat: add preset time ranges to usage controls
ben-fornefeld Oct 25, 2025
50ae0ab
feat: enhance time range picker with form validation
ben-fornefeld Oct 25, 2025
833c55a
fix: external time change detection in time-range-picker
ben-fornefeld Oct 25, 2025
585667d
feat: add zoom controls to usage charts and remove data filling
ben-fornefeld Oct 26, 2025
eced2d0
fix: empty data x axis minmax
ben-fornefeld Oct 26, 2025
fbeeb03
feat: add weekly data aggregation for long time ranges
ben-fornefeld Oct 26, 2025
5577254
refactor: improve chart hover interaction and data aggregation
ben-fornefeld Oct 27, 2025
a86b57c
refactor: centralize chart utilities and improve usage data handling
ben-fornefeld Oct 27, 2025
25ebe33
feat: improve UTC date handling and chart interactions
ben-fornefeld Oct 27, 2025
4663e16
fix: date range formatting
ben-fornefeld Oct 27, 2025
73191be
fix: min max x axis calculation
ben-fornefeld Oct 27, 2025
a07ca19
feat: improve time axis display in compute usage chart
ben-fornefeld Oct 27, 2025
4d7b447
feat: improve chart axis formatting and display
ben-fornefeld Oct 27, 2025
d0a02d4
feat: improve compute usage chart sampling and display
ben-fornefeld Oct 28, 2025
442f529
wip: refactor chart to category and only control data
ben-fornefeld Oct 31, 2025
e748feb
refactor: update chart utilities and improve date formatting
ben-fornefeld Nov 2, 2025
183ab81
refactor: enhance usage data processing and display utilities
ben-fornefeld Nov 3, 2025
11d8751
fix: unit test imports
ben-fornefeld Nov 3, 2025
55e3e09
fix: timeframe state setting on previous and brush
ben-fornefeld Nov 3, 2025
e663ffa
Update src/features/dashboard/usage/usage-time-range-controls.tsx
ben-fornefeld Nov 3, 2025
6405e95
fix: week start/end calc inconsistency
ben-fornefeld Nov 3, 2025
4b10e72
Merge branch 'main' into refactor-usage-page-charts-to-be-able-to-sho…
ben-fornefeld Nov 3, 2025
8bb233e
chore: remove unecessary comment
ben-fornefeld Nov 3, 2025
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
33 changes: 23 additions & 10 deletions src/__test__/unit/chart-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {
calculateYAxisMax,
transformMetrics,
} from '@/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/utils'
import { transformMetrics } from '@/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/utils'
import { calculateAxisMax } from '@/lib/utils/chart'
import { ClientTeamMetric } from '@/types/sandboxes.types'
import { describe, expect, it } from 'vitest'

Expand All @@ -14,29 +12,44 @@ describe('team-metrics-chart-utils', () => {
{ x: 3, y: 60 },
]
// max = 100, scale = 1.25 → 125 → snap to 150
expect(calculateYAxisMax(data, 1.25)).toBe(150)
expect(
calculateAxisMax(
data.map((d) => d.y),
1.25
)
).toBe(150)
})

it('should use custom scale factor', () => {
const data = [{ x: 1, y: 100 }]
// max = 100, scale = 1.5 → 150
expect(calculateYAxisMax(data, 1.5)).toBe(150)
expect(
calculateAxisMax(
data.map((d) => d.y),
1.5
)
).toBe(150)
})

it('should snap to nice values for different ranges', () => {
// small values < 10
expect(calculateYAxisMax([{ x: 1, y: 5 }], 1.5)).toBe(8) // 7.5 → ceil to 8
expect(calculateAxisMax([5], 1.5)).toBe(8) // 7.5 → ceil to 8

// values 10-100
expect(calculateYAxisMax([{ x: 1, y: 50 }], 1.5)).toBe(80) // 75 → snap to 80
expect(calculateAxisMax([50], 1.5)).toBe(80) // 75 → snap to 80

// values 100-1000
expect(calculateYAxisMax([{ x: 1, y: 500 }], 1.5)).toBe(750) // 750 → snap to 750
expect(calculateAxisMax([500], 1.5)).toBe(750) // 750 → snap to 750
})

it('should return default for empty data', () => {
const data: Array<{ x: number; y: number }> = []
expect(calculateYAxisMax(data, 1.25)).toBe(1)
expect(
calculateAxisMax(
data.map((d) => d.y),
1.25
)
).toBe(1)
})
})

Expand Down
94 changes: 53 additions & 41 deletions src/app/dashboard/[teamIdOrSlug]/usage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { CostCard } from '@/features/dashboard/usage/cost-card'
import { RAMCard } from '@/features/dashboard/usage/ram-card'
import { SandboxesCard } from '@/features/dashboard/usage/sandboxes-card'
import { VCPUCard } from '@/features/dashboard/usage/vcpu-card'
import { UsageChartsProvider } from '@/features/dashboard/usage/usage-charts-context'
import { UsageMetricChart } from '@/features/dashboard/usage/usage-metric-chart'
import { resolveTeamIdInServerComponent } from '@/lib/utils/server'
import { CatchErrorBoundary } from '@/ui/error'
import { getUsage } from '@/server/usage/get-usage'
import ErrorBoundary from '@/ui/error'
import Frame from '@/ui/frame'

export default async function UsagePage({
Expand All @@ -14,44 +13,57 @@ export default async function UsagePage({
const { teamIdOrSlug } = await params
const teamId = await resolveTeamIdInServerComponent(teamIdOrSlug)

return (
<Frame
classNames={{
frame:
'relative grid max-h-full w-full grid-cols-1 self-start lg:grid-cols-12 max-md:border-none',
wrapper: 'w-full max-md:p-0',
}}
>
<SandboxesCard
teamId={teamId}
className="col-span-1 min-h-[360px] border-b lg:col-span-12"
const result = await getUsage({ teamId })

if (!result?.data || result.serverError || result.validationErrors) {
return (
<ErrorBoundary
error={
{
name: 'Usage Error',
message: result?.serverError ?? 'Failed to load usage data',
} satisfies Error
}
description="Could not load usage data"
/>
<UsagePageContent teamId={teamId} />
</Frame>
)
}
)
}

function UsagePageContent({ teamId }: { teamId: string }) {
return (
<CatchErrorBoundary
hideFrame
classNames={{
wrapper: 'col-span-full bg-bg',
errorBoundary: 'mx-auto',
}}
>
<CostCard
teamId={teamId}
className="col-span-1 min-h-[360px] border-b lg:col-span-12"
/>
<VCPUCard
teamId={teamId}
className="col-span-1 min-h-[360px] border-b lg:col-span-12 lg:border-r"
/>
<RAMCard
teamId={teamId}
className="col-span-1 min-h-[360px] border-b lg:col-span-12 lg:border-b-0"
/>
</CatchErrorBoundary>
<UsageChartsProvider data={result.data}>
<div className="flex-1 overflow-y-auto max-h-full min-h-0">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 w-full max-w-[1800px]">
<Frame
classNames={{
wrapper: 'w-full lg:h-[75vh] lg:min-h-[700px]',
frame: 'lg:h-full max-lg:border-0',
}}
>
<div className="grid grid-cols-1 lg:grid-cols-2 lg:grid-rows-2 lg:h-full">
<UsageMetricChart
metric="sandboxes"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="cost"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="hidden lg:flex"
/>
<UsageMetricChart
metric="vcpu"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="ram"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="hidden"
/>
</div>
</Frame>
</div>
</div>
</UsageChartsProvider>
)
}
8 changes: 7 additions & 1 deletion src/configs/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import micromatch from 'micromatch'
export interface DashboardLayoutConfig {
title: string
type: 'default' | 'custom'
custom?: {
includeHeaderBottomStyles: boolean
}
}

const DASHBOARD_LAYOUT_CONFIGS: Record<string, DashboardLayoutConfig> = {
Expand All @@ -24,7 +27,10 @@ const DASHBOARD_LAYOUT_CONFIGS: Record<string, DashboardLayoutConfig> = {
},
'/dashboard/*/usage': {
title: 'Usage',
type: 'default',
type: 'custom',
custom: {
includeHeaderBottomStyles: true,
},
},
'/dashboard/*/members': {
title: 'Members',
Expand Down
5 changes: 3 additions & 2 deletions src/features/dashboard/billing/credits-content.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getUsageThroughReactCache } from '@/server/usage/get-usage'
import { getUsage } from '@/server/usage/get-usage'
import ErrorTooltip from '@/ui/error-tooltip'
import { AlertTriangle } from 'lucide-react'

Expand All @@ -7,9 +7,10 @@ export default async function BillingCreditsContent({
}: {
teamId: string
}) {
const res = await getUsageThroughReactCache({
const res = await getUsage({
teamId,
})

if (!res?.data || res.serverError) {
return (
<ErrorTooltip
Expand Down
3 changes: 3 additions & 0 deletions src/features/dashboard/layout/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default function DashboardLayoutHeader({
'border-b min-h-[var(--height-protected-navbar)+12px] md:min-h-[var(--height-protected-navbar)+24px] max-h-min':
config.type === 'default',
'!pb-0 min-h-protected-navbar max-h-min': config.type === 'custom',
'border-b !pb-3 md:!pb-6':
config.custom?.includeHeaderBottomStyles &&
config.type === 'custom',
},
className
)}
Expand Down
2 changes: 1 addition & 1 deletion src/features/dashboard/layout/wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function DashboardLayoutWrapper({
}

return (
<div className="flex-1 min-h-0 max-h-dvh w-full max-w-full overflow-hidden">
<div className="flex-1 min-h-0 max-h-dvh w-full max-w-full overflow-y-auto md:overflow-hidden">
<CatchErrorBoundary
classNames={{
wrapper: 'h-full w-full',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { ChartType, TeamMetricChartConfig } from './types'

/**
* Static chart configurations by type
* These never change and can be referenced directly
*/
export const CHART_CONFIGS: Record<ChartType, TeamMetricChartConfig> = {
concurrent: {
id: 'concurrent-sandboxes',
Expand All @@ -25,21 +21,4 @@ export const CHART_CONFIGS: Record<ChartType, TeamMetricChartConfig> = {
},
}

// echarts static configuration that never changes
export const STATIC_ECHARTS_CONFIG = {
backgroundColor: 'transparent',
animation: false,
toolbox: {
id: 'toolbox',
show: true,
iconStyle: { opacity: 0 },
showTitle: false,
feature: {
dataZoom: {
yAxisIndex: 'none',
},
},
},
} as const

export const LIVE_PADDING_MULTIPLIER = 1
Loading
Loading