Skip to content

Commit 2c3cb4a

Browse files
authored
feat(webapp): UX improvements for TaskRun page and TaskRun table (#2760)
## ✅ Checklist - [x] I have followed every step in the [contributing guide](https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md) - [x] The PR title follows the convention. - [x] I ran and tested the code works --- ## Testing Manual testing of the task run pages --- ## Changelog - Add previous/next run navigation buttons to run detail page header - Support [ and ] keyboard shortcuts to jump between adjacent runs - Preserve runs table state (filters, pagination) when navigating - Preload adjacent page runs at boundaries for seamless navigation - Add actions prop to PageTitle component - Document shortcut in keyboard shortcuts panel - Store current filter state from runs table as `tableState` search param when navigating to individual run pages - Restore filters when navigating back from run detail view to runs list - Update `v3RunPath` and `v3RunSpanPath` helpers to accept optional searchParams - Use `useOptimisticLocation` to capture current search params in TaskRunsTable - Parse `tableState` param in run detail route and pass filters to back button - This improves UX by remembering filter selections (task, status, date range, etc.) when users click into a run and then navigate back to the runs list - Add new text-below variant that shows "Click to copy" tooltip on hover and "Copied" on click. Also add controlled open/onOpenChange props to SimpleTooltip for managing tooltip visibility. --- ## Screenshots https://github.com/user-attachments/assets/5067bbe0-1bcd-4e75-80a7-f56dabd5ed69
1 parent b71bf89 commit 2c3cb4a

File tree

8 files changed

+360
-64
lines changed

8 files changed

+360
-64
lines changed

apps/webapp/app/components/Shortcuts.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ function ShortcutContent() {
134134
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium/bright" />
135135
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium/bright" />
136136
</Shortcut>
137+
<Shortcut name="Jump to adjacent">
138+
<ShortcutKey shortcut={{ key: "[" }} variant="medium/bright" />
139+
<ShortcutKey shortcut={{ key: "]" }} variant="medium/bright" />
140+
</Shortcut>
137141
<Shortcut name="Expand all">
138142
<ShortcutKey shortcut={{ key: "e" }} variant="medium/bright" />
139143
</Shortcut>

apps/webapp/app/components/primitives/Buttons.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ export const LinkButton = ({
372372
<ExtLink
373373
href={to.toString()}
374374
ref={innerRef}
375-
className={cn("group/button focus-custom", props.fullWidth ? "w-full" : "")}
375+
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "")}
376376
onClick={onClick}
377377
onMouseDown={onMouseDown}
378378
onMouseEnter={onMouseEnter}
@@ -387,7 +387,7 @@ export const LinkButton = ({
387387
<Link
388388
to={to}
389389
ref={innerRef}
390-
className={cn("group/button focus-custom", props.fullWidth ? "w-full" : "")}
390+
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "")}
391391
onClick={onClick}
392392
onMouseDown={onMouseDown}
393393
onMouseEnter={onMouseEnter}
@@ -408,7 +408,7 @@ export const NavLinkButton = ({ to, className, target, ...props }: NavLinkPropsT
408408
return (
409409
<NavLink
410410
to={to}
411-
className={cn("group/button outline-none", props.fullWidth ? "w-full" : "")}
411+
className={cn("group/button outline-none block", props.fullWidth ? "w-full" : "")}
412412
target={target}
413413
>
414414
{({ isActive, isPending }) => (

apps/webapp/app/components/primitives/CopyableText.tsx

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,95 @@ import { useState } from "react";
33
import { SimpleTooltip } from "~/components/primitives/Tooltip";
44
import { useCopy } from "~/hooks/useCopy";
55
import { cn } from "~/utils/cn";
6+
import { Button } from "./Buttons";
67

78
export function CopyableText({
89
value,
910
copyValue,
1011
className,
1112
asChild,
13+
variant,
1214
}: {
1315
value: string;
1416
copyValue?: string;
1517
className?: string;
1618
asChild?: boolean;
19+
variant?: "icon-right" | "text-below";
1720
}) {
1821
const [isHovered, setIsHovered] = useState(false);
1922
const { copy, copied } = useCopy(copyValue ?? value);
2023

21-
return (
22-
<span
23-
className={cn("group relative inline-flex h-6 items-center", className)}
24-
onMouseLeave={() => setIsHovered(false)}
25-
>
26-
<span onMouseEnter={() => setIsHovered(true)}>{value}</span>
24+
const resolvedVariant = variant ?? "icon-right";
25+
26+
if (resolvedVariant === "icon-right") {
27+
return (
2728
<span
28-
onClick={copy}
29-
onMouseDown={(e) => e.stopPropagation()}
30-
className={cn(
31-
"absolute -right-6 top-0 z-10 size-6 font-sans",
32-
isHovered ? "flex" : "hidden"
33-
)}
29+
className={cn("group relative inline-flex h-6 items-center", className)}
30+
onMouseLeave={() => setIsHovered(false)}
3431
>
35-
<SimpleTooltip
36-
button={
37-
<span
38-
className={cn(
39-
"ml-1 flex size-6 items-center justify-center rounded border border-charcoal-650 bg-charcoal-750",
40-
asChild && "p-1",
41-
copied
42-
? "text-green-500"
43-
: "text-text-dimmed hover:border-charcoal-600 hover:bg-charcoal-700 hover:text-text-bright"
44-
)}
45-
>
46-
{copied ? (
47-
<ClipboardCheckIcon className="size-3.5" />
48-
) : (
49-
<ClipboardIcon className="size-3.5" />
50-
)}
51-
</span>
52-
}
53-
content={copied ? "Copied!" : "Copy"}
54-
className="font-sans"
55-
disableHoverableContent
56-
asChild={asChild}
57-
/>
32+
<span onMouseEnter={() => setIsHovered(true)}>{value}</span>
33+
<span
34+
onClick={copy}
35+
onMouseDown={(e) => e.stopPropagation()}
36+
className={cn(
37+
"absolute -right-6 top-0 z-10 size-6 font-sans",
38+
isHovered ? "flex" : "hidden"
39+
)}
40+
>
41+
<SimpleTooltip
42+
button={
43+
<span
44+
className={cn(
45+
"ml-1 flex size-6 items-center justify-center rounded border border-charcoal-650 bg-charcoal-750",
46+
asChild && "p-1",
47+
copied
48+
? "text-green-500"
49+
: "text-text-dimmed hover:border-charcoal-600 hover:bg-charcoal-700 hover:text-text-bright"
50+
)}
51+
>
52+
{copied ? (
53+
<ClipboardCheckIcon className="size-3.5" />
54+
) : (
55+
<ClipboardIcon className="size-3.5" />
56+
)}
57+
</span>
58+
}
59+
content={copied ? "Copied!" : "Copy"}
60+
className="font-sans"
61+
disableHoverableContent
62+
asChild={asChild}
63+
/>
64+
</span>
5865
</span>
59-
</span>
60-
);
66+
);
67+
}
68+
69+
if (resolvedVariant === "text-below") {
70+
return (
71+
<SimpleTooltip
72+
button={
73+
<Button
74+
variant="minimal/small"
75+
onClick={(e) => {
76+
e.stopPropagation();
77+
copy();
78+
}}
79+
className={cn(
80+
"cursor-pointer bg-transparent py-0 px-1 text-left text-text-bright transition-colors hover:text-white hover:bg-transparent",
81+
className
82+
)}
83+
>
84+
<span>{value}</span>
85+
</Button>
86+
}
87+
content={copied ? "Copied" : "Click to copy"}
88+
className="font-sans px-2 py-1"
89+
disableHoverableContent
90+
open={isHovered || copied}
91+
onOpenChange={setIsHovered}
92+
/>
93+
);
94+
}
95+
96+
return null;
6197
}

apps/webapp/app/components/primitives/ShortcutKey.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { useOperatingSystem } from "./OperatingSystemProvider";
99
import { KeyboardEnterIcon } from "~/assets/icons/KeyboardEnterIcon";
1010

1111
const medium =
12-
"text-[0.75rem] font-medium min-w-[17px] rounded-[2px] tabular-nums px-1 ml-1 -mr-0.5 flex items-center gap-x-1.5 border border-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-dimmed/60 transition uppercase";
12+
"justify-center min-w-[1.25rem] min-h-[1.25rem] text-[0.65rem] font-mono font-medium rounded-[2px] tabular-nums px-1 ml-1 -mr-0.5 flex items-center gap-x-1.5 border border-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-dimmed/60 transition uppercase";
1313

1414
export const variants = {
1515
small:
16-
"text-[0.6rem] font-medium min-w-[17px] rounded-[2px] tabular-nums px-1 ml-1 -mr-0.5 flex items-center gap-x-1 border border-text-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-text-dimmed/60 transition uppercase",
16+
"justify-center text-[0.6rem] font-mono font-medium min-w-[1rem] min-h-[1rem] rounded-[2px] tabular-nums px-1 ml-1 -mr-0.5 flex items-center gap-x-1 border border-text-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-text-dimmed/60 transition uppercase",
1717
medium: cn(medium, "group-hover:border-charcoal-550"),
1818
"medium/bright": cn(medium, "bg-charcoal-750 text-text-bright border-charcoal-650"),
1919
};
@@ -57,7 +57,7 @@ export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps)
5757
function keyString(key: string, isMac: boolean, variant: "small" | "medium" | "medium/bright") {
5858
key = key.toLowerCase();
5959

60-
const className = variant === "small" ? "w-2.5 h-4" : "w-3 h-5";
60+
const className = variant === "small" ? "w-2.5 h-4" : "w-2.5 h-4.5";
6161

6262
switch (key) {
6363
case "enter":

apps/webapp/app/components/primitives/Tooltip.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { cn } from "~/utils/cn";
66
const variantClasses = {
77
basic:
88
"bg-background-bright border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
9-
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50",
9+
dark: "bg-background-dimmed border border-grid-bright rounded px-3 py-2 text-sm text-text-bright shadow-md fade-in-50"
1010
};
1111

1212
type Variant = keyof typeof variantClasses;
@@ -64,6 +64,8 @@ function SimpleTooltip({
6464
buttonStyle,
6565
asChild = false,
6666
sideOffset,
67+
open,
68+
onOpenChange,
6769
}: {
6870
button: React.ReactNode;
6971
content: React.ReactNode;
@@ -76,10 +78,12 @@ function SimpleTooltip({
7678
buttonStyle?: React.CSSProperties;
7779
asChild?: boolean;
7880
sideOffset?: number;
81+
open?: boolean;
82+
onOpenChange?: (open: boolean) => void;
7983
}) {
8084
return (
8185
<TooltipProvider disableHoverableContent={disableHoverableContent}>
82-
<Tooltip>
86+
<Tooltip open={open} onOpenChange={onOpenChange}>
8387
<TooltipTrigger
8488
tabIndex={-1}
8589
className={cn("h-fit", buttonClassName)}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
filterableTaskRunStatuses,
5656
TaskRunStatusCombo,
5757
} from "./TaskRunStatus";
58+
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
5859

5960
type RunsTableProps = {
6061
total: number;
@@ -81,6 +82,8 @@ export function TaskRunsTable({
8182
const checkboxes = useRef<(HTMLInputElement | null)[]>([]);
8283
const { has, hasAll, select, deselect, toggle } = useSelectedItems(allowSelection);
8384
const { isManagedCloud } = useFeatures();
85+
const location = useOptimisticLocation();
86+
const tableStateParam = encodeURIComponent(location.search ? `${location.search}&rt=1` : "rt=1");
8487

8588
const showCompute = isManagedCloud;
8689

@@ -293,16 +296,20 @@ export function TaskRunsTable({
293296
<BlankState isLoading={isLoading} filters={filters} />
294297
) : (
295298
runs.map((run, index) => {
299+
const searchParams = new URLSearchParams();
300+
if (tableStateParam) {
301+
searchParams.set("tableState", tableStateParam);
302+
}
296303
const path = v3RunSpanPath(organization, project, run.environment, run, {
297304
spanId: run.spanId,
298-
});
305+
}, searchParams);
299306
return (
300307
<TableRow key={run.id}>
301308
{allowSelection && (
302309
<TableCell className="pl-3 pr-0">
303310
<Checkbox
304311
checked={has(run.friendlyId)}
305-
onChange={(element) => {
312+
onChange={() => {
306313
toggle(run.friendlyId);
307314
}}
308315
ref={(r) => {

0 commit comments

Comments
 (0)