Skip to content
Closed
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
55 changes: 55 additions & 0 deletions apps/web/app/lib/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
formatDistanceStrict,
formatDistanceToNow,
formatDistanceToNowStrict,
type FormatDistanceStrictOptions,
type FormatDistanceToNowStrictOptions,
} from "date-fns";

function toValidDate(value: Date | string | null | undefined): Date | null {
if (value == null) return null;
const date = value instanceof Date ? value : new Date(value);
if (isNaN(date.getTime())) return null;
return date;
}

export function safeFormatDistanceToNowStrict(
value: Date | string | null | undefined,
options?: FormatDistanceToNowStrictOptions,
): string | null {
const date = toValidDate(value);
if (date == null) return null;
return formatDistanceToNowStrict(date, options);
}

export function safeFormatDistanceToNow(
value: Date | string | null | undefined,
options?: Parameters<typeof formatDistanceToNow>[1],
): string | null {
const date = toValidDate(value);
if (date == null) return null;
return formatDistanceToNow(date, options);
}

export function safeFormatDistanceStrict(
a: Date | string | null | undefined,
b: Date | string | null | undefined,
options?: FormatDistanceStrictOptions,
): string | null {
const dateA = toValidDate(a);
const dateB = toValidDate(b);
if (dateA == null || dateB == null) return null;
return formatDistanceStrict(dateA, dateB, options);
}

export function safeIsPast(value: Date | string | null | undefined): boolean {
const date = toValidDate(value);
if (date == null) return false;
return date.getTime() < Date.now();
}

export function safeIsFuture(value: Date | string | null | undefined): boolean {
const date = toValidDate(value);
if (date == null) return false;
return date.getTime() > Date.now();
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import type { AppRouter } from "@ctrlplane/trpc";
import { useMemo } from "react";
import {
formatDistanceStrict,
formatDistanceToNowStrict,
isPast,
} from "date-fns";
import _ from "lodash";
import {
CheckCircle2Icon,
Expand All @@ -15,6 +10,11 @@ import { rrulestr } from "rrule";

import { trpc } from "~/api/trpc";
import { useWorkspace } from "~/components/WorkspaceProvider";
import {
safeFormatDistanceStrict,
safeFormatDistanceToNowStrict,
safeIsPast,
} from "~/lib/date";
Comment on lines +13 to +17
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file still references isPast and formatDistanceToNowStrict later in WindowRow (e.g. checks for ended/next window), but those date-fns imports were removed and the safe replacements are imported instead. This will cause a TypeScript compile error (unresolved identifiers) and also defeats the goal of using safe formatters. Update the remaining usages to safeIsPast / safeFormatDistanceToNowStrict (and handle null returns) or reintroduce the needed imports consistently.

Copilot uses AI. Check for mistakes.

type DeploymentWindow = NonNullable<
Awaited<ReturnType<AppRouter["deploymentVersions"]["evaulate"]>>
Expand Down Expand Up @@ -111,14 +111,12 @@ function WindowRow({
const windowOpenedAt = isOpen
? new Date(nextEnd.getTime() - durationMs)
: null;
const timeOpen =
windowOpenedAt != null ? formatDistanceToNowStrict(windowOpenedAt) : null;
const timeOpen = safeFormatDistanceToNowStrict(windowOpenedAt);
const timeRemaining =
isOpen && !isPast(nextEnd) ? formatDistanceToNowStrict(nextEnd) : null;
const totalDuration =
windowOpenedAt != null
? formatDistanceStrict(windowOpenedAt, nextEnd)
isOpen && !safeIsPast(nextEnd)
? safeFormatDistanceToNowStrict(nextEnd)
: null;
const totalDuration = safeFormatDistanceStrict(windowOpenedAt, nextEnd);

const windowLabel =
policyName ?? (isAllowWindow ? "Allow Window" : "Deny Window");
Expand Down Expand Up @@ -148,15 +146,15 @@ function WindowRow({
{timeOpen != null &&
` (${timeOpen}${totalDuration != null ? ` of ${totalDuration}` : ""})`}
{timeRemaining != null && ` · closes in ${timeRemaining}`}
{isPast(nextEnd) &&
` · ended ${formatDistanceToNowStrict(nextEnd)} ago`}
{safeIsPast(nextEnd) &&
` · ended ${safeFormatDistanceToNowStrict(nextEnd) ?? "?"} ago`}
</span>
) : (
<span>
{windowLabel} Closed · next{" "}
{isPast(nextStart)
? `${formatDistanceToNowStrict(nextStart)} ago`
: `in ${formatDistanceToNowStrict(nextStart)}`}
{safeIsPast(nextStart)
? `${safeFormatDistanceToNowStrict(nextStart) ?? "?"} ago`
: `in ${safeFormatDistanceToNowStrict(nextStart) ?? "?"}`}
</span>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AppRouter } from "@ctrlplane/trpc";
import { useMemo } from "react";
import { formatDistanceToNowStrict } from "date-fns";
import _ from "lodash";
import {
CheckCircle2Icon,
Expand All @@ -20,6 +19,7 @@ import {
} from "~/components/ui/dialog";
import { Progress } from "~/components/ui/progress";
import { useWorkspace } from "~/components/WorkspaceProvider";
import { safeFormatDistanceToNowStrict } from "~/lib/date";
import { cn } from "~/lib/utils";

type RuleEvaluation = NonNullable<
Expand All @@ -41,15 +41,6 @@ type ProgressionDetails = {
failed_environments?: number;
};

function safeFormatDistanceToNowStrict(
value: Date | string | null | undefined,
) {
if (value == null) return null;
const date = value instanceof Date ? value : new Date(value);
if (isNaN(date.getTime())) return null;
return formatDistanceToNowStrict(date);
}

function extractEnvDetails(details: Record<string, unknown>): PerEnvDetail[] {
const envDetails: PerEnvDetail[] = [];
for (const [key, value] of Object.entries(details)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AppRouter } from "@ctrlplane/trpc";
import { useMemo } from "react";
import { formatDistanceToNowStrict, isFuture } from "date-fns";
import _ from "lodash";
import {
CheckCircle2Icon,
Expand All @@ -12,6 +11,10 @@ import {
import { trpc } from "~/api/trpc";
import { Progress } from "~/components/ui/progress";
import { useWorkspace } from "~/components/WorkspaceProvider";
import {
safeFormatDistanceToNowStrict,
safeIsFuture,
} from "~/lib/date";

type RolloutEvaluation = NonNullable<
Awaited<ReturnType<AppRouter["deploymentVersions"]["evaulate"]>>
Expand Down Expand Up @@ -126,19 +129,27 @@ function RolloutRow({
{!isComplete && (
<span className="shrink-0 text-muted-foreground">
{isWaiting && "Waiting for other policies to pass"}
{!isWaiting && nextDeployment != null && isFuture(nextDeployment) && (
<>next in {formatDistanceToNowStrict(nextDeployment)}</>
)}
{!isWaiting &&
nextDeployment != null &&
isFuture(nextDeployment) &&
safeIsFuture(nextDeployment) && (
<>
next in{" "}
{safeFormatDistanceToNowStrict(nextDeployment) ?? "?"}
</>
)}
{!isWaiting &&
nextDeployment != null &&
safeIsFuture(nextDeployment) &&
estimatedCompletion != null &&
isFuture(estimatedCompletion) &&
safeIsFuture(estimatedCompletion) &&
" · "}
{!isWaiting &&
estimatedCompletion != null &&
isFuture(estimatedCompletion) && (
<>done in {formatDistanceToNowStrict(estimatedCompletion)}</>
safeIsFuture(estimatedCompletion) && (
<>
done in{" "}
{safeFormatDistanceToNowStrict(estimatedCompletion) ?? "?"}
</>
)}
</span>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { AppRouter } from "@ctrlplane/trpc";
import { useMemo } from "react";
import { formatDistanceToNowStrict, isFuture } from "date-fns";
import _ from "lodash";
import {
CheckCircle2Icon,
Expand All @@ -11,6 +10,10 @@ import {

import { trpc } from "~/api/trpc";
import { useWorkspace } from "~/components/WorkspaceProvider";
import {
safeFormatDistanceToNowStrict,
safeIsFuture,
} from "~/lib/date";

type CooldownEvaluation = NonNullable<
Awaited<ReturnType<AppRouter["deploymentVersions"]["evaulate"]>>
Expand Down Expand Up @@ -101,15 +104,18 @@ function CooldownDescription({
{details.time_remaining} remaining
{details.required_interval != null &&
` of ${details.required_interval}`}
{earliest != null && isFuture(earliest) && (
<> · deploys in {formatDistanceToNowStrict(earliest)}</>
{earliest != null && safeIsFuture(earliest) && (
<>
{" "}
· deploys in {safeFormatDistanceToNowStrict(earliest) ?? "?"}
</>
)}
</>
);
}

if (earliest != null && isFuture(earliest))
return <>next in {formatDistanceToNowStrict(earliest)}</>;
if (earliest != null && safeIsFuture(earliest))
return <>next in {safeFormatDistanceToNowStrict(earliest) ?? "?"}</>;

return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { formatDistanceToNowStrict } from "date-fns";

import { Skeleton } from "~/components/ui/skeleton";
import { safeFormatDistanceToNowStrict } from "~/lib/date";
import { cn } from "~/lib/utils";

import { useMetricMeasurements } from "./useMetricMeasurements";
Expand Down Expand Up @@ -127,7 +126,7 @@ export function VerificationMetricStatus({
)[0];

const timeAgo = latestMeasurement
? formatDistanceToNowStrict(new Date(latestMeasurement.measuredAt), {
? safeFormatDistanceToNowStrict(latestMeasurement.measuredAt, {
addSuffix: true,
})
: null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { formatDistanceToNowStrict } from "date-fns";
import { Check, Loader2, X } from "lucide-react";

import { Spinner } from "~/components/ui/spinner";
import { safeFormatDistanceToNowStrict } from "~/lib/date";
import { cn } from "~/lib/utils";
import { useMetricMeasurements } from "../useMetricMeasurements";
import {
Expand Down Expand Up @@ -140,9 +140,9 @@ export function ArgoCDVerificationDisplay({
<div className="flex justify-between">
<div className="text-muted-foreground">Last Synced</div>
<div>
{formatDistanceToNowStrict(new Date(status.reconciledAt), {
{safeFormatDistanceToNowStrict(status.reconciledAt, {
addSuffix: true,
})}
}) ?? "unknown"}
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { formatDistanceToNowStrict } from "date-fns";

import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip";
import { Spinner } from "~/components/ui/spinner";
import { safeFormatDistanceToNowStrict } from "~/lib/date";
import { cn } from "~/lib/utils";
import { useMetricMeasurements } from "../useMetricMeasurements";
import {
Expand Down Expand Up @@ -151,7 +150,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) {
const isPassed = measurement.status === "passed";
const isFailed = measurement.status === "failed";

const timeAgo = formatDistanceToNowStrict(new Date(measurement.measuredAt), {
const timeAgo = safeFormatDistanceToNowStrict(measurement.measuredAt, {
addSuffix: true,
});

Expand All @@ -168,7 +167,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) {
!isPassed && !isFailed && "bg-muted-foreground",
)}
/>
<span className="text-muted-foreground">{timeAgo}</span>
<span className="text-muted-foreground">{timeAgo ?? "—"}</span>
</div>
<div className="flex items-center gap-3 font-mono">
{queryEntries.length > 0 ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { formatDistanceToNowStrict } from "date-fns";

import { Spinner } from "~/components/ui/spinner";
import { safeFormatDistanceToNowStrict } from "~/lib/date";
import { cn } from "~/lib/utils";
import { useMetricMeasurements } from "../useMetricMeasurements";
import {
Expand Down Expand Up @@ -115,10 +114,9 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) {
const isPassed = measurement.status === "passed";
const isFailed = measurement.status === "failed";

const timeAgo = formatDistanceToNowStrict(
new Date(measurement.measuredAt),
{ addSuffix: true },
);
const timeAgo = safeFormatDistanceToNowStrict(measurement.measuredAt, {
addSuffix: true,
});

return (
<div className="flex items-center justify-between rounded px-1 py-0.5 text-xs">
Expand All @@ -131,7 +129,7 @@ function MeasurementRow({ measurement }: { measurement: MetricMeasurement }) {
!isPassed && !isFailed && "bg-muted-foreground",
)}
/>
<span className="text-muted-foreground">{timeAgo}</span>
<span className="text-muted-foreground">{timeAgo ?? "—"}</span>
</div>
<span className="font-mono">
{parsed?.value != null ? parsed.value : "—"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useMemo } from "react";
import { formatDistanceToNow } from "date-fns";
import _ from "lodash";
import {
AlertCircleIcon,
Expand All @@ -17,6 +16,7 @@ import { Link, useParams } from "react-router";
import { toast } from "sonner";

import { trpc } from "~/api/trpc";
import { safeFormatDistanceToNow } from "~/lib/date";
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
import { Badge } from "~/components/ui/badge";
import {
Expand Down Expand Up @@ -245,18 +245,21 @@ function EvalRow({ evaluation }: { evaluation: Evaluation }) {
<span className="flex items-center gap-1">
<Clock className="size-3" />
Evaluated{" "}
{formatDistanceToNow(new Date(evaluation.evaluatedAt), {
{safeFormatDistanceToNow(evaluation.evaluatedAt, {
addSuffix: true,
})}
}) ?? "unknown"}
</span>
{evaluation.satisfiedAt != null && (
<span>
Satisfied{" "}
{formatDistanceToNow(new Date(evaluation.satisfiedAt), {
addSuffix: true,
})}
</span>
)}
{evaluation.satisfiedAt != null &&
safeFormatDistanceToNow(evaluation.satisfiedAt, {
addSuffix: true,
}) != null && (
<span>
Satisfied{" "}
{safeFormatDistanceToNow(evaluation.satisfiedAt, {
addSuffix: true,
})}
</span>
)}
Comment on lines +252 to +262
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeFormatDistanceToNow(evaluation.satisfiedAt, { addSuffix: true }) is computed twice (once in the conditional and again for rendering). Consider computing it once into a local variable and reusing it to avoid duplicate parsing/formatting and ensure the displayed value matches the conditional check.

Suggested change
{evaluation.satisfiedAt != null &&
safeFormatDistanceToNow(evaluation.satisfiedAt, {
addSuffix: true,
}) != null && (
<span>
Satisfied{" "}
{safeFormatDistanceToNow(evaluation.satisfiedAt, {
addSuffix: true,
})}
</span>
)}
{(() => {
const satisfiedAtDistance =
evaluation.satisfiedAt == null
? null
: safeFormatDistanceToNow(evaluation.satisfiedAt, {
addSuffix: true,
});
return (
satisfiedAtDistance != null && (
<span>
Satisfied {satisfiedAtDistance}
</span>
)
);
})()}

Copilot uses AI. Check for mistakes.
</div>
{windowDetails != null && <WindowInfo details={windowDetails} />}
{approvalDetails != null && <ApprovalInfo details={approvalDetails} />}
Expand Down
Loading
Loading