Skip to content

Commit 99f72f7

Browse files
authored
Merge branch 'main' into feature/tri-13446-redis-snapshot-store
2 parents 0daa273 + 910011d commit 99f72f7

61 files changed

Lines changed: 4663 additions & 800 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
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+
Customize the runs list: show, hide, and reorder columns, and add smart columns that pull a value straight out of a run's payload, metadata, or output. Your column choices are saved in the page URL, so you can share a view, bookmark it, or save it straight to your favorites.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
New Vercel connections now get version skew protection turned on automatically, so each run uses the task version its deployment shipped with. Automatic atomic deployments are deprecated and no longer offered when you connect a project, but stay available in your Vercel integration settings.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function ColumnsIcon({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" strokeWidth="2" />
5+
<line x1="9" y1="19" x2="9" y2="5" stroke="currentColor" strokeWidth="2" />
6+
<line x1="15" y1="19" x2="15" y2="5" stroke="currentColor" strokeWidth="2" />
7+
</svg>
8+
);
9+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function ResetIcon({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M7 3L4 6L7 9"
6+
stroke="currentColor"
7+
strokeWidth="2"
8+
strokeLinecap="round"
9+
strokeLinejoin="round"
10+
/>
11+
<path
12+
d="M5 6H13.5C17.0899 6 20 8.91015 20 12.5C20 16.0899 17.0899 19 13.5 19H6"
13+
stroke="currentColor"
14+
strokeWidth="2"
15+
strokeLinecap="round"
16+
strokeLinejoin="round"
17+
/>
18+
</svg>
19+
);
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** Marks a smart column: in the runs table header, the Columns popover, and the dialog preview. */
2+
export function SmartColumnIcon({ className }: { className?: string }) {
3+
return (
4+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5+
<path
6+
d="M5.94723 12.4318L12.3011 3.53646C12.9468 2.63242 14.3689 3.24855 14.1511 4.33794L13.1543 9.32131C13.0905 9.64031 13.3346 9.93793 13.6599 9.93793H17.2138C18.0524 9.93793 18.5402 10.8859 18.0527 11.5682L11.6989 20.4636C11.0532 21.3676 9.63107 20.7515 9.84895 19.6621L10.8456 14.6788C10.9095 14.3598 10.6654 14.0621 10.3401 14.0621H6.78622C5.9476 14.0621 5.45978 13.1142 5.94723 12.4318Z"
7+
stroke="currentColor"
8+
strokeWidth="2"
9+
strokeLinejoin="round"
10+
/>
11+
</svg>
12+
);
13+
}

apps/webapp/app/components/Shortcuts.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ASK_AI_SHORTCUT, askAiCanOpen } from "~/components/dashboard-agent/ask-
66
import { useDashboardAgentAvailable } from "~/components/dashboard-agent/dashboardAgentOpenRequest";
77
import { NEW_CHAT_SHORTCUT } from "~/components/dashboard-agent/DashboardAgentHeader";
88
import { TOGGLE_PANEL_SHORTCUT } from "~/components/dashboard-agent/dashboardAgentLauncher";
9+
import { COLUMNS_SHORTCUT } from "~/components/runs/v3/RunsDisplayOptions";
910
import { useAskAiAvailability } from "~/hooks/useAskAiAvailability";
1011
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
1112
import { Header3 } from "./primitives/Headers";
@@ -142,6 +143,9 @@ function ShortcutContent() {
142143
)}
143144
<div className="space-y-3">
144145
<Header3>Runs page</Header3>
146+
<Shortcut name="Customize columns">
147+
<ShortcutKey shortcut={COLUMNS_SHORTCUT} variant="medium/bright" />
148+
</Shortcut>
145149
<Shortcut name="Bulk action: Cancel runs">
146150
<ShortcutKey shortcut={{ key: "c" }} variant="medium/bright" />
147151
</Shortcut>

apps/webapp/app/components/integrations/VercelBuildSettings.tsx

Lines changed: 89 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Switch } from "~/components/primitives/Switch";
22
import { LinkButton } from "~/components/primitives/Buttons";
3+
import { Badge } from "~/components/primitives/Badge";
34
import { Label } from "~/components/primitives/Label";
45
import {
56
SettingsRow,
67
SettingsRowDescription,
78
SettingsRowTitle,
89
} from "~/components/primitives/SettingsLayout";
910
import { cn } from "~/utils/cn";
11+
import { docsPath } from "~/utils/pathBuilder";
1012
import { Hint } from "~/components/primitives/Hint";
1113
import { TextLink } from "~/components/primitives/TextLink";
1214
import { SimpleTooltip } from "~/components/primitives/Tooltip";
@@ -17,6 +19,16 @@ import {
1719
} from "~/components/environments/EnvironmentLabel";
1820
import { envSlugToType, type EnvSlug } from "~/v3/vercel/vercelProjectIntegrationSchema";
1921

22+
export const SKEW_PROTECTION_DOCS_PATH = docsPath("deployment/version-skew-protection");
23+
24+
const SKEW_PROTECTION_MIN_SDK_VERSION: string | null = "4.5.12";
25+
26+
export function skewProtectionVersionRequirement(): string {
27+
return SKEW_PROTECTION_MIN_SDK_VERSION
28+
? `from SDK and CLI v${SKEW_PROTECTION_MIN_SDK_VERSION} and later`
29+
: "from a recent SDK and CLI — see the docs for the exact version";
30+
}
31+
2032
type BuildSettingsFieldsProps = {
2133
availableEnvSlugs: EnvSlug[];
2234
pullEnvVarsBeforeBuild: EnvSlug[];
@@ -38,6 +50,7 @@ type BuildSettingsFieldsProps = {
3850
currentTriggerVersionFetchFailed?: boolean;
3951
/** Hide the section-level master toggles for "Pull env vars" and "Discover new env vars". */
4052
hideSectionToggles?: boolean;
53+
showAtomicDeployments?: boolean;
4154
layout?: "settings" | "card";
4255
};
4356

@@ -56,6 +69,7 @@ export function BuildSettingsFields({
5669
currentTriggerVersion,
5770
currentTriggerVersionFetchFailed,
5871
hideSectionToggles,
72+
showAtomicDeployments = true,
5973
layout = "card",
6074
}: BuildSettingsFieldsProps) {
6175
const isSlugDisabled = (slug: EnvSlug) => !!disabledEnvSlugs?.[slug];
@@ -126,7 +140,7 @@ export function BuildSettingsFields({
126140
) : null;
127141

128142
const atomicSections =
129-
layout === "settings" ? (
143+
layout === "settings" && showAtomicDeployments ? (
130144
<>
131145
<SettingsRow
132146
action={
@@ -140,11 +154,25 @@ export function BuildSettingsFields({
140154
}
141155
>
142156
<div className="flex-1 space-y-1">
143-
<SettingsRowTitle>Atomic deployments</SettingsRowTitle>
157+
<SettingsRowTitle>
158+
<span className="flex items-center gap-2">
159+
Atomic deployments <DeprecatedBadge />
160+
</span>
161+
</SettingsRowTitle>
144162
<SettingsRowDescription>
145-
Promotes your Vercel deployment and your tasks together in Production, so your app
146-
never runs against a mismatched task version. Requires turning off "Auto-assign Custom
147-
Production Domains" on your Vercel project, which Trigger.dev does for you.{" "}
163+
Version skew protection replaces this. It pins every run to the deployment that
164+
triggered it, and works on its own {skewProtectionVersionRequirement()}. Atomic
165+
deployments still work, so turn this off whenever you're ready.{" "}
166+
<TextLink href={SKEW_PROTECTION_DOCS_PATH} target="_blank">
167+
Read about version skew protection
168+
</TextLink>
169+
.
170+
</SettingsRowDescription>
171+
<SettingsRowDescription>
172+
Atomic deployments promote your Vercel deployment and your tasks together in
173+
Production, so your app never runs against a mismatched task version. This needs
174+
"Auto-assign Custom Production Domains" turned off on your Vercel project, and
175+
Trigger.dev takes care of that for you.{" "}
148176
<TextLink
149177
href="https://trigger.dev/docs/vercel-integration#atomic-deployments"
150178
target="_blank"
@@ -172,7 +200,7 @@ export function BuildSettingsFields({
172200
{atomicBuilds.includes("prod") && onAutoPromoteChange !== undefined && (
173201
<SettingsRow
174202
title="Auto promotion"
175-
description="Once your tasks finish deploying, Trigger.dev promotes the Vercel deployment for you. Turn this off to promote from the Vercel dashboard yourself, and Trigger.dev will follow as soon as you do."
203+
description="Part of atomic deployments, and only used while they are on. Once your tasks finish deploying, Trigger.dev promotes the Vercel deployment for you. Turn this off to promote from the Vercel dashboard yourself, and Trigger.dev will follow as soon as you do."
176204
action={
177205
<Switch
178206
variant="medium"
@@ -333,10 +361,14 @@ export function BuildSettingsFields({
333361
{atomicSections}
334362

335363
{/* Atomic deployments */}
336-
{layout === "card" && (
364+
{layout === "card" && showAtomicDeployments && (
337365
<div>
338366
<div className="flex items-center justify-between">
339-
<Label>Atomic deployments</Label>
367+
<Label>
368+
<span className="flex items-center gap-2">
369+
Atomic deployments <DeprecatedBadge />
370+
</span>
371+
</Label>
340372
<Switch
341373
variant="small"
342374
checked={atomicBuilds.includes("prod")}
@@ -346,10 +378,18 @@ export function BuildSettingsFields({
346378
/>
347379
</div>
348380
<Hint className="pr-6">
349-
When enabled, production deployments wait for Vercel deployment to complete before
350-
promoting the Trigger.dev deployment. This will disable the "Auto-assign Custom
351-
Production Domains" option in your Vercel project settings to perform staged
352-
deployments.{" "}
381+
Version skew protection replaces this, and works on its own{" "}
382+
{skewProtectionVersionRequirement()}.{" "}
383+
<TextLink href={SKEW_PROTECTION_DOCS_PATH} target="_blank">
384+
Read about version skew protection
385+
</TextLink>
386+
.
387+
</Hint>
388+
<Hint className="pr-6">
389+
Atomic deployments promote your Vercel deployment and your tasks together in Production,
390+
so your app never runs against a mismatched task version. This needs "Auto-assign Custom
391+
Production Domains" turned off on your Vercel project, and Trigger.dev takes care of
392+
that for you.{" "}
353393
<TextLink
354394
href="https://trigger.dev/docs/vercel-integration#atomic-deployments"
355395
target="_blank"
@@ -375,27 +415,48 @@ export function BuildSettingsFields({
375415
)}
376416

377417
{/* Auto promotion — only visible when atomic deployments are on */}
378-
{layout === "card" && atomicBuilds.includes("prod") && onAutoPromoteChange !== undefined && (
379-
<div>
380-
<div className="flex items-center justify-between">
381-
<Label>Auto promotion</Label>
382-
<Switch
383-
variant="small"
384-
checked={autoPromote ?? true}
385-
onCheckedChange={onAutoPromoteChange}
386-
/>
418+
{layout === "card" &&
419+
showAtomicDeployments &&
420+
atomicBuilds.includes("prod") &&
421+
onAutoPromoteChange !== undefined && (
422+
<div>
423+
<div className="flex items-center justify-between">
424+
<Label>Auto promotion</Label>
425+
<Switch
426+
variant="small"
427+
checked={autoPromote ?? true}
428+
onCheckedChange={onAutoPromoteChange}
429+
/>
430+
</div>
431+
<Hint className="pr-6">
432+
When enabled, the integration automatically promotes the Vercel deployment after the
433+
Trigger.dev build completes. Turn off to manually promote from your Vercel dashboard —
434+
Trigger.dev will then promote automatically once you do.
435+
</Hint>
387436
</div>
388-
<Hint className="pr-6">
389-
When enabled, the integration automatically promotes the Vercel deployment after the
390-
Trigger.dev build completes. Turn off to manually promote from your Vercel dashboard —
391-
Trigger.dev will then promote automatically once you do.
392-
</Hint>
393-
</div>
394-
)}
437+
)}
395438
</>
396439
);
397440
}
398441

442+
function DeprecatedBadge() {
443+
return (
444+
<SimpleTooltip
445+
asChild
446+
button={
447+
<Badge
448+
variant="extra-small"
449+
className="text-warning system:border-transparent system:bg-warning system:text-white"
450+
>
451+
Deprecated
452+
</Badge>
453+
}
454+
content="Use version skew protection instead"
455+
disableHoverableContent
456+
/>
457+
);
458+
}
459+
399460
function EnvToggleRow({
400461
slug,
401462
checked,

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export function VercelOnboardingModal({
235235
const [pullEnvVarsBeforeBuild, setPullEnvVarsBeforeBuild] = useState<EnvSlug[]>(
236236
() => availableEnvSlugsForOnboardingBuildSettings
237237
);
238-
const [atomicBuilds, setAtomicBuilds] = useState<EnvSlug[]>(() => ["prod"]);
238+
const [atomicBuilds, setAtomicBuilds] = useState<EnvSlug[]>([]);
239239
const [discoverEnvVars, setDiscoverEnvVars] = useState<EnvSlug[]>(
240240
() => availableEnvSlugsForOnboardingBuildSettings
241241
);
@@ -1164,7 +1164,7 @@ export function VercelOnboardingModal({
11641164
<div className="flex flex-col gap-4">
11651165
<Header3>Build Settings</Header3>
11661166
<Paragraph className="text-sm">
1167-
Configure how environment variables are pulled during builds and atomic deployments.
1167+
Configure how environment variables are pulled during builds.
11681168
</Paragraph>
11691169

11701170
<BuildSettingsFields
@@ -1176,6 +1176,7 @@ export function VercelOnboardingModal({
11761176
atomicBuilds={atomicBuilds}
11771177
onAtomicBuildsChange={setAtomicBuilds}
11781178
disabledEnvSlugs={disabledEnvSlugsForBuildSettings}
1179+
showAtomicDeployments={false}
11791180
/>
11801181

11811182
<FormButtons

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

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
import { StarIcon as StarIconOutline } from "@heroicons/react/24/outline";
22
import { StarIcon as StarIconSolid } from "@heroicons/react/20/solid";
3-
import { useFetcher, useLocation, useSearchParams } from "@remix-run/react";
3+
import { useLocation, useSearchParams } from "@remix-run/react";
44
import { useEffect } from "react";
5-
import { useIsImpersonating } from "~/hooks/useOrganizations";
65
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
76
import { useOptionalUser } from "~/hooks/useUser";
87
import { cn } from "~/utils/cn";
98
import { Button } from "../primitives/Buttons";
109
import { ShortcutKey } from "../primitives/ShortcutKey";
1110
import { SimpleTooltip } from "../primitives/Tooltip";
12-
import {
13-
buildFavoriteLabel,
14-
canonicalFavoriteUrl,
15-
FAVORITE_SEARCH_PARAM,
16-
FAVORITES_ACTION_PATH,
17-
favoritePageUrl,
18-
resolvePageMeta,
19-
useFavorites,
20-
} from "./favoritePages";
11+
import { FAVORITE_SEARCH_PARAM, useFavoritePageToggle, useFavorites } from "./favoritePages";
2112

2213
/**
2314
* The star in the page header that favorites the current page (full URL, including filters and
@@ -31,15 +22,10 @@ export function FavoritePageButton({
3122
className?: string;
3223
}) {
3324
const user = useOptionalUser();
34-
const isImpersonating = useIsImpersonating();
3525
const location = useLocation();
3626
const favorites = useFavorites();
37-
const fetcher = useFetcher();
3827
const [, setSearchParams] = useSearchParams();
39-
40-
// The marker param and pagination position never count toward URL identity, so paging through
41-
// a favorited view keeps the same favorite (and never saves a soon-stale cursor)
42-
const url = favoritePageUrl(location.pathname, location.search);
28+
const { isFavorited, pageName, canFavorite, toggle } = useFavoritePageToggle(pageTitle);
4329

4430
// A marker that isn't one of this user's favorites came from a shared link (or a favorite
4531
// that's since been removed): clean it from the URL so the page behaves like a normal visit.
@@ -58,34 +44,8 @@ export function FavoritePageButton({
5844
{ replace: true, preventScrollReset: true }
5945
);
6046
}, [hasForeignMarker, setSearchParams]);
61-
const existing = favorites.find((favorite) => canonicalFavoriteUrl(favorite.url) === url);
62-
const isFavorited = existing !== undefined;
63-
// The tooltip names the favorite: its custom name once saved, else the label saving would use
64-
// (which includes detail-page ids and filter summaries, e.g. "Runs: Completed, last 7d")
65-
const pageName =
66-
existing?.label ?? buildFavoriteLabel(location.pathname, location.search, pageTitle);
67-
68-
const toggle = () => {
69-
if (existing) {
70-
fetcher.submit(
71-
{ intent: "remove", id: existing.id },
72-
{ method: "POST", action: FAVORITES_ACTION_PATH }
73-
);
74-
} else {
75-
fetcher.submit(
76-
{
77-
intent: "add",
78-
id: crypto.randomUUID(),
79-
url,
80-
label: buildFavoriteLabel(location.pathname, location.search, pageTitle),
81-
icon: resolvePageMeta(location.pathname).icon,
82-
},
83-
{ method: "POST", action: FAVORITES_ACTION_PATH }
84-
);
85-
}
86-
};
8747

88-
const showButton = user !== undefined && !isImpersonating;
48+
const showButton = canFavorite;
8949

9050
// Option+F reports event.key "ƒ" on macOS, but the hotkeys matcher falls back to the physical
9151
// event.code ("KeyF"), so the standard hook captures it; exact modifier matching keeps the

0 commit comments

Comments
 (0)