Skip to content

Commit 558298c

Browse files
carderneTrigger.dev RepoOps
authored andcommitted
fix(webapp): remove Node 21 deprecation banner
Mono-RevId: d220091bd913394572a9c06aeb2f702461909a84
1 parent 731372b commit 558298c

11 files changed

Lines changed: 106 additions & 186 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: improvement
4+
---
5+
6+
Removed the organization-wide Node.js 21 deprecation banner while keeping runtime upgrade guidance in Projects settings.

‎apps/webapp/app/components/billing/OrgBanner.tsx‎

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,11 @@ import {
1515
useOrganization,
1616
useBillingLimit,
1717
useCanManageBillingLimits,
18-
useHasProjectRuntimeUpdate,
1918
} from "~/hooks/useOrganizations";
2019
import { useOptionalProject, useProject } from "~/hooks/useProject";
2120
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
2221
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
23-
import {
24-
concurrencyPath,
25-
organizationProjectsPath,
26-
v3BillingLimitsPath,
27-
v3BillingPath,
28-
} from "~/utils/pathBuilder";
22+
import { concurrencyPath, v3BillingLimitsPath, v3BillingPath } from "~/utils/pathBuilder";
2923
import { ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT } from "~/utils/environmentPauseSource";
3024

3125
/** Wire values for the billing-limits route action's "remove the limit" submission. */
@@ -45,7 +39,6 @@ export function OrgBanner() {
4539
const project = useOptionalProject();
4640
const environment = useOptionalEnvironment();
4741
const billingLimit = useBillingLimit();
48-
const hasProjectRuntimeUpdate = useHasProjectRuntimeUpdate();
4942
const currentPlan = useCurrentPlan();
5043
const showSelfServe = useShowSelfServe();
5144
const location = useLocation();
@@ -64,7 +57,6 @@ export function OrgBanner() {
6457
const isArchived = !!(organization && project && environment && environment.archivedAt);
6558

6659
const bannerKind = selectOrgBanner({
67-
hasProjectRuntimeUpdate,
6860
billingLimit,
6961
hasExceededFreeTier: currentPlan?.v3Usage.hasExceededFreeTier === true,
7062
showEnvironmentWarning: isPaused || isArchived,
@@ -75,8 +67,6 @@ export function OrgBanner() {
7567
const hideBillingLimitBanner = location.pathname.endsWith("/settings/billing-limits");
7668

7769
switch (bannerKind) {
78-
case OrgBannerKind.RuntimeUpdate:
79-
return <RuntimeUpdateBanner />;
8070
case OrgBannerKind.LimitRejected:
8171
return hideBillingLimitBanner ? null : <LimitRejectedBanner />;
8272
case OrgBannerKind.LimitGrace:
@@ -96,24 +86,6 @@ export function OrgBanner() {
9686
}
9787
}
9888

99-
function RuntimeUpdateBanner() {
100-
const organization = useOrganization();
101-
102-
return (
103-
<AnimatedOrgBannerBar
104-
show
105-
variant="warning"
106-
action={
107-
<LinkButton variant="tertiary/small" to={organizationProjectsPath(organization)}>
108-
See projects and upgrade
109-
</LinkButton>
110-
}
111-
>
112-
At least one of your projects uses Node 21: deployments will fail from 5 October.
113-
</AnimatedOrgBannerBar>
114-
);
115-
}
116-
11789
function LimitRejectedBanner() {
11890
const organization = useOrganization();
11991
const showSelfServe = useShowSelfServe();

‎apps/webapp/app/components/billing/selectOrgBanner.ts‎

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { BillingLimitResult } from "~/services/billingLimit.schemas";
22

33
export enum OrgBannerKind {
4-
RuntimeUpdate = "runtime-update",
54
LimitRejected = "limit-rejected",
65
LimitGrace = "limit-grace",
76
NoLimitConfigured = "no-limit",
@@ -11,24 +10,13 @@ export enum OrgBannerKind {
1110
}
1211

1312
export function selectOrgBanner(input: {
14-
hasProjectRuntimeUpdate?: boolean;
1513
billingLimit?: BillingLimitResult;
1614
hasExceededFreeTier?: boolean;
1715
showEnvironmentWarning?: boolean;
1816
/** Self-serve billing UI — hide configure-limit prompt for managed customers. */
1917
showSelfServe?: boolean;
2018
}): OrgBannerKind {
21-
const {
22-
hasProjectRuntimeUpdate,
23-
billingLimit,
24-
hasExceededFreeTier,
25-
showEnvironmentWarning,
26-
showSelfServe = true,
27-
} = input;
28-
29-
if (hasProjectRuntimeUpdate) {
30-
return OrgBannerKind.RuntimeUpdate;
31-
}
19+
const { billingLimit, hasExceededFreeTier, showEnvironmentWarning, showSelfServe = true } = input;
3220

3321
if (billingLimit?.isConfigured) {
3422
const status = billingLimit.limitState.status;

‎apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { UserGroupIcon } from "~/assets/icons/UserGroupIcon";
1313
import { VercelLogo } from "~/components/integrations/VercelLogo";
1414
import { useFeatureFlags } from "~/hooks/useFeatureFlags";
1515
import { useFeatures } from "~/hooks/useFeatures";
16-
import { type MatchedOrganization, useHasProjectRuntimeUpdate } from "~/hooks/useOrganizations";
16+
import { type MatchedOrganization } from "~/hooks/useOrganizations";
1717
import { cn } from "~/utils/cn";
1818
import {
1919
organizationPath,
@@ -53,17 +53,18 @@ export function OrganizationSettingsSideMenu({
5353
buildInfo,
5454
isUsingPlugin,
5555
isSsoUsingPlugin,
56+
hasProjectRuntimeUpdate,
5657
}: {
5758
organization: MatchedOrganization;
5859
buildInfo: BuildInfo;
5960
isUsingPlugin: boolean;
6061
isSsoUsingPlugin: boolean;
62+
hasProjectRuntimeUpdate: boolean;
6163
}) {
6264
const { isManagedCloud } = useFeatures();
6365
const featureFlags = useFeatureFlags();
6466
const currentPlan = useCurrentPlan();
6567
const showSelfServe = useShowSelfServe();
66-
const hasProjectRuntimeUpdate = useHasProjectRuntimeUpdate();
6768
const isAdmin = useHasAdminAccess();
6869
const showBuildInfo = isAdmin || !isManagedCloud;
6970

‎apps/webapp/app/hooks/useOrganizations.ts‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,3 @@ export function useCanManageBillingLimits(matches?: UIMatch[]) {
9494
});
9595
return data?.canManageBillingLimits === true;
9696
}
97-
98-
export function useHasProjectRuntimeUpdate(matches?: UIMatch[]) {
99-
const data = useTypedMatchesData<typeof orgLoader>({
100-
id: "routes/_app.orgs.$organizationSlug",
101-
matches,
102-
});
103-
return data?.hasProjectRuntimeUpdate === true;
104-
}

‎apps/webapp/app/routes/_app.orgs.$organizationSlug.settings/route.tsx‎

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Outlet, useRouteLoaderData } from "@remix-run/react";
2+
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
23
import { VERSION as coreVersion } from "@trigger.dev/core";
34
import { type ReactNode } from "react";
45
import { typedjson, useTypedLoaderData } from "remix-typedjson";
@@ -9,15 +10,60 @@ import {
910
OrganizationSettingsSideMenu,
1011
} from "~/components/navigation/OrganizationSettingsSideMenu";
1112
import { useOrganization } from "~/hooks/useOrganizations";
13+
import { resolveOrgIdFromSlugForUser } from "~/models/organization.server";
14+
import { logger } from "~/services/logger.server";
15+
import { organizationHasProjectRuntimeUpdate } from "~/services/projectRuntimeUpdates.server";
1216
import { rbac } from "~/services/rbac.server";
17+
import { requireUserId } from "~/services/session.server";
1318
import { ssoController } from "~/services/sso.server";
1419

1520
const SETTINGS_ROUTE_ID = "routes/_app.orgs.$organizationSlug.settings";
1621

17-
export const loader = async () => {
18-
const [isUsingPlugin, isSsoUsingPlugin] = await Promise.all([
22+
// The side-menu dot links to the Projects settings page, which requires `read` on
23+
// `deployments`, so gate the dot on the same ability the page checks.
24+
async function canReadDeployments({
25+
request,
26+
userId,
27+
organizationSlug,
28+
}: {
29+
request: Request;
30+
userId: string;
31+
organizationSlug: string;
32+
}) {
33+
// Membership-scoped so the dot is never computed against an org the user is not in.
34+
const organizationId = await resolveOrgIdFromSlugForUser(organizationSlug, userId);
35+
if (!organizationId) {
36+
return false;
37+
}
38+
39+
const auth = await rbac.authenticateAuthorizeSession(
40+
request,
41+
{ userId, organizationId },
42+
{ action: "read", resource: { type: "deployments" } }
43+
);
44+
return auth.ok;
45+
}
46+
47+
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
48+
const userId = await requireUserId(request);
49+
const organizationSlug = params.organizationSlug;
50+
51+
const [isUsingPlugin, isSsoUsingPlugin, hasProjectRuntimeUpdate] = await Promise.all([
1952
rbac.isUsingPlugin(),
2053
ssoController.isUsingPlugin(),
54+
organizationSlug
55+
? canReadDeployments({ request, userId, organizationSlug })
56+
.then((canRead) =>
57+
canRead ? organizationHasProjectRuntimeUpdate({ organizationSlug, userId }) : false
58+
)
59+
.catch((error) => {
60+
logger.error("Failed to check project runtime updates", {
61+
organizationSlug,
62+
error,
63+
});
64+
return false;
65+
})
66+
: Promise.resolve(false),
2167
]);
2268
return typedjson({
2369
buildInfo: {
@@ -29,18 +75,21 @@ export const loader = async () => {
2975
} satisfies BuildInfo,
3076
isUsingPlugin,
3177
isSsoUsingPlugin,
78+
hasProjectRuntimeUpdate,
3279
});
3380
};
3481

3582
function SettingsChrome({
3683
buildInfo,
3784
isUsingPlugin,
3885
isSsoUsingPlugin,
86+
hasProjectRuntimeUpdate,
3987
children,
4088
}: {
4189
buildInfo: BuildInfo;
4290
isUsingPlugin: boolean;
4391
isSsoUsingPlugin: boolean;
92+
hasProjectRuntimeUpdate: boolean;
4493
children: ReactNode;
4594
}) {
4695
const organization = useOrganization();
@@ -53,6 +102,7 @@ function SettingsChrome({
53102
buildInfo={buildInfo}
54103
isUsingPlugin={isUsingPlugin}
55104
isSsoUsingPlugin={isSsoUsingPlugin}
105+
hasProjectRuntimeUpdate={hasProjectRuntimeUpdate}
56106
/>
57107
<MainBody>{children}</MainBody>
58108
</div>
@@ -61,13 +111,15 @@ function SettingsChrome({
61111
}
62112

63113
export default function Page() {
64-
const { buildInfo, isUsingPlugin, isSsoUsingPlugin } = useTypedLoaderData<typeof loader>();
114+
const { buildInfo, isUsingPlugin, isSsoUsingPlugin, hasProjectRuntimeUpdate } =
115+
useTypedLoaderData<typeof loader>();
65116

66117
return (
67118
<SettingsChrome
68119
buildInfo={buildInfo}
69120
isUsingPlugin={isUsingPlugin}
70121
isSsoUsingPlugin={isSsoUsingPlugin}
122+
hasProjectRuntimeUpdate={hasProjectRuntimeUpdate}
71123
>
72124
<Outlet />
73125
</SettingsChrome>
@@ -84,6 +136,7 @@ export function ErrorBoundary() {
84136
buildInfo: BuildInfo;
85137
isUsingPlugin: boolean;
86138
isSsoUsingPlugin: boolean;
139+
hasProjectRuntimeUpdate: boolean;
87140
}
88141
| undefined;
89142

@@ -96,6 +149,7 @@ export function ErrorBoundary() {
96149
buildInfo={data.buildInfo}
97150
isUsingPlugin={data.isUsingPlugin}
98151
isSsoUsingPlugin={data.isSsoUsingPlugin}
152+
hasProjectRuntimeUpdate={data.hasProjectRuntimeUpdate}
99153
>
100154
<RouteErrorDisplay />
101155
</SettingsChrome>

‎apps/webapp/app/routes/_app.orgs.$organizationSlug/route.tsx‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import { useTypedMatchesData } from "~/hooks/useTypedMatchData";
99
import { OrganizationsPresenter } from "~/presenters/OrganizationsPresenter.server";
1010
import { RegionsPresenter, type Region } from "~/presenters/v3/RegionsPresenter.server";
1111
import { getImpersonationId } from "~/services/impersonation.server";
12-
import { logger } from "~/services/logger.server";
1312
import { getCachedUsage, getBillingLimit, getCurrentPlan } from "~/services/platform.v3.server";
1413
import { rbac } from "~/services/rbac.server";
1514
import { ssoController } from "~/services/sso.server";
16-
import { organizationHasProjectRuntimeUpdate } from "~/services/projectRuntimeUpdates.server";
1715
import { canManageBillingLimits } from "~/services/routeBuilders/permissions.server";
1816
import { requireUser } from "~/services/session.server";
1917
import { telemetry } from "~/services/telemetry.server";
@@ -130,7 +128,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
130128
regions,
131129
isUsingRbacPlugin,
132130
isUsingSsoPlugin,
133-
organizationHasRuntimeUpdate,
134131
] = await Promise.all([
135132
rbac
136133
.authenticateSession(request, {
@@ -160,21 +157,10 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
160157
// items. Both calls are cheap and cached.
161158
rbac.isUsingPlugin().catch(() => false),
162159
ssoController.isUsingPlugin().catch(() => false),
163-
organizationHasProjectRuntimeUpdate({ organizationId: organization.id }).catch((error) => {
164-
logger.error("Failed to check project runtime updates", {
165-
organizationId: organization.id,
166-
error,
167-
});
168-
return false;
169-
}),
170160
]);
171161
const userCanManageBillingLimits = sessionAuth.ok
172162
? canManageBillingLimits(sessionAuth.ability)
173163
: false;
174-
const hasProjectRuntimeUpdate =
175-
sessionAuth.ok &&
176-
sessionAuth.ability.can("read", { type: "deployments" }) &&
177-
organizationHasRuntimeUpdate;
178164

179165
let hasExceededFreeTier = false;
180166
let usagePercentage = 0;
@@ -232,7 +218,6 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
232218
},
233219
widgetLimitPerDashboard,
234220
canManageBillingLimits: userCanManageBillingLimits,
235-
hasProjectRuntimeUpdate,
236221
isUsingRbacPlugin,
237222
isUsingSsoPlugin,
238223
});

‎apps/webapp/app/services/deleteProject.server.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { PrismaClient } from "@trigger.dev/database";
22
import { prisma } from "~/db.server";
3-
import { invalidateOrganizationProjectRuntimeUpdateCache } from "~/services/projectRuntimeUpdates.server";
43
import { engine } from "~/v3/runEngine.server";
54
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
65

@@ -59,8 +58,6 @@ export class DeleteProjectService {
5958
},
6059
});
6160

62-
await invalidateOrganizationProjectRuntimeUpdateCache(project.organization.id);
63-
6461
/** project.deletedAt, which the engine gates enqueue and dequeue on, changed; drop every cached env of this project. */
6562
for (const environment of project.environments) {
6663
controlPlaneResolver.invalidateEnvironment(environment.id);

0 commit comments

Comments
 (0)