Skip to content

Commit 19c1675

Browse files
authored
feat(webapp): errors page polish and GA rollout (#3477)
## What this does Polish + bug-fix pass on the Errors page so it can ship to everyone. Touches the Slack alert config UX, errors list, error detail page, and unhides the SideMenu entry for non-admins. ## Decisions **"No channel" item over standalone Remove button** Chose pinning a `<XMarkIcon /> No channel` `SelectItem` above the channel list. Rejected the standalone "Remove channel" link in a `<Hint>` — color/hover behaviour clashed with the sibling `<TextLink>`, and "channel selection" is the right context for clearing. Server action already deletes the channel when `slackChannel=""` is submitted. **Slack `<!date^>` token over per-user TZ field for alerts** Chose Slack's native `<!date^TS^…>` token so each viewer sees timestamps in their own timezone (UTC fallback). Rejected per-user/per-org TZ schema work — works for multi-region channels for free. Email/dashboard TZ source-of-truth filed as TRI-8885 / TRI-8886. **Make errors GA**
1 parent 24de77c commit 19c1675

7 files changed

Lines changed: 87 additions & 60 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Ship the Errors page to all users, with a polish + bug-fix pass: pinned "No channel" item in the Slack alert channel picker, viewer-timezone alert timestamps via Slack's `<!date^>` token, Activity sparkline peak tooltip, centered loading spinner and bug-icon empty state on the error detail page, ellipsis on the Configure alerts trigger.

apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ export function ConfigureErrorAlerts({
196196
name={slackChannel.name}
197197
placeholder={<span className="text-text-dimmed">Select a Slack channel</span>}
198198
heading="Filter channels…"
199-
defaultValue={selectedSlackChannelValue}
199+
value={selectedSlackChannelValue ?? ""}
200200
dropdownIcon
201201
variant="tertiary/medium"
202202
items={slack.channels}
@@ -218,6 +218,15 @@ export function ConfigureErrorAlerts({
218218
>
219219
{(matches) => (
220220
<>
221+
<SelectItem
222+
value=""
223+
className="border-b border-grid-bright text-text-dimmed"
224+
>
225+
<div className="flex items-center gap-1.5">
226+
<XMarkIcon className="size-4" />
227+
<span>No channel</span>
228+
</div>
229+
</SelectItem>
221230
{matches?.map((channel) => (
222231
<SelectItem
223232
key={channel.id}

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -524,17 +524,15 @@ export function SideMenu({
524524
isCollapsed={isCollapsed}
525525
/>
526526
)}
527-
{(user.admin || user.isImpersonating) && (
528-
<SideMenuItem
529-
name="Errors"
530-
icon={IconBugFilled}
531-
activeIconColor="text-errors"
532-
inactiveIconColor="text-errors"
533-
to={v3ErrorsPath(organization, project, environment)}
534-
data-action="errors"
535-
isCollapsed={isCollapsed}
536-
/>
537-
)}
527+
<SideMenuItem
528+
name="Errors"
529+
icon={IconBugFilled}
530+
activeIconColor="text-errors"
531+
inactiveIconColor="text-errors"
532+
to={v3ErrorsPath(organization, project, environment)}
533+
data-action="errors"
534+
isCollapsed={isCollapsed}
535+
/>
538536
<SideMenuItem
539537
name="Query"
540538
icon={TableCellsIcon}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { type LoaderFunctionArgs, type ActionFunctionArgs, json } from "@remix-run/server-runtime";
22
import { type MetaFunction, useFetcher, useRevalidator } from "@remix-run/react";
33
import { BellAlertIcon } from "@heroicons/react/20/solid";
4-
import { IconAlarmSnooze as IconAlarmSnoozeBase, IconCircleDotted } from "@tabler/icons-react";
4+
import {
5+
IconAlarmSnooze as IconAlarmSnoozeBase,
6+
IconBugFilled,
7+
IconCircleDotted,
8+
} from "@tabler/icons-react";
59
import { parse } from "@conform-to/zod";
610
import { z } from "zod";
711
import { ErrorStatusBadge } from "~/components/errors/ErrorStatusBadge";
@@ -27,7 +31,7 @@ import {
2731
import { type NextRunList } from "~/presenters/v3/NextRunListPresenter.server";
2832
import { $replica } from "~/db.server";
2933
import { logsClickhouseClient, clickhouseClient } from "~/services/clickhouseInstance.server";
30-
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
34+
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
3135
import { PageBody } from "~/components/layout/AppLayout";
3236
import {
3337
ResizableHandle,
@@ -324,13 +328,23 @@ export default function Page() {
324328
}}
325329
title={<span className="font-mono text-xs">{ErrorId.toFriendlyId(fingerprint)}</span>}
326330
/>
331+
<PageAccessories>
332+
<LinkButton
333+
to={alertsHref}
334+
variant="secondary/small"
335+
LeadingIcon={BellAlertIcon}
336+
leadingIconClassName="text-alerts"
337+
>
338+
Configure alerts…
339+
</LinkButton>
340+
</PageAccessories>
327341
</NavBar>
328342

329343
<PageBody scrollable={false}>
330344
<Suspense
331345
fallback={
332-
<div className="my-2 flex items-center justify-center">
333-
<div className="mx-auto flex items-center gap-2">
346+
<div className="flex h-full items-center justify-center">
347+
<div className="flex items-center gap-2">
334348
<Spinner />
335349
<Paragraph variant="small">Loading error details…</Paragraph>
336350
</div>
@@ -366,7 +380,6 @@ export default function Page() {
366380
projectParam={projectParam}
367381
envParam={envParam}
368382
fingerprint={fingerprint}
369-
alertsHref={alertsHref}
370383
/>
371384
);
372385
}}
@@ -385,7 +398,6 @@ function ErrorGroupDetail({
385398
projectParam,
386399
envParam,
387400
fingerprint,
388-
alertsHref,
389401
}: {
390402
errorGroup: ErrorGroupSummary | undefined;
391403
runList: NextRunList | undefined;
@@ -394,7 +406,6 @@ function ErrorGroupDetail({
394406
projectParam: string;
395407
envParam: string;
396408
fingerprint: string;
397-
alertsHref: string;
398409
}) {
399410
const { value, values } = useSearchParams();
400411
const organization = useOrganization();
@@ -499,9 +510,12 @@ function ErrorGroupDetail({
499510
additionalTableState={{ errorId: ErrorId.toFriendlyId(fingerprint) }}
500511
/>
501512
) : (
502-
<Paragraph variant="small" className="p-4 text-text-dimmed">
503-
No runs found for this error.
504-
</Paragraph>
513+
<div className="flex flex-1 flex-col items-center justify-center gap-3">
514+
<IconBugFilled className="size-16 text-charcoal-650" />
515+
<Paragraph className="max-w-32 text-center text-text-dimmed">
516+
No runs found for this error.
517+
</Paragraph>
518+
</div>
505519
)}
506520
</div>
507521
</div>
@@ -510,11 +524,7 @@ function ErrorGroupDetail({
510524
{/* Right-hand detail sidebar */}
511525
<ResizableHandle id="error-detail-handle" />
512526
<ResizablePanel id="error-detail" min="280px" default="380px" max="500px" isStaticAtRest>
513-
<ErrorDetailSidebar
514-
errorGroup={errorGroup}
515-
fingerprint={fingerprint}
516-
alertsHref={alertsHref}
517-
/>
527+
<ErrorDetailSidebar errorGroup={errorGroup} fingerprint={fingerprint} />
518528
</ResizablePanel>
519529
</ResizablePanelGroup>
520530
);
@@ -523,24 +533,14 @@ function ErrorGroupDetail({
523533
function ErrorDetailSidebar({
524534
errorGroup,
525535
fingerprint,
526-
alertsHref,
527536
}: {
528537
errorGroup: ErrorGroupSummary;
529538
fingerprint: string;
530-
alertsHref: string;
531539
}) {
532540
return (
533541
<div className="grid h-full grid-rows-[auto_1fr] overflow-hidden bg-background-bright">
534-
<div className="flex items-center justify-between border-b border-grid-dimmed py-2 pl-3 pr-2">
542+
<div className="border-b border-grid-dimmed px-3 py-2">
535543
<Header2 className="truncate">Details</Header2>
536-
<LinkButton
537-
to={alertsHref}
538-
variant="secondary/small"
539-
LeadingIcon={BellAlertIcon}
540-
leadingIconClassName="text-alerts"
541-
>
542-
Configure alerts
543-
</LinkButton>
544544
</div>
545545
<div className="overflow-y-auto px-3 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
546546
<div className="flex flex-col gap-4">

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
statusActionToastMessage,
5656
} from "~/components/errors/ErrorStatusMenu";
5757
import { useToast } from "~/components/primitives/Toast";
58+
import { SimpleTooltip } from "~/components/primitives/Tooltip";
5859
import TooltipPortal from "~/components/primitives/TooltipPortal";
5960
import { appliedSummary, FilterMenuProvider, TimeFilter } from "~/components/runs/v3/SharedFilters";
6061
import { $replica } from "~/db.server";
@@ -463,7 +464,7 @@ function FiltersBar({
463464
LeadingIcon={BellAlertIcon}
464465
leadingIconClassName="text-alerts"
465466
>
466-
Configure alerts
467+
Configure alerts
467468
</LinkButton>
468469
{list && <ListPagination list={list} />}
469470
</div>
@@ -706,9 +707,14 @@ function ErrorActivityGraph({ activity }: { activity: ErrorOccurrenceActivity })
706707
</BarChart>
707708
</ResponsiveContainer>
708709
</div>
709-
<span className="-mt-1 text-xxs tabular-nums text-text-dimmed">
710-
{formatNumberCompact(maxCount)}
711-
</span>
710+
<SimpleTooltip
711+
button={
712+
<span className="-mt-1 text-xxs tabular-nums text-text-dimmed">
713+
{formatNumberCompact(maxCount)}
714+
</span>
715+
}
716+
content="Peak occurrences in a single time bucket"
717+
/>
712718
</div>
713719
);
714720
}

apps/webapp/app/v3/services/alerts/deliverAlert.server.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,15 +1182,19 @@ export class DeliverAlertService extends BaseService {
11821182
}
11831183

11841184
#formatTimestamp(date: Date): string {
1185-
return new Intl.DateTimeFormat("en-US", {
1186-
month: "short",
1187-
day: "numeric",
1188-
year: "numeric",
1189-
hour: "numeric",
1190-
minute: "2-digit",
1191-
second: "2-digit",
1192-
hour12: true,
1193-
}).format(date);
1185+
const unix = Math.floor(date.getTime() / 1000);
1186+
const fallback =
1187+
new Intl.DateTimeFormat("en-US", {
1188+
month: "short",
1189+
day: "numeric",
1190+
year: "numeric",
1191+
hour: "numeric",
1192+
minute: "2-digit",
1193+
second: "2-digit",
1194+
hour12: true,
1195+
timeZone: "UTC",
1196+
}).format(date) + " UTC";
1197+
return `<!date^${unix}^{date_short_pretty} {time_secs}|${fallback}>`;
11941198
}
11951199

11961200
#buildWebhookGitObject(git: GitMetaLinks | null) {

apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -383,15 +383,19 @@ export class DeliverErrorGroupAlertService {
383383
}
384384

385385
#formatTimestamp(date: Date): string {
386-
return new Intl.DateTimeFormat("en-US", {
387-
month: "short",
388-
day: "numeric",
389-
year: "numeric",
390-
hour: "numeric",
391-
minute: "2-digit",
392-
second: "2-digit",
393-
hour12: true,
394-
}).format(date);
386+
const unix = Math.floor(date.getTime() / 1000);
387+
const fallback =
388+
new Intl.DateTimeFormat("en-US", {
389+
month: "short",
390+
day: "numeric",
391+
year: "numeric",
392+
hour: "numeric",
393+
minute: "2-digit",
394+
second: "2-digit",
395+
hour12: true,
396+
timeZone: "UTC",
397+
}).format(date) + " UTC";
398+
return `<!date^${unix}^{date_short_pretty} {time_secs}|${fallback}>`;
395399
}
396400
}
397401

0 commit comments

Comments
 (0)