Skip to content

Commit 0818772

Browse files
committed
Link to bulk replay. Better styling
1 parent 1f58e42 commit 0818772

File tree

4 files changed

+100
-47
lines changed

4 files changed

+100
-47
lines changed

apps/webapp/app/components/runs/v3/RunFilters.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as Ariakit from "@ariakit/react";
22
import {
3-
BugAntIcon,
43
CalendarIcon,
54
ClockIcon,
65
FingerPrintIcon,
@@ -10,7 +9,7 @@ import {
109
XMarkIcon,
1110
} from "@heroicons/react/20/solid";
1211
import { Form, useFetcher } from "@remix-run/react";
13-
import { IconRotateClockwise2, IconToggleLeft } from "@tabler/icons-react";
12+
import { IconBugFilled, IconRotateClockwise2, IconToggleLeft } from "@tabler/icons-react";
1413
import { MachinePresetName } from "@trigger.dev/core/v3";
1514
import type { BulkActionType, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database";
1615
import { ListFilterIcon } from "lucide-react";
@@ -263,7 +262,7 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
263262
case "versions":
264263
return <IconRotateClockwise2 className="size-4" />;
265264
case "errorId":
266-
return <BugAntIcon className="size-4" />;
265+
return <IconBugFilled className="size-4" />;
267266
default:
268267
return undefined;
269268
}
@@ -388,7 +387,7 @@ const filterTypes = [
388387
{ name: "batch", title: "Batch ID", icon: <Squares2X2Icon className="size-4" /> },
389388
{ name: "schedule", title: "Schedule ID", icon: <ClockIcon className="size-4" /> },
390389
{ name: "bulk", title: "Bulk action", icon: <ListCheckedIcon className="size-4" /> },
391-
{ name: "error", title: "Error ID", icon: <BugAntIcon className="size-4" /> },
390+
{ name: "error", title: "Error ID", icon: <IconBugFilled className="size-4" /> },
392391
] as const;
393392

394393
type FilterType = (typeof filterTypes)[number]["name"];
@@ -667,7 +666,7 @@ function TasksDropdown({
667666
<TaskTriggerSourceIcon source={item.triggerSource} className="size-4 flex-none" />
668667
}
669668
>
670-
<MiddleTruncate text={item.slug}/>
669+
<MiddleTruncate text={item.slug} />
671670
</SelectItem>
672671
))}
673672
</SelectList>

apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
import { z } from "zod";
22
import { type ClickHouse, msToClickHouseInterval } from "@internal/clickhouse";
33
import { TimeGranularity } from "~/utils/timeGranularity";
4-
5-
const errorGroupGranularity = new TimeGranularity([
6-
{ max: "1h", granularity: "1m" },
7-
{ max: "1d", granularity: "30m" },
8-
{ max: "1w", granularity: "8h" },
9-
{ max: "31d", granularity: "1d" },
10-
{ max: "45d", granularity: "1w" },
11-
{ max: "Infinity", granularity: "30d" },
12-
]);
134
import { ErrorId } from "@trigger.dev/core/v3/isomorphic";
145
import { type PrismaClientOrTransaction } from "@trigger.dev/database";
156
import { timeFilterFromTo } from "~/components/runs/v3/SharedFilters";
@@ -23,6 +14,15 @@ import {
2314
} from "~/presenters/v3/NextRunListPresenter.server";
2415
import { sortVersionsDescending } from "~/utils/semver";
2516

17+
const errorGroupGranularity = new TimeGranularity([
18+
{ max: "1h", granularity: "1m" },
19+
{ max: "1d", granularity: "20m" },
20+
{ max: "1w", granularity: "2h" },
21+
{ max: "31d", granularity: "12h" },
22+
{ max: "60d", granularity: "1w" },
23+
{ max: "Infinity", granularity: "30d" },
24+
]);
25+
2626
export type ErrorGroupOptions = {
2727
userId?: string;
2828
projectId: string;
@@ -287,6 +287,7 @@ export class ErrorGroupPresenter extends BasePresenter {
287287
const result = await runListPresenter.call(organizationId, environmentId, {
288288
userId: options.userId,
289289
projectId: options.projectId,
290+
rootOnly: false,
290291
errorId: ErrorId.toFriendlyId(options.fingerprint),
291292
pageSize: options.pageSize,
292293
from: options.from,

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

Lines changed: 85 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { type MetaFunction } from "@remix-run/react";
33
import { ServiceValidationError } from "~/v3/services/baseService.server";
44
import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson";
55
import { requireUser } from "~/services/session.server";
6-
import { EnvironmentParamSchema, v3ErrorsPath } from "~/utils/pathBuilder";
6+
import {
7+
EnvironmentParamSchema,
8+
v3CreateBulkActionPath,
9+
v3ErrorsPath,
10+
v3RunsPath,
11+
} from "~/utils/pathBuilder";
712
import { findProjectBySlug } from "~/models/project.server";
813
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
914
import {
@@ -21,17 +26,26 @@ import { Suspense, useMemo } from "react";
2126
import { Spinner } from "~/components/primitives/Spinner";
2227
import { Paragraph } from "~/components/primitives/Paragraph";
2328
import { Callout } from "~/components/primitives/Callout";
24-
import { Header2, Header3 } from "~/components/primitives/Headers";
29+
import { Header1, Header2, Header3 } from "~/components/primitives/Headers";
2530
import { formatDistanceToNow } from "date-fns";
2631
import { formatNumberCompact } from "~/utils/numberFormatter";
2732
import * as Property from "~/components/primitives/PropertyTable";
2833
import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
29-
import { DateTime } from "~/components/primitives/DateTime";
34+
import { DateTime, RelativeDateTime } from "~/components/primitives/DateTime";
3035
import { ErrorId } from "@trigger.dev/core/v3/isomorphic";
3136
import { Chart, type ChartConfig } from "~/components/primitives/charts/ChartCompound";
3237
import { TimeFilter, timeFilterFromTo } from "~/components/runs/v3/SharedFilters";
3338
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
3439
import { DirectionSchema, ListPagination } from "~/components/ListPagination";
40+
import { LinkButton } from "~/components/primitives/Buttons";
41+
import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon";
42+
import { useOrganization } from "~/hooks/useOrganizations";
43+
import { useProject } from "~/hooks/useProject";
44+
import { useEnvironment } from "~/hooks/useEnvironment";
45+
import { RunsIcon } from "~/assets/icons/RunsIcon";
46+
import { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
47+
import { useSearchParams } from "~/hooks/useSearchParam";
48+
import { CopyableText } from "~/components/primitives/CopyableText";
3549

3650
export const meta: MetaFunction<typeof loader> = ({ data }) => {
3751
return [
@@ -218,6 +232,11 @@ function ErrorGroupDetail({
218232
envParam: string;
219233
fingerprint: string;
220234
}) {
235+
const { value } = useSearchParams();
236+
const organization = useOrganization();
237+
const project = useProject();
238+
const environment = useEnvironment();
239+
221240
if (!errorGroup) {
222241
return (
223242
<div className="flex h-full items-center justify-center">
@@ -231,37 +250,58 @@ function ErrorGroupDetail({
231250
);
232251
}
233252

253+
const fromValue = value("from") ?? undefined;
254+
const toValue = value("to") ?? undefined;
255+
256+
const filters: TaskRunListSearchFilters = {
257+
period: value("period") ?? undefined,
258+
from: fromValue ? parseInt(fromValue, 10) : undefined,
259+
to: toValue ? parseInt(toValue, 10) : undefined,
260+
rootOnly: false,
261+
errorId: ErrorId.toFriendlyId(fingerprint),
262+
};
263+
234264
return (
235-
<div className="grid h-full grid-rows-[auto_16rem_1fr] overflow-hidden">
265+
<div className="grid h-full grid-rows-[auto_12rem_1fr] overflow-hidden">
236266
{/* Error Summary */}
237-
<div className="border-b border-grid-bright p-4">
238-
<Header2 className="mb-4">{errorGroup.errorMessage}</Header2>
239-
240-
<div className="mb-4">
241-
<TimeFilter defaultPeriod="7d" labelName="Occurred" />
267+
<div className="flex flex-col gap-2 border-b border-grid-bright bg-background-bright p-4">
268+
<div className="flex flex-col gap-0.5">
269+
<Header2>{errorGroup.errorMessage}</Header2>
270+
<Header3>{formatNumberCompact(errorGroup.count)} total occurrences</Header3>
242271
</div>
243272

244-
<div className="grid grid-cols-3 gap-x-12 gap-y-1">
273+
<div className="grid grid-cols-[auto_auto_auto_1fr] gap-x-12 gap-y-0.5">
245274
<Property.Table>
246275
<Property.Item>
247276
<Property.Label>ID</Property.Label>
248277
<Property.Value>
249-
<span className="font-mono">{ErrorId.toFriendlyId(errorGroup.fingerprint)}</span>
278+
<CopyableText value={ErrorId.toFriendlyId(errorGroup.fingerprint)} />
250279
</Property.Value>
251280
</Property.Item>
252281
<Property.Item>
253282
<Property.Label>Task</Property.Label>
254283
<Property.Value>
255-
<span className="font-mono">{errorGroup.taskIdentifier}</span>
284+
<CopyableText value={errorGroup.taskIdentifier} />
256285
</Property.Value>
257286
</Property.Item>
258287
</Property.Table>
259288

260289
<Property.Table>
261290
<Property.Item>
262-
<Property.Label>Total occurrences</Property.Label>
263-
<Property.Value>{formatNumberCompact(errorGroup.count)}</Property.Value>
291+
<Property.Label>First seen</Property.Label>
292+
<Property.Value>
293+
<DateTime date={errorGroup.firstSeen} />
294+
</Property.Value>
295+
</Property.Item>
296+
<Property.Item>
297+
<Property.Label>Last seen</Property.Label>
298+
<Property.Value>
299+
<RelativeDateTime date={errorGroup.lastSeen} />
300+
</Property.Value>
264301
</Property.Item>
302+
</Property.Table>
303+
304+
<Property.Table>
265305
{errorGroup.affectedVersions.length > 0 && (
266306
<Property.Item>
267307
<Property.Label>Affected versions</Property.Label>
@@ -273,27 +313,15 @@ function ErrorGroupDetail({
273313
</Property.Item>
274314
)}
275315
</Property.Table>
276-
277-
<Property.Table>
278-
<Property.Item>
279-
<Property.Label>First seen</Property.Label>
280-
<Property.Value>
281-
<DateTime date={errorGroup.firstSeen} />
282-
</Property.Value>
283-
</Property.Item>
284-
<Property.Item>
285-
<Property.Label>Last seen</Property.Label>
286-
<Property.Value>
287-
{formatDistanceToNow(errorGroup.lastSeen, { addSuffix: true })}
288-
</Property.Value>
289-
</Property.Item>
290-
</Property.Table>
291316
</div>
292317
</div>
293318

294319
{/* Activity chart */}
295-
<div className="flex flex-col overflow-hidden border-b border-grid-bright px-4 py-3">
296-
<Header3 className="mb-2 shrink-0">Activity</Header3>
320+
<div className="flex flex-col gap-3 overflow-hidden border-b border-grid-bright px-4 py-3">
321+
<div className="flex items-center">
322+
<TimeFilter defaultPeriod="7d" labelName="Occurred" />
323+
</div>
324+
297325
<Suspense fallback={<ActivityChartBlankState />}>
298326
<TypedAwait resolve={activity} errorElement={<ActivityChartBlankState />}>
299327
{(result) =>
@@ -311,7 +339,32 @@ function ErrorGroupDetail({
311339
<div className="flex flex-col gap-1 overflow-y-hidden">
312340
<div className="flex items-center justify-between px-4">
313341
<Header3 className="mb-1 mt-2">Runs</Header3>
314-
{runList && <ListPagination list={runList} />}
342+
{runList && (
343+
<div className="flex items-center gap-2">
344+
<LinkButton
345+
variant="secondary/small"
346+
to={v3RunsPath(organization, project, environment, filters)}
347+
LeadingIcon={RunsIcon}
348+
>
349+
View all runs
350+
</LinkButton>
351+
<LinkButton
352+
variant="secondary/small"
353+
to={v3CreateBulkActionPath(
354+
organization,
355+
project,
356+
environment,
357+
filters,
358+
"filter",
359+
"replay"
360+
)}
361+
LeadingIcon={ListCheckedIcon}
362+
>
363+
Bulk replay…
364+
</LinkButton>
365+
<ListPagination list={runList} />
366+
</div>
367+
)}
315368
</div>
316369
{runList ? (
317370
<TaskRunsTable

apps/webapp/app/utils/pathBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ export function v3CreateBulkActionPath(
325325
project: ProjectForPath,
326326
environment: EnvironmentForPath,
327327
filters?: TaskRunListSearchFilters,
328-
mode?: "selected" | "filters",
328+
mode?: "selected" | "filter",
329329
action?: "replay" | "cancel"
330330
) {
331331
const searchParams = objectToSearchParams(filters) ?? new URLSearchParams();

0 commit comments

Comments
 (0)