diff --git a/site/src/pages/AgentsPage/AgentSettingsPageView.tsx b/site/src/pages/AgentsPage/AgentSettingsPageView.tsx index 3357751f58..41cb3b568a 100644 --- a/site/src/pages/AgentsPage/AgentSettingsPageView.tsx +++ b/site/src/pages/AgentsPage/AgentSettingsPageView.tsx @@ -274,6 +274,7 @@ const UsageContent: FC = ({ now }) => { } /> diff --git a/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.stories.tsx b/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.stories.tsx index 31c8786e26..e459aec31e 100644 --- a/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.stories.tsx +++ b/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.stories.tsx @@ -14,6 +14,9 @@ const defaultValue: DateRangeValue = { const meta: Meta = { title: "components/DateRangePicker", component: DateRangePicker, + args: { + now: fixedNow.toDate(), + }, }; export default meta; @@ -61,7 +64,13 @@ export const Open: Story = { export const SelectPreset: Story = { render: function SelectPresetStory() { const [value, setValue] = useState(defaultValue); - return ; + return ( + + ); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -93,7 +102,13 @@ export const SelectCalendarRange: Story = { startDate: new Date("2025-03-01"), endDate: new Date("2025-03-15"), }); - return ; + return ( + + ); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); @@ -119,7 +134,13 @@ export const SelectCalendarRange: Story = { export const CancelClosesWithoutApplying: Story = { render: function CancelStory() { const [value, setValue] = useState(defaultValue); - return ; + return ( + + ); }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); diff --git a/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.tsx b/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.tsx index 0cd9e6d172..c6e1d649e7 100644 --- a/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.tsx +++ b/site/src/pages/AgentsPage/components/DateRangePicker/DateRangePicker.tsx @@ -27,44 +27,60 @@ interface DateRangePreset { range: () => { from: Date; to: Date }; } -const defaultPresets: DateRangePreset[] = [ - { - label: "Today", - range: () => ({ from: new Date(), to: new Date() }), - }, - { - label: "Yesterday", - range: () => { - const d = dayjs().subtract(1, "day").toDate(); - return { from: d, to: d }; +const buildDefaultPresets = (now?: Date): DateRangePreset[] => { + const getCurrentTime = () => dayjs(now ?? new Date()); + return [ + { + label: "Today", + range: () => { + const currentTime = getCurrentTime(); + return { from: currentTime.toDate(), to: currentTime.toDate() }; + }, }, - }, - { - label: "Last 7 days", - range: () => ({ - from: dayjs().subtract(6, "day").toDate(), - to: new Date(), - }), - }, - { - label: "Last 14 days", - range: () => ({ - from: dayjs().subtract(13, "day").toDate(), - to: new Date(), - }), - }, - { - label: "Last 30 days", - range: () => ({ - from: dayjs().subtract(29, "day").toDate(), - to: new Date(), - }), - }, -]; + { + label: "Yesterday", + range: () => { + const d = getCurrentTime().subtract(1, "day").toDate(); + return { from: d, to: d }; + }, + }, + { + label: "Last 7 days", + range: () => { + const currentTime = getCurrentTime(); + return { + from: currentTime.subtract(6, "day").toDate(), + to: currentTime.toDate(), + }; + }, + }, + { + label: "Last 14 days", + range: () => { + const currentTime = getCurrentTime(); + return { + from: currentTime.subtract(13, "day").toDate(), + to: currentTime.toDate(), + }; + }, + }, + { + label: "Last 30 days", + range: () => { + const currentTime = getCurrentTime(); + return { + from: currentTime.subtract(29, "day").toDate(), + to: currentTime.toDate(), + }; + }, + }, + ]; +}; interface DateRangePickerProps { value: DateRangeValue; onChange: (value: DateRangeValue) => void; + now?: Date; presets?: DateRangePreset[]; } @@ -74,10 +90,11 @@ interface DateRangePickerProps { * rounded up to the next hour (if it falls on today) or to the start of * the following day. */ -function toBoundary(from: Date, to: Date): DateRangeValue { +function toBoundary(from: Date, to: Date, now: Date): DateRangeValue { + const currentTime = dayjs(now); const start = dayjs(from).startOf("day").toDate(); - const end = dayjs(to).isSame(dayjs(), "day") - ? dayjs().startOf("hour").add(1, "hour").toDate() + const end = dayjs(to).isSame(currentTime, "day") + ? currentTime.startOf("hour").add(1, "hour").toDate() : dayjs(to).startOf("day").add(1, "day").toDate(); return { startDate: start, endDate: end }; } @@ -100,9 +117,12 @@ function fromBoundary(value: DateRangeValue): DayPickerDateRange { export const DateRangePicker: FC = ({ value, onChange, - presets = defaultPresets, + now, + presets, }) => { const [open, setOpen] = useState(false); + const currentTime = now ?? new Date(); + const resolvedPresets = presets ?? buildDefaultPresets(now); // Internal selection state kept separate from the committed value // so the user can freely adjust the range before applying. This @@ -113,7 +133,7 @@ export const DateRangePicker: FC = ({ const commit = () => { if (selection?.from && selection?.to) { - onChange(toBoundary(selection.from, selection.to)); + onChange(toBoundary(selection.from, selection.to, now ?? new Date())); } setOpen(false); }; @@ -122,7 +142,7 @@ export const DateRangePicker: FC = ({ const { from, to } = preset.range(); setSelection({ from, to }); // Presets are a complete selection — commit immediately. - onChange(toBoundary(from, to)); + onChange(toBoundary(from, to, now ?? new Date())); setOpen(false); }; @@ -168,7 +188,7 @@ export const DateRangePicker: FC = ({
{/* Presets sidebar */}
- {presets.map((preset) => ( + {resolvedPresets.map((preset) => (
diff --git a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx index d11a38540b..cd2a5f14b4 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenForm.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenForm.tsx @@ -33,6 +33,7 @@ interface CreateTokenFormProps { setFormError: (arg0: unknown) => void; isCreating: boolean; creationFailed: boolean; + now?: Date; } export const CreateTokenForm: FC = ({ @@ -42,6 +43,7 @@ export const CreateTokenForm: FC = ({ setFormError, isCreating, creationFailed, + now, }) => { const navigate = useNavigate(); @@ -49,6 +51,7 @@ export const CreateTokenForm: FC = ({ const [lifetimeDays, setLifetimeDays] = useState( determineDefaultLtValue(maxTokenLifetime), ); + const currentTime = dayjs(now ?? new Date()); // biome-ignore lint/correctness/useExhaustiveDependencies: adding form will cause an infinite loop useEffect(() => { @@ -86,7 +89,7 @@ export const CreateTokenForm: FC = ({ <> The token will expire on{" "} - {dayjs() + {currentTime .add(form.values.lifetime, "days") .utc() .format("MMMM DD, YYYY")} diff --git a/site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx b/site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx index 8885dc5841..d23b69df0e 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenPage.stories.tsx @@ -17,4 +17,8 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + args: { + now: new Date("2026-03-12T12:00:00Z"), + }, +}; diff --git a/site/src/pages/CreateTokenPage/CreateTokenPage.tsx b/site/src/pages/CreateTokenPage/CreateTokenPage.tsx index 4c39af9741..f058e170df 100644 --- a/site/src/pages/CreateTokenPage/CreateTokenPage.tsx +++ b/site/src/pages/CreateTokenPage/CreateTokenPage.tsx @@ -19,7 +19,11 @@ const initialValues: CreateTokenData = { lifetime: 30, }; -const CreateTokenPage: FC = () => { +type CreateTokenPageProps = { + now?: Date; +}; + +const CreateTokenPage: FC = ({ now }) => { const navigate = useNavigate(); const { @@ -105,6 +109,7 @@ const CreateTokenPage: FC = () => { setFormError={setFormError} isCreating={isCreating} creationFailed={creationFailed} + now={now} /> = { title: "pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseCard", @@ -176,7 +175,7 @@ export const ExpiredAIGovernanceOverageShowsExpired: Story = { ...MockLicenseResponse[1], claims: { ...MockLicenseResponse[1].claims, - license_expires: YESTERDAY, + license_expires: EXPIRED_DATE, features: { ...MockLicenseResponse[1].claims.features, ai_governance_user_limit: 1000, @@ -208,7 +207,7 @@ export const ExpiredAIGovernanceInGracePeriodShowsExceeded: Story = { ...MockLicenseResponse[1], claims: { ...MockLicenseResponse[1].claims, - license_expires: YESTERDAY, + license_expires: EXPIRED_DATE, features: { ...MockLicenseResponse[1].claims.features, ai_governance_user_limit: 1000, @@ -238,7 +237,7 @@ export const NotYetValid: Story = { ...MockLicenseResponse[1], claims: { ...MockLicenseResponse[1].claims, - nbf: NEXT_WEEK, + nbf: FUTURE_START_DATE, }, }, }, @@ -254,7 +253,7 @@ export const FutureAIGovernanceOverageShowsStartsOn: Story = { ...MockLicenseResponse[1], claims: { ...MockLicenseResponse[1].claims, - nbf: NEXT_WEEK, + nbf: FUTURE_START_DATE, features: { ...MockLicenseResponse[1].claims.features, ai_governance_user_limit: 1000, @@ -286,7 +285,7 @@ export const FutureAIGovernanceUsageShowsNoCurrentSeats: Story = { ...MockLicenseResponse[1], claims: { ...MockLicenseResponse[1].claims, - nbf: NEXT_WEEK, + nbf: FUTURE_START_DATE, features: { ...MockLicenseResponse[1].claims.features, ai_governance_user_limit: 1000, diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.stories.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.stories.tsx index 95e4abeb6d..1b734582a0 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.stories.tsx @@ -23,6 +23,8 @@ const USER_STATUS_COUNTS_QUERY = { data: { active: [] }, }; +const STORY_NOW = dayjs("2099-01-01T12:00:00Z"); + const withBaseQueries = ({ entitlements = MockEntitlements, licenses = MockLicenseResponse, @@ -89,8 +91,8 @@ const createLicense = ({ addons?: string[]; }) => ({ id, - uploaded_at: String(dayjs().subtract(uploadedDaysAgo, "day").unix()), - expires_at: String(dayjs().add(expiresInDays, "day").unix()), + uploaded_at: String(STORY_NOW.subtract(uploadedDaysAgo, "day").unix()), + expires_at: String(STORY_NOW.add(expiresInDays, "day").unix()), uuid, claims: { trial: false, @@ -102,8 +104,8 @@ const createLicense = ({ user_limit: userLimit, }, addons, - license_expires: dayjs().add(licenseExpiresInDays, "day").unix(), - nbf: dayjs().add(nbfOffsetDays, "day").unix(), + license_expires: STORY_NOW.add(licenseExpiresInDays, "day").unix(), + nbf: STORY_NOW.add(nbfOffsetDays, "day").unix(), }, }); diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx index 1e1e0e8198..a5e12fa0dd 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsControls.stories.tsx @@ -17,6 +17,7 @@ const defaultArgs: Partial> = { startDate: new Date("2025-08-05"), endDate: new Date("2025-08-07"), }, + now: new Date("2025-08-07T12:00:00Z"), setDateRange: () => {}, searchParams: new URLSearchParams(), setSearchParams: () => {}, diff --git a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx index fc7747690e..444b5a10f1 100644 --- a/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx +++ b/site/src/pages/TemplatePage/TemplateInsightsPage/TemplateInsightsPage.tsx @@ -142,6 +142,7 @@ interface TemplateInsightsControlsProps { setDateRange: (value: DateRangeValue) => void; searchParams: URLSearchParams; setSearchParams: SetURLSearchParams; + now?: Date; } export const TemplateInsightsControls: FC = ({ @@ -150,6 +151,7 @@ export const TemplateInsightsControls: FC = ({ setDateRange, searchParams, setSearchParams, + now, }) => { return ( <> @@ -165,7 +167,7 @@ export const TemplateInsightsControls: FC = ({ }} /> {interval === "day" ? ( - + ) : ( )}