diff --git a/apps/status-page/src/app/(public)/client.tsx b/apps/status-page/src/app/(public)/client.tsx index eb305fee7..c31d2d913 100644 --- a/apps/status-page/src/app/(public)/client.tsx +++ b/apps/status-page/src/app/(public)/client.tsx @@ -352,7 +352,10 @@ function ThemePlaygroundEvents({ ))} ) : null} - + diff --git a/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(list)/page.tsx b/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(list)/page.tsx index 04a7fb97f..89900e219 100644 --- a/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(list)/page.tsx +++ b/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(list)/page.tsx @@ -88,6 +88,7 @@ export default function Page() { ) : null} diff --git a/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(view)/report/[id]/page.tsx b/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(view)/report/[id]/page.tsx index 8557d45cc..bdd8db626 100644 --- a/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(view)/report/[id]/page.tsx +++ b/apps/status-page/src/app/(status-page)/[domain]/(public)/events/(view)/report/[id]/page.tsx @@ -68,7 +68,10 @@ export default function ReportPage() { ))} ) : null} - + diff --git a/apps/status-page/src/app/(status-page)/[domain]/(public)/page.tsx b/apps/status-page/src/app/(status-page)/[domain]/(public)/page.tsx index 601a52e1d..b617d2e88 100644 --- a/apps/status-page/src/app/(status-page)/[domain]/(public)/page.tsx +++ b/apps/status-page/src/app/(status-page)/[domain]/(public)/page.tsx @@ -144,8 +144,10 @@ export default function Page() { diff --git a/apps/status-page/src/components/status-page/status-events.tsx b/apps/status-page/src/components/status-page/status-events.tsx index cbc7a2163..03cd2f8a2 100644 --- a/apps/status-page/src/components/status-page/status-events.tsx +++ b/apps/status-page/src/components/status-page/status-events.tsx @@ -8,10 +8,12 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; +import { usePathnamePrefix } from "@/hooks/use-pathname-prefix"; import { formatDate, formatDateRange, formatDateTime } from "@/lib/formatter"; import { cn } from "@/lib/utils"; import { formatDistanceStrict } from "date-fns"; -import { Check } from "lucide-react"; +import { Check, ChevronRight } from "lucide-react"; +import Link from "next/link"; import { status } from "./messages"; export function StatusEventGroup({ @@ -171,52 +173,70 @@ export function StatusEventTimelineReport({ className, updates, withDot = true, + maxUpdates, + reportId, ...props }: React.ComponentProps<"div"> & { // TODO: remove unused props + reportId: number; updates: { date: Date; message: string; status: "investigating" | "identified" | "monitoring" | "resolved"; }[]; withDot?: boolean; + maxUpdates?: number; }) { + const prefix = usePathnamePrefix(); + const sortedUpdates = [...updates].sort( + (a, b) => b.date.getTime() - a.date.getTime(), + ); + const hasMoreUpdates = maxUpdates && sortedUpdates.length > maxUpdates; + const displayedUpdates = maxUpdates + ? sortedUpdates.slice(0, maxUpdates) + : sortedUpdates; + return (
{/* NOTE: make sure they are sorted by date */} - {updates - .sort((a, b) => b.date.getTime() - a.date.getTime()) - .map((update, index) => { - const updateDate = new Date(update.date); - let durationText: string | undefined; + {displayedUpdates.map((update, index) => { + const updateDate = new Date(update.date); + let durationText: string | undefined; - if (index === 0) { - const startedAt = new Date(updates[updates.length - 1].date); - const duration = formatDistanceStrict(startedAt, updateDate); + if (index === 0) { + const startedAt = new Date( + sortedUpdates[sortedUpdates.length - 1].date, + ); + const duration = formatDistanceStrict(startedAt, updateDate); - if (duration !== "0 seconds" && update.status === "resolved") { - durationText = `(in ${duration})`; - } - } else { - const lastUpdateDate = new Date(updates[index - 1].date); - const timeFromLast = formatDistanceStrict( - updateDate, - lastUpdateDate, - ); - durationText = `(${timeFromLast} earlier)`; + if (duration !== "0 seconds" && update.status === "resolved") { + durationText = `(in ${duration})`; } + } else { + const lastUpdateDate = new Date(displayedUpdates[index - 1].date); + const timeFromLast = formatDistanceStrict(updateDate, lastUpdateDate); + durationText = `(${timeFromLast} earlier)`; + } - return ( - - ); - })} + return ( + + ); + })} + {hasMoreUpdates && ( + + )}
); } @@ -278,6 +298,37 @@ function StatusEventTimelineReportUpdate({ ); } +function StatusEventTimelineReadMore({ + href, + withDot = true, +}: { + href: string; + withDot?: boolean; +}) { + return ( +
+
+
+ {withDot ? ( +
+
+
+
+
+ ) : null} + + View full report + + +
+
+
+ ); +} + export function StatusEventTimelineMaintenance({ maintenance, withDot = true, diff --git a/apps/status-page/src/components/status-page/status-feed.tsx b/apps/status-page/src/components/status-page/status-feed.tsx index 0955b3b24..77058abf8 100644 --- a/apps/status-page/src/components/status-page/status-feed.tsx +++ b/apps/status-page/src/components/status-page/status-feed.tsx @@ -126,7 +126,10 @@ export function StatusFeed({ ))} )} - + diff --git a/apps/workflows/src/incident/index.ts b/apps/workflows/src/incident/index.ts index c31d6d378..251c21e95 100644 --- a/apps/workflows/src/incident/index.ts +++ b/apps/workflows/src/incident/index.ts @@ -1,5 +1,5 @@ import { schema } from "@openstatus/db"; -import { and, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm"; +import { and, eq, inArray, isNull, ne } from "drizzle-orm"; import { Hono } from "hono"; import { env } from "../env"; import { db } from "../lib/db"; @@ -19,11 +19,7 @@ incidentRoute.get("/cleanup", async (c) => { const unresolvedIncidentMonitorIds = db .select({ monitorId: schema.incidentTable.monitorId }) .from(schema.incidentTable) - .where( - and( - isNull(schema.incidentTable.resolvedAt), - ), - ); + .where(and(isNull(schema.incidentTable.resolvedAt))); const activeMonitorsWithUnresolvedIncidents = await db .select({ id: schema.monitor.id })