From 7c34e666c322c2c76dec67e47798dc2fe1bcef3e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:18:01 +0000 Subject: [PATCH 1/4] feat: extend sandboxes monitoring to support up to 90 days - Add 90d preset to TIME_OPTIONS in time-picker constants - Add 90d entry to TIME_RANGES in timeframe utilities - Increase MAX_DAYS_AGO from 31 to 91 days in constants, use-timeframe hook - Update validation error messages to reflect 91-day limit - Update Peak Concurrent Sandboxes header to show 90-day max - Update header metric query to fetch 90 days of data Co-Authored-By: ben@e2b.dev --- src/features/dashboard/sandboxes/monitoring/header.tsx | 5 ++--- .../dashboard/sandboxes/monitoring/hooks/use-timeframe.ts | 2 +- .../sandboxes/monitoring/time-picker/constants.ts | 8 +++++++- .../sandboxes/monitoring/time-picker/time-panel.tsx | 2 +- .../sandboxes/monitoring/time-picker/validation.ts | 6 +++--- src/lib/utils/timeframe.ts | 1 + 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/features/dashboard/sandboxes/monitoring/header.tsx b/src/features/dashboard/sandboxes/monitoring/header.tsx index 2e038efe0..41d7d4140 100644 --- a/src/features/dashboard/sandboxes/monitoring/header.tsx +++ b/src/features/dashboard/sandboxes/monitoring/header.tsx @@ -11,7 +11,6 @@ import { MaxConcurrentSandboxesClient, SandboxesStartRateClient, } from './header.client' -import { MAX_DAYS_AGO } from './time-picker/constants' interface MonitoringContentParams { teamSlug: string @@ -86,7 +85,7 @@ export default function SandboxesMonitoringHeader({ Peak Concurrent Sandboxes
- (30-day max) + (90-day max)
@@ -160,7 +159,7 @@ export const MaxConcurrentSandboxes = async ({ const { teamSlug } = await params const end = Date.now() - const start = end - (MAX_DAYS_AGO - 60_000) // 1 minute margin to avoid validation errors + const start = end - (90 * 24 * 60 * 60 * 1000 - 60_000) // 90 days minus 1 minute margin to avoid validation errors const teamMetricsResult = await getTeamMetricsMax({ teamSlug, diff --git a/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts b/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts index fe66cd2c9..7a39c455b 100644 --- a/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts +++ b/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts @@ -9,7 +9,7 @@ import { import { TIME_RANGES, type TimeRangeKey } from '@/lib/utils/timeframe' import { calculateIsLive } from '../utils' -const MAX_DAYS_AGO = 31 * 24 * 60 * 60 * 1000 +const MAX_DAYS_AGO = 91 * 24 * 60 * 60 * 1000 const MIN_RANGE_MS = 1.5 * 60 * 1000 const getStableNow = () => { diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts b/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts index da73bb297..f8e1b042e 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts @@ -67,10 +67,16 @@ export const TIME_OPTIONS: TimeOption[] = [ shortcut: '30D', rangeMs: 30 * 24 * 60 * 60 * 1000, }, + { + label: `Last 90 days`, + value: '90d', + shortcut: '90D', + rangeMs: 90 * 24 * 60 * 60 * 1000, + }, ] // constraints -export const MAX_DAYS_AGO = 31 * 24 * 60 * 60 * 1000 // 31 days in ms +export const MAX_DAYS_AGO = 91 * 24 * 60 * 60 * 1000 // 91 days in ms export const MIN_RANGE_MS = 1.5 * 60 * 1000 // 1.5 minutes minimum export const CLOCK_SKEW_TOLERANCE = 60 * 1000 // 60 seconds export const DEFAULT_RANGE_MS = 60 * 60 * 1000 // 1 hour default diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx b/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx index 8add8c0d7..963507aac 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx @@ -153,7 +153,7 @@ export const TimePanel = forwardRef( const { minDate, maxDate } = useMemo(() => { const now = new Date() - // create new Date object for minDate (31 days ago) + // create new Date object for minDate (91 days ago) const minDate = new Date(now.getTime() - MAX_DAYS_AGO) minDate.setHours(0, 0, 0, 0) diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts index 89f904448..bea9338e6 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts @@ -39,11 +39,11 @@ export const customTimeFormSchema = z const now = Date.now() const startTimestamp = startDateTime.getTime() - // validate start date is not more than 31 days ago + // validate start date is not more than 91 days ago if (startTimestamp < now - MAX_DAYS_AGO) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Start date cannot be more than 31 days ago', + message: 'Start date cannot be more than 91 days ago', path: ['startDate'], }) return @@ -108,7 +108,7 @@ export const customTimeFormSchema = z if (endTimestamp - startTimestamp > MAX_DAYS_AGO) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Date range cannot exceed 31 days', + message: 'Date range cannot exceed 91 days', path: ['endDate'], }) return diff --git a/src/lib/utils/timeframe.ts b/src/lib/utils/timeframe.ts index fb73ba654..ade35e5d0 100644 --- a/src/lib/utils/timeframe.ts +++ b/src/lib/utils/timeframe.ts @@ -21,6 +21,7 @@ export const TIME_RANGES = { '6h': 1000 * 60 * 60 * 6, '24h': 1000 * 60 * 60 * 24, '30d': 1000 * 60 * 60 * 24 * 30, + '90d': 1000 * 60 * 60 * 24 * 90, } as const export type TimeRangeKey = keyof typeof TIME_RANGES From 8de383e2a8c2a455ca7c18475ebdbd85da3187c7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:26:53 +0000 Subject: [PATCH 2/4] fix: use '90 days' in user-facing error messages and update mock data clamp - Hardcode '90 days' in validation error messages instead of interpolating MAX_DAYS_AGO / 86_400_000 (which yields 91, mismatching the UI) - Update mock data generator to clamp at 90 days instead of 30 days - Affects: validation.ts, get-team-metrics.ts, get-team-metrics-max.ts, schemas.ts, types.ts, mock-data.ts Co-Authored-By: ben@e2b.dev --- src/app/api/teams/[teamSlug]/metrics/types.ts | 4 ++-- src/configs/mock-data.ts | 10 +++++----- src/core/modules/sandboxes/schemas.ts | 4 ++-- .../server/functions/sandboxes/get-team-metrics-max.ts | 4 ++-- .../server/functions/sandboxes/get-team-metrics.ts | 4 ++-- .../sandboxes/monitoring/time-picker/validation.ts | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/app/api/teams/[teamSlug]/metrics/types.ts b/src/app/api/teams/[teamSlug]/metrics/types.ts index d32af3573..740485f58 100644 --- a/src/app/api/teams/[teamSlug]/metrics/types.ts +++ b/src/app/api/teams/[teamSlug]/metrics/types.ts @@ -15,7 +15,7 @@ export const TeamMetricsRequestSchema = z return start >= now - MAX_DAYS_AGO }, { - message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, + message: 'Start date cannot be more than 90 days ago', } ), end: z @@ -32,7 +32,7 @@ export const TeamMetricsRequestSchema = z return data.end - data.start <= MAX_DAYS_AGO }, { - message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, + message: 'Date range cannot exceed 90 days', } ) diff --git a/src/configs/mock-data.ts b/src/configs/mock-data.ts index bd8d40b57..864316a5a 100644 --- a/src/configs/mock-data.ts +++ b/src/configs/mock-data.ts @@ -1082,18 +1082,18 @@ export function calculateTeamMetricsStep( /** * Generate mock team metrics for monitoring charts * Supports small, medium, and large teams with realistic patterns - * Can generate data for the past 30 days from now + * Can generate data for the past 90 days from now */ export function generateMockTeamMetrics( startMs: number, endMs: number ): { metrics: ClientTeamMetrics; step: number } { const now = Date.now() - const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000 + const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000 - // Clamp start time to no earlier than 30 days ago - if (startMs < thirtyDaysAgo) { - startMs = thirtyDaysAgo + // Clamp start time to no earlier than 90 days ago + if (startMs < ninetyDaysAgo) { + startMs = ninetyDaysAgo } // Don't generate data beyond current time diff --git a/src/core/modules/sandboxes/schemas.ts b/src/core/modules/sandboxes/schemas.ts index 18b4d280c..6881fb9aa 100644 --- a/src/core/modules/sandboxes/schemas.ts +++ b/src/core/modules/sandboxes/schemas.ts @@ -15,7 +15,7 @@ const _startDateSchema = z return start >= now - MAX_DAYS_AGO }, { - message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, + message: 'Start date cannot be more than 90 days ago', } ) @@ -33,7 +33,7 @@ const _dateRangeRefine = (data: { startDate: number; endDate: number }) => { } const _dateRangeRefineMessage = { - message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, + message: 'Date range cannot exceed 90 days', } // PUBLIC diff --git a/src/core/server/functions/sandboxes/get-team-metrics-max.ts b/src/core/server/functions/sandboxes/get-team-metrics-max.ts index 0a2078f8a..147639da0 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics-max.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics-max.ts @@ -29,7 +29,7 @@ export const GetTeamMetricsMaxSchema = z return start >= now - MAX_DAYS_AGO }, { - message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, + message: 'Start date cannot be more than 90 days ago', } ), endDate: z @@ -47,7 +47,7 @@ export const GetTeamMetricsMaxSchema = z return data.endDate - data.startDate <= MAX_DAYS_AGO }, { - message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, + message: 'Date range cannot exceed 90 days', } ) diff --git a/src/core/server/functions/sandboxes/get-team-metrics.ts b/src/core/server/functions/sandboxes/get-team-metrics.ts index cc90ddacf..8a230e7e3 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics.ts @@ -26,7 +26,7 @@ export const GetTeamMetricsSchema = z return start >= now - MAX_DAYS_AGO }, { - message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, + message: 'Start date cannot be more than 90 days ago', } ), endDate: z @@ -43,7 +43,7 @@ export const GetTeamMetricsSchema = z return data.endDate - data.startDate <= MAX_DAYS_AGO }, { - message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, + message: 'Date range cannot exceed 90 days', } ) diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts index bea9338e6..5b665380f 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts @@ -39,11 +39,11 @@ export const customTimeFormSchema = z const now = Date.now() const startTimestamp = startDateTime.getTime() - // validate start date is not more than 91 days ago + // validate start date is not more than 90 days ago if (startTimestamp < now - MAX_DAYS_AGO) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Start date cannot be more than 91 days ago', + message: 'Start date cannot be more than 90 days ago', path: ['startDate'], }) return @@ -108,7 +108,7 @@ export const customTimeFormSchema = z if (endTimestamp - startTimestamp > MAX_DAYS_AGO) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Date range cannot exceed 91 days', + message: 'Date range cannot exceed 90 days', path: ['endDate'], }) return From 543fbf8fc210958b08a00f59a1d3f6f1422e057c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:44:45 +0000 Subject: [PATCH 3/4] refactor: use exact 90-day MAX_DAYS_AGO with separate buffer constant - Change MAX_DAYS_AGO to exactly 90 days (was 91) - Add MAX_DAYS_AGO_BUFFER (MAX_DAYS_AGO + 60s) for validation checks - Restore dynamic interpolation from MAX_DAYS_AGO in all error messages - Re-add MAX_DAYS_AGO import in header.tsx instead of inline constant - validation.ts uses MAX_DAYS_AGO_BUFFER for checks to prevent preset race conditions while keeping error messages accurate Co-Authored-By: ben@e2b.dev --- src/app/api/teams/[teamSlug]/metrics/types.ts | 4 ++-- src/core/modules/sandboxes/schemas.ts | 4 ++-- .../functions/sandboxes/get-team-metrics-max.ts | 4 ++-- .../functions/sandboxes/get-team-metrics.ts | 4 ++-- .../dashboard/sandboxes/monitoring/header.tsx | 3 ++- .../sandboxes/monitoring/hooks/use-timeframe.ts | 2 +- .../monitoring/time-picker/constants.ts | 3 ++- .../monitoring/time-picker/time-panel.tsx | 2 +- .../monitoring/time-picker/validation.ts | 17 +++++++++++------ 9 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/app/api/teams/[teamSlug]/metrics/types.ts b/src/app/api/teams/[teamSlug]/metrics/types.ts index 740485f58..d32af3573 100644 --- a/src/app/api/teams/[teamSlug]/metrics/types.ts +++ b/src/app/api/teams/[teamSlug]/metrics/types.ts @@ -15,7 +15,7 @@ export const TeamMetricsRequestSchema = z return start >= now - MAX_DAYS_AGO }, { - message: 'Start date cannot be more than 90 days ago', + message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, } ), end: z @@ -32,7 +32,7 @@ export const TeamMetricsRequestSchema = z return data.end - data.start <= MAX_DAYS_AGO }, { - message: 'Date range cannot exceed 90 days', + message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, } ) diff --git a/src/core/modules/sandboxes/schemas.ts b/src/core/modules/sandboxes/schemas.ts index 6881fb9aa..18b4d280c 100644 --- a/src/core/modules/sandboxes/schemas.ts +++ b/src/core/modules/sandboxes/schemas.ts @@ -15,7 +15,7 @@ const _startDateSchema = z return start >= now - MAX_DAYS_AGO }, { - message: 'Start date cannot be more than 90 days ago', + message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, } ) @@ -33,7 +33,7 @@ const _dateRangeRefine = (data: { startDate: number; endDate: number }) => { } const _dateRangeRefineMessage = { - message: 'Date range cannot exceed 90 days', + message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, } // PUBLIC diff --git a/src/core/server/functions/sandboxes/get-team-metrics-max.ts b/src/core/server/functions/sandboxes/get-team-metrics-max.ts index 147639da0..0a2078f8a 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics-max.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics-max.ts @@ -29,7 +29,7 @@ export const GetTeamMetricsMaxSchema = z return start >= now - MAX_DAYS_AGO }, { - message: 'Start date cannot be more than 90 days ago', + message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, } ), endDate: z @@ -47,7 +47,7 @@ export const GetTeamMetricsMaxSchema = z return data.endDate - data.startDate <= MAX_DAYS_AGO }, { - message: 'Date range cannot exceed 90 days', + message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, } ) diff --git a/src/core/server/functions/sandboxes/get-team-metrics.ts b/src/core/server/functions/sandboxes/get-team-metrics.ts index 8a230e7e3..cc90ddacf 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics.ts @@ -26,7 +26,7 @@ export const GetTeamMetricsSchema = z return start >= now - MAX_DAYS_AGO }, { - message: 'Start date cannot be more than 90 days ago', + message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, } ), endDate: z @@ -43,7 +43,7 @@ export const GetTeamMetricsSchema = z return data.endDate - data.startDate <= MAX_DAYS_AGO }, { - message: 'Date range cannot exceed 90 days', + message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, } ) diff --git a/src/features/dashboard/sandboxes/monitoring/header.tsx b/src/features/dashboard/sandboxes/monitoring/header.tsx index 41d7d4140..5398ca536 100644 --- a/src/features/dashboard/sandboxes/monitoring/header.tsx +++ b/src/features/dashboard/sandboxes/monitoring/header.tsx @@ -11,6 +11,7 @@ import { MaxConcurrentSandboxesClient, SandboxesStartRateClient, } from './header.client' +import { MAX_DAYS_AGO } from './time-picker/constants' interface MonitoringContentParams { teamSlug: string @@ -159,7 +160,7 @@ export const MaxConcurrentSandboxes = async ({ const { teamSlug } = await params const end = Date.now() - const start = end - (90 * 24 * 60 * 60 * 1000 - 60_000) // 90 days minus 1 minute margin to avoid validation errors + const start = end - (MAX_DAYS_AGO - 60_000) // 1 minute margin to avoid validation errors const teamMetricsResult = await getTeamMetricsMax({ teamSlug, diff --git a/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts b/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts index 7a39c455b..828e01847 100644 --- a/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts +++ b/src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts @@ -9,7 +9,7 @@ import { import { TIME_RANGES, type TimeRangeKey } from '@/lib/utils/timeframe' import { calculateIsLive } from '../utils' -const MAX_DAYS_AGO = 91 * 24 * 60 * 60 * 1000 +const MAX_DAYS_AGO = 90 * 24 * 60 * 60 * 1000 const MIN_RANGE_MS = 1.5 * 60 * 1000 const getStableNow = () => { diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts b/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts index f8e1b042e..dd22d5799 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts @@ -76,7 +76,8 @@ export const TIME_OPTIONS: TimeOption[] = [ ] // constraints -export const MAX_DAYS_AGO = 91 * 24 * 60 * 60 * 1000 // 91 days in ms +export const MAX_DAYS_AGO = 90 * 24 * 60 * 60 * 1000 // 90 days in ms +export const MAX_DAYS_AGO_BUFFER = MAX_DAYS_AGO + 60 * 1000 // validation buffer to prevent preset race conditions export const MIN_RANGE_MS = 1.5 * 60 * 1000 // 1.5 minutes minimum export const CLOCK_SKEW_TOLERANCE = 60 * 1000 // 60 seconds export const DEFAULT_RANGE_MS = 60 * 60 * 1000 // 1 hour default diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx b/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx index 963507aac..669ece03a 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx @@ -153,7 +153,7 @@ export const TimePanel = forwardRef( const { minDate, maxDate } = useMemo(() => { const now = new Date() - // create new Date object for minDate (91 days ago) + // create new Date object for minDate (MAX_DAYS_AGO ago) const minDate = new Date(now.getTime() - MAX_DAYS_AGO) minDate.setHours(0, 0, 0, 0) diff --git a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts index 5b665380f..aebd430a5 100644 --- a/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts +++ b/src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts @@ -4,7 +4,12 @@ import { z } from 'zod' import { combineDateTimeStrings } from '@/lib/utils/formatting' -import { CLOCK_SKEW_TOLERANCE, MAX_DAYS_AGO, MIN_RANGE_MS } from './constants' +import { + CLOCK_SKEW_TOLERANCE, + MAX_DAYS_AGO, + MAX_DAYS_AGO_BUFFER, + MIN_RANGE_MS, +} from './constants' export const customTimeFormSchema = z .object({ @@ -39,11 +44,11 @@ export const customTimeFormSchema = z const now = Date.now() const startTimestamp = startDateTime.getTime() - // validate start date is not more than 90 days ago - if (startTimestamp < now - MAX_DAYS_AGO) { + // validate start date is not more than MAX_DAYS_AGO + if (startTimestamp < now - MAX_DAYS_AGO_BUFFER) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Start date cannot be more than 90 days ago', + message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, path: ['startDate'], }) return @@ -105,10 +110,10 @@ export const customTimeFormSchema = z } // ensure range doesn't exceed maximum - if (endTimestamp - startTimestamp > MAX_DAYS_AGO) { + if (endTimestamp - startTimestamp > MAX_DAYS_AGO_BUFFER) { ctx.addIssue({ code: z.ZodIssueCode.custom, - message: 'Date range cannot exceed 90 days', + message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, path: ['endDate'], }) return From 8bcb21761716ee13e30f77b882fcaf368bb70e63 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:42:10 +0000 Subject: [PATCH 4/4] fix: use MAX_DAYS_AGO_BUFFER in server-side validation to prevent 90d preset rejection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server-side Zod schemas used MAX_DAYS_AGO (exactly 90d) for refine checks, but by the time a 90d preset request reaches the server, Date.now() has advanced a few ms — causing the start timestamp to fail the 'start >= now - MAX_DAYS_AGO' check. SWR's keepPreviousData then showed the old data instead of 90d range. Fix: use MAX_DAYS_AGO_BUFFER (90d + 60s) for validation checks in all server schemas, matching what the client-side validation already does. Error messages still reference MAX_DAYS_AGO for the user-facing '90 days' text. Co-Authored-By: ben@e2b.dev --- src/app/api/teams/[teamSlug]/metrics/types.ts | 9 ++++++--- src/core/modules/sandboxes/schemas.ts | 9 ++++++--- .../server/functions/sandboxes/get-team-metrics-max.ts | 9 ++++++--- src/core/server/functions/sandboxes/get-team-metrics.ts | 9 ++++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/app/api/teams/[teamSlug]/metrics/types.ts b/src/app/api/teams/[teamSlug]/metrics/types.ts index d32af3573..429ceb9b7 100644 --- a/src/app/api/teams/[teamSlug]/metrics/types.ts +++ b/src/app/api/teams/[teamSlug]/metrics/types.ts @@ -1,6 +1,9 @@ import { z } from 'zod' import type { ClientTeamMetrics } from '@/core/modules/sandboxes/models.client' -import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' +import { + MAX_DAYS_AGO, + MAX_DAYS_AGO_BUFFER, +} from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' export const TeamMetricsRequestSchema = z .object({ @@ -12,7 +15,7 @@ export const TeamMetricsRequestSchema = z .refine( (start) => { const now = Date.now() - return start >= now - MAX_DAYS_AGO + return start >= now - MAX_DAYS_AGO_BUFFER }, { message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, @@ -29,7 +32,7 @@ export const TeamMetricsRequestSchema = z }) .refine( (data) => { - return data.end - data.start <= MAX_DAYS_AGO + return data.end - data.start <= MAX_DAYS_AGO_BUFFER }, { message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, diff --git a/src/core/modules/sandboxes/schemas.ts b/src/core/modules/sandboxes/schemas.ts index 18b4d280c..2708019c1 100644 --- a/src/core/modules/sandboxes/schemas.ts +++ b/src/core/modules/sandboxes/schemas.ts @@ -1,5 +1,8 @@ import { z } from 'zod' -import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' +import { + MAX_DAYS_AGO, + MAX_DAYS_AGO_BUFFER, +} from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' // PRIVATE @@ -12,7 +15,7 @@ const _startDateSchema = z (start) => { const now = Date.now() - return start >= now - MAX_DAYS_AGO + return start >= now - MAX_DAYS_AGO_BUFFER }, { message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, @@ -29,7 +32,7 @@ const _endDateSchema = z }) const _dateRangeRefine = (data: { startDate: number; endDate: number }) => { - return data.endDate - data.startDate <= MAX_DAYS_AGO + return data.endDate - data.startDate <= MAX_DAYS_AGO_BUFFER } const _dateRangeRefineMessage = { diff --git a/src/core/server/functions/sandboxes/get-team-metrics-max.ts b/src/core/server/functions/sandboxes/get-team-metrics-max.ts index 0a2078f8a..49c133bda 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics-max.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics-max.ts @@ -12,7 +12,10 @@ import { handleDefaultInfraError } from '@/core/server/actions/utils' import { infra } from '@/core/shared/clients/api' import { l } from '@/core/shared/clients/logger/logger' import { TeamSlugSchema } from '@/core/shared/schemas/team' -import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' +import { + MAX_DAYS_AGO, + MAX_DAYS_AGO_BUFFER, +} from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' export const GetTeamMetricsMaxSchema = z .object({ @@ -26,7 +29,7 @@ export const GetTeamMetricsMaxSchema = z (start) => { const now = Date.now() - return start >= now - MAX_DAYS_AGO + return start >= now - MAX_DAYS_AGO_BUFFER }, { message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, @@ -44,7 +47,7 @@ export const GetTeamMetricsMaxSchema = z }) .refine( (data) => { - return data.endDate - data.startDate <= MAX_DAYS_AGO + return data.endDate - data.startDate <= MAX_DAYS_AGO_BUFFER }, { message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, diff --git a/src/core/server/functions/sandboxes/get-team-metrics.ts b/src/core/server/functions/sandboxes/get-team-metrics.ts index cc90ddacf..6031236a7 100644 --- a/src/core/server/functions/sandboxes/get-team-metrics.ts +++ b/src/core/server/functions/sandboxes/get-team-metrics.ts @@ -8,7 +8,10 @@ import { import { returnServerError } from '@/core/server/actions/utils' import { getPublicErrorMessage } from '@/core/shared/errors' import { TeamSlugSchema } from '@/core/shared/schemas/team' -import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' +import { + MAX_DAYS_AGO, + MAX_DAYS_AGO_BUFFER, +} from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' import { getTeamMetricsCore } from './get-team-metrics-core' export const GetTeamMetricsSchema = z @@ -23,7 +26,7 @@ export const GetTeamMetricsSchema = z (start) => { const now = Date.now() - return start >= now - MAX_DAYS_AGO + return start >= now - MAX_DAYS_AGO_BUFFER }, { message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, @@ -40,7 +43,7 @@ export const GetTeamMetricsSchema = z }) .refine( (data) => { - return data.endDate - data.startDate <= MAX_DAYS_AGO + return data.endDate - data.startDate <= MAX_DAYS_AGO_BUFFER }, { message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`,