Skip to content

Commit 6f86da8

Browse files
kathiekiwiTrigger.dev RepoOps
authored andcommitted
fix(alerts): hide the alerts preview from admins when alerts are not enabled
fix(webapp): hide the Alerts section on installations where alerts are disabled Mono-RevId: ed9b4658376cddba2969bcb40d86e5dfc77732a3
1 parent 8e28ab4 commit 6f86da8

15 files changed

Lines changed: 112 additions & 32 deletions

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ export function SideMenu({
346346
const isFreeUser = currentPlan?.v3Subscription?.isPaying === false;
347347
const isAdmin = useHasAdminAccess();
348348
const isViewingAsUser = useIsViewingAsUser();
349-
const { isManagedCloud } = useFeatures();
349+
const { isManagedCloud, alertsConfigured } = useFeatures();
350350
const featureFlags = useFeatureFlags();
351351
const incidentStatus = useIncidentStatus();
352352
const isV3Project = project.engine === "V1";
@@ -749,6 +749,7 @@ export function SideMenu({
749749
project,
750750
environment,
751751
isAdmin,
752+
alertsConfigured,
752753
featureFlags,
753754
isManagedCloud,
754755
dashboards: {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export function buildSideMenuSections({
8181
project,
8282
environment,
8383
isAdmin,
84+
alertsConfigured,
8485
featureFlags,
8586
isManagedCloud,
8687
dashboards,
@@ -89,6 +90,7 @@ export function buildSideMenuSections({
8990
project: ProjectForPath;
9091
environment: EnvironmentForPath;
9192
isAdmin: boolean;
93+
alertsConfigured: boolean;
9294
featureFlags: {
9395
hasAiAccess?: boolean;
9496
hasQueryAccess?: boolean;
@@ -138,7 +140,8 @@ export function buildSideMenuSections({
138140
const showObservability =
139141
isAdmin || featureFlags.hasQueryAccess || featureFlags.hasLogsPageAccess;
140142
const alertsInObservability =
141-
showObservability && computeCanUseAlertsList(featureFlags.alertsEnabled === true, isAdmin);
143+
showObservability &&
144+
computeCanUseAlertsList(featureFlags.alertsEnabled === true, isAdmin, alertsConfigured);
142145

143146
if (showObservability) {
144147
staticSections.push({

‎apps/webapp/app/features.server.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { env } from "./env.server";
2+
import { isAlertsConfigured } from "./v3/services/alerts/alertsConfigured.server";
23
import { queueMetricsEnabled } from "./v3/services/alerts/offeredCapabilities.server";
34
import { requestUrl } from "./utils/requestUrl.server";
45

@@ -7,6 +8,7 @@ export type TriggerFeatures = {
78
hasPrivateConnections: boolean;
89
queueMetricsQueryTables: boolean;
910
queueMetricsEnabled: boolean;
11+
alertsConfigured: boolean;
1012
};
1113

1214
const MANAGED_CLOUD_HOSTS = ["cloud.trigger.dev", "test-cloud.trigger.dev", "internal.trigger.dev"];
@@ -34,6 +36,7 @@ function featuresForHost(host: string): TriggerFeatures {
3436
hasPrivateConnections: hasPrivateConnections(host),
3537
queueMetricsQueryTables: env.QUEUE_METRICS_QUERY_TABLES_VISIBLE === "1",
3638
queueMetricsEnabled: queueMetricsEnabled(),
39+
alertsConfigured: isAlertsConfigured({ alertsEnabled: env.ALERTS_ENABLED }),
3740
};
3841
}
3942

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function useFeatures(): TriggerFeatures {
1111
hasPrivateConnections: false,
1212
queueMetricsQueryTables: false,
1313
queueMetricsEnabled: true,
14+
alertsConfigured: false,
1415
}
1516
);
1617
}

‎apps/webapp/app/routes/account._index/route.tsx‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ function CustomizeSidebarButton({
725725
}) {
726726
const user = useUser();
727727
const isAdmin = useHasAdminAccess();
728-
const { isManagedCloud } = useFeatures();
728+
const { isManagedCloud, alertsConfigured } = useFeatures();
729729
const favorites = useFavorites();
730730
const [isOpen, setIsOpen] = useState(false);
731731
const [isConfirming, setIsConfirming] = useState(false);
@@ -767,16 +767,18 @@ function CustomizeSidebarButton({
767767
},
768768
]
769769
: []),
770-
...buildSideMenuSections({ ...context, isAdmin, isManagedCloud }).map((section) => ({
771-
id: section.id,
772-
title: section.title,
773-
items: section.items.map((item) => ({
774-
id: item.id,
775-
name: item.name,
776-
icon: item.icon,
777-
defaultHidden: item.defaultHidden,
778-
})),
779-
})),
770+
...buildSideMenuSections({ ...context, isAdmin, alertsConfigured, isManagedCloud }).map(
771+
(section) => ({
772+
id: section.id,
773+
title: section.title,
774+
items: section.items.map((item) => ({
775+
id: item.id,
776+
name: item.name,
777+
icon: item.icon,
778+
defaultHidden: item.defaultHidden,
779+
})),
780+
})
781+
),
780782
];
781783

782784
return (

‎apps/webapp/app/v3/canUseAlertsList.server.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { v3ProjectAlertsPath } from "~/utils/pathBuilder";
55
import { FEATURE_FLAG } from "~/v3/featureFlags";
66
import { makeFlag } from "~/v3/featureFlags.server";
77
import { computeCanUseAlertsList, isAlertsEnabledEnv } from "./canUseAlertsList";
8+
import { isAlertsConfigured } from "./services/alerts/alertsConfigured.server";
89

910
async function isAlertsFlagEnabled(
1011
orgFeatureFlags: Record<string, unknown> | null | undefined
@@ -53,7 +54,11 @@ export async function canUseAlertsList(options: {
5354
isAdmin = user?.admin ?? false;
5455
}
5556

56-
return computeCanUseAlertsList(Boolean(flagResult), isAdmin);
57+
return computeCanUseAlertsList(
58+
Boolean(flagResult),
59+
isAdmin,
60+
isAlertsConfigured({ alertsEnabled: env.ALERTS_ENABLED })
61+
);
5762
}
5863

5964
type AlertsGateParams = {

‎apps/webapp/app/v3/canUseAlertsList.test.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ import { describe, expect, it } from "vitest";
22
import { computeCanUseAlertsList, isAlertsEnabledEnv } from "./canUseAlertsList";
33

44
describe("computeCanUseAlertsList", () => {
5-
it("admin, flag off -> true", () => {
6-
expect(computeCanUseAlertsList(false, true)).toBe(true);
5+
it("admin, flag off, configured -> true", () => {
6+
expect(computeCanUseAlertsList(false, true, true)).toBe(true);
7+
});
8+
9+
it("admin, flag off, not configured -> false", () => {
10+
expect(computeCanUseAlertsList(false, true, false)).toBe(false);
711
});
812

913
it("non-admin, flag off -> false", () => {
10-
expect(computeCanUseAlertsList(false, false)).toBe(false);
14+
expect(computeCanUseAlertsList(false, false, true)).toBe(false);
1115
});
1216

1317
it("non-admin, flag on -> true", () => {
14-
expect(computeCanUseAlertsList(true, false)).toBe(true);
18+
expect(computeCanUseAlertsList(true, false, false)).toBe(true);
1519
});
1620
});
1721

‎apps/webapp/app/v3/canUseAlertsList.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
/**
22
* The single Alerts-list gating rule: the `alertsEnabled` flag (per-org override
3-
* winning), or the viewer being a platform admin. Off leaves the legacy channel-list
4-
* page unchanged for non-admins.
3+
* winning), or a platform admin on an install where alerts are configured. Off leaves
4+
* the legacy channel-list page unchanged.
55
*/
6-
export function computeCanUseAlertsList(flagEnabled: boolean, isAdmin: boolean): boolean {
7-
return flagEnabled || isAdmin;
6+
export function computeCanUseAlertsList(
7+
flagEnabled: boolean,
8+
isAdmin: boolean,
9+
alertsConfigured: boolean
10+
): boolean {
11+
return flagEnabled || (isAdmin && alertsConfigured);
812
}
913

1014
export function isAlertsEnabledEnv(value: string | undefined): boolean {

‎apps/webapp/test/alertsGateRoutes.test.ts‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type * as AlertsGate from "~/v3/canUseAlertsList.server";
12
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
23

34
vi.setConfig({ testTimeout: 30_000 });
@@ -35,8 +36,11 @@ vi.mock("~/services/routeBuilders/dashboardBuilder", () => {
3536
vi.mock("~/services/session.server", () => ({ requireUserId: async () => "user_1" }));
3637

3738
vi.mock("~/db.server", () => ({
38-
prisma: { organizationIntegration: { findFirst: async () => ({ id: "int_1" }) } },
39-
$replica: {},
39+
prisma: {
40+
organizationIntegration: { findFirst: async () => ({ id: "int_1" }) },
41+
user: { findFirst: async () => ({ admin: true }) },
42+
},
43+
$replica: { organization: { findFirst: async () => ({ featureFlags: {} }) } },
4044
}));
4145

4246
vi.mock("~/models/project.server", async (importOriginal) => ({
@@ -132,6 +136,11 @@ vi.mock("~/v3/canUseAlertsList.server", async () => {
132136
};
133137
});
134138

139+
vi.mock("~/v3/featureFlags.server", async (importOriginal) => ({
140+
...(await importOriginal<object>()),
141+
makeFlag: () => async (opts: { defaultValue: unknown }) => opts.defaultValue,
142+
}));
143+
135144
const P = "_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam";
136145
const params = { organizationSlug: "o", projectParam: "p", envParam: "dev", channelId: "c" };
137146
const request = (method = "GET") => new Request("http://localhost/x", { method });
@@ -189,6 +198,15 @@ describe("new alerts surfaces when alertsEnabled is off", () => {
189198
});
190199
});
191200

201+
describe("admin preview when ALERTS_ENABLED is unset", () => {
202+
it("the real gate redirects an admin to the old alerts page", async () => {
203+
const real = await vi.importActual<typeof AlertsGate>("~/v3/canUseAlertsList.server");
204+
const res = await thrown(() => real.requireAlertsEnabledOrRedirect("user_1", params));
205+
expect(res?.status).toBe(302);
206+
expect(await real.canUseAlertsList({ orgFeatureFlags: {}, user: { admin: true } })).toBe(false);
207+
});
208+
});
209+
192210
describe("preview-series authorization", () => {
193211
it("responds 403 to a member denied read:alerts", async () => {
194212
STATE.enabled = true;

‎apps/webapp/test/alertsRequireEnabled.test.ts‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { beforeEach, describe, expect, it, vi } from "vitest";
22

3-
const STATE = vi.hoisted(() => ({ member: true, flag: false, admin: false }));
3+
const STATE = vi.hoisted(() => ({ member: true, flag: false, admin: false, configured: "1" }));
44

5-
vi.mock("~/env.server", () => ({ env: {} }));
5+
vi.mock("~/env.server", () => ({
6+
env: {
7+
get ALERTS_ENABLED() {
8+
return STATE.configured;
9+
},
10+
},
11+
}));
612
vi.mock("~/v3/featureFlags.server", () => ({ makeFlag: () => async () => STATE.flag }));
713
vi.mock("~/db.server", () => ({
814
prisma: { user: { findFirst: async () => ({ admin: STATE.admin }) } },
@@ -34,6 +40,7 @@ beforeEach(() => {
3440
STATE.member = true;
3541
STATE.flag = false;
3642
STATE.admin = false;
43+
STATE.configured = "1";
3744
});
3845

3946
describe.each([
@@ -49,11 +56,17 @@ describe.each([
4956
expect(await thrown(run)).toBeUndefined();
5057
});
5158

52-
it("allows an admin member with the flag off", async () => {
59+
it("allows an admin member with the flag off when alerts are configured", async () => {
5360
STATE.admin = true;
5461
expect(await thrown(run)).toBeUndefined();
5562
});
5663

64+
it("denies an admin member with the flag off when alerts are not configured", async () => {
65+
STATE.admin = true;
66+
STATE.configured = "";
67+
expect((await thrown(run))?.status).toBe(deniedStatus);
68+
});
69+
5770
it("denies a non-member even with the flag on", async () => {
5871
STATE.member = false;
5972
STATE.flag = true;

0 commit comments

Comments
 (0)