Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/page/onboarding/create-org.scene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const member: ClerkOrganization = {
homeRegion: "us",
isServedHere: true,
isRegionOpen: false,
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false, users: false },
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false },
}

const view = (model: Model, h: HtmlBuilder<Message>): Html => createOrgView(h, model, "david@acme.dev", [])
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/page/onboarding/plan-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const organization: ClerkOrganization = {
homeRegion: "us",
isServedHere: true,
isRegionOpen: false,
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false, users: false },
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false },
}

const customerWith = (status: string | null): CustomerSlot => ({
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/page/onboarding/wrong-region.scene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const organization: ClerkOrganization = {
homeRegion: "eu",
isServedHere: false,
isRegionOpen: false,
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false, users: false },
flags: { aiAutoTriage: false, releases: false, securityAudit: false, statusPages: false },
}

const usOrganization: ClerkOrganization = {
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/platform/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ export const ClerkOrganization = Schema.Struct({
releases: Schema.Boolean,
securityAudit: Schema.Boolean,
statusPages: Schema.Boolean,
users: Schema.Boolean,
}),
})
export type ClerkOrganization = typeof ClerkOrganization.Type
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/route/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,12 @@ export const routeRedirect = (route: AppRoute): Option.Option<Redirect> =>
)

const isStatusPageRoute = AppRoute.isAnyOf(["StatusPages", "StatusPageNew", "StatusPage"])
const isUsersRoute = AppRoute.isAnyOf(["Users", "UserDetail"])

/** A route an org outside its rollout cannot open goes home (`/status-pages` without `statusPages`). */
export const flaggedRouteRedirect = (
route: AppRoute,
flags: Pick<OrganizationFeatureFlags, "statusPages" | "users">,
flags: Pick<OrganizationFeatureFlags, "statusPages">,
): Option.Option<Redirect> =>
(!flags.statusPages && isStatusPageRoute(route)) || (!flags.users && isUsersRoute(route))
!flags.statusPages && isStatusPageRoute(route)
? redirect(AppRoute.Home({ search: {} }), true)
: Option.none()
21 changes: 3 additions & 18 deletions apps/web/src/route/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,27 +129,12 @@ describe("routes", () => {

test("an org outside the statusPages rollout is sent home from every status page route", () => {
for (const path of ["/status-pages", "/status-pages/new", "/status-pages/stp_abc"]) {
expect(flaggedRouteRedirect(parse(path), { statusPages: false, users: true })).toEqual(
expect(flaggedRouteRedirect(parse(path), { statusPages: false })).toEqual(
Option.some({ url: "/", isReplace: true }),
)
expect(flaggedRouteRedirect(parse(path), { statusPages: true, users: true })).toEqual(
Option.none(),
)
}
expect(flaggedRouteRedirect(parse("/services"), { statusPages: false, users: false })).toEqual(
Option.none(),
)
})

test("an org outside the users rollout is sent home from the users pages", () => {
for (const path of ["/users", "/users/user_42"]) {
expect(flaggedRouteRedirect(parse(path), { statusPages: true, users: false })).toEqual(
Option.some({ url: "/", isReplace: true }),
)
expect(flaggedRouteRedirect(parse(path), { statusPages: true, users: true })).toEqual(
Option.none(),
)
expect(flaggedRouteRedirect(parse(path), { statusPages: true })).toEqual(Option.none())
}
expect(flaggedRouteRedirect(parse("/services"), { statusPages: false })).toEqual(Option.none())
})

test("a literal sibling wins over a param route", () => {
Expand Down
18 changes: 5 additions & 13 deletions apps/web/src/shell/nav.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const infrastructure = Option.getOrUndefined(
Arr.findFirst(
Arr.flatMap(
navGroups({ releases: false, securityAudit: false, statusPages: false, users: false }),
navGroups({ releases: false, securityAudit: false, statusPages: false }),
(group) => group.items,
),
(item) => item.title === "Infrastructure",
Expand Down Expand Up @@ -45,15 +45,15 @@
)

test("pages without a row of their own highlight their parent row", () => {
const off = { releases: false, securityAudit: false, statusPages: false, users: false }
const off = { releases: false, securityAudit: false, statusPages: false }
expect(activeTitles("/anomalies", off)).toEqual(["Alerts"])
expect(activeTitles("/security/code/findings", off)).toEqual(["Code review"])
expect(activeTitles("/releases", off)).toEqual(["Services"])
expect(activeTitles("/status-pages/stp_abc", off)).toEqual(["Services"])
})

test("a flagged page with its own row highlights only that row", () => {
const on = { releases: true, securityAudit: true, statusPages: true, users: true }
const on = { releases: true, securityAudit: true, statusPages: true }
expect(activeTitles("/security", on)).toEqual(["Security"])
expect(activeTitles("/security/code/runs/run_1", on)).toEqual(["Security"])
expect(activeTitles("/releases", on)).toEqual(["Releases"])
Expand All @@ -66,17 +66,9 @@
Arr.flatMap(navGroups(flags), (group) => Arr.map(group.items, (item) => item.title))

test("the Status pages row shows only for an org in the statusPages rollout", () => {
expect(
titles({ releases: false, securityAudit: false, statusPages: false, users: false }),
).not.toContain("Status pages")
expect(titles({ releases: false, securityAudit: false, statusPages: true, users: false })).toContain(
expect(titles({ releases: false, securityAudit: false, statusPages: false })).not.toContain(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

The change's own behavior — Users always shown and reachable — is pinned by no test

F1 · Warning · tests

The Users-row test was deleted rather than inverted, so nothing asserts navGroups always contains Users, and route.test.ts:130 no longer pins that /users and /users/:id are not redirected. Both files test this kind of code, so the ungating itself is unpinned and a later re-gate would pass CI.

Add `expect(titles({ releases: false, securityAudit: false, statusPages: false })).toContain("Users")` here, and in `route.test.ts` assert `flaggedRouteRedirect(parse("/users"), { statusPages: false })` is `Option.none()`.
🤖 Prompt to fix with an AI agent
In `apps/web/src/shell/nav.test.ts:69-73`: The change's own behavior — Users always shown and reachable — is pinned by no test.

The Users-row test was deleted rather than inverted, so nothing asserts `navGroups` always contains `Users`, and `route.test.ts:130` no longer pins that `/users` and `/users/:id` are not redirected. Both files test this kind of code, so the ungating itself is unpinned and a later re-gate would pass CI.

Suggested fix: Add `expect(titles({ releases: false, securityAudit: false, statusPages: false })).toContain("Users")` here, and in `route.test.ts` assert `flaggedRouteRedirect(parse("/users"), { statusPages: false })` is `Option.none()`.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

"Status pages",
)
})

test("the Users row shows only for an org in the users rollout", () => {
const off = { releases: false, securityAudit: false, statusPages: false, users: false }
expect(titles(off)).not.toContain("Users")
expect(titles({ ...off, users: true })).toContain("Users")
expect(titles({ releases: false, securityAudit: false, statusPages: true })).toContain("Status pages")
})

Check warning on line 73 in apps/web/src/shell/nav.test.ts

View check run for this annotation

Maple Review Bot / Maple / review

tests: The change's own behavior — Users always shown and reachable — is pinned by no test

The Users-row test was deleted rather than inverted, so nothing asserts `navGroups` always contains `Users`, and `route.test.ts:130` no longer pins that `/users` and `/users/:id` are not redirected. Both files test this kind of code, so the ungating itself is unpinned and a later re-gate would pass CI.
})
5 changes: 2 additions & 3 deletions apps/web/src/shell/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,9 @@ export type NavFlags = Readonly<{
releases: boolean
securityAudit: boolean
statusPages: boolean
users: boolean
}>

/** `navGroups(flags)`; `releases`, `securityAudit`, `statusPages` and `users` gate a row each. */
/** `navGroups(flags)`; `releases`, `securityAudit` and `statusPages` gate a row each. */
export const navGroups = (flags: NavFlags): ReadonlyArray<NavGroup> => [
{ id: "overview", items: [{ title: "Overview", href: "/", icon: "HouseIcon" }] },
{
Expand Down Expand Up @@ -166,7 +165,7 @@ export const navGroups = (flags: NavFlags): ReadonlyArray<NavGroup> => [
items: [
exploreItem,
{ title: "Web analytics", href: "/analytics", icon: "ChartBarHorizontalIcon" },
...(flags.users ? [{ title: "Users", href: "/users", icon: "UsersIcon" } as const] : []),
{ title: "Users", href: "/users", icon: "UsersIcon" },
{ title: "Dashboards", href: "/dashboards", icon: "GridSquareCirclePlusIcon" },
],
},
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/shell/palette/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const href = (url: string): Target => ({ _tag: "Href", href: url })
/** `paletteNavItems`: every section, its children and their views, deduplicated by title and href. */
const navigationEntries = (): ReadonlyArray<PaletteEntry> => {
const rows = Arr.flatMap(
navGroups({ releases: true, securityAudit: true, statusPages: true, users: true }),
navGroups({ releases: true, securityAudit: true, statusPages: true }),
(group) =>
Arr.flatMap(group.items, (item) => [
{ id: `nav:${item.title}`, title: item.title, href: item.href, icon: item.icon },
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/shell/view-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const shellNavGroups = (clerk: ShellClerk | null = null): ReadonlyArray<N
releases: flag("releases"),
securityAudit: flag("securityAudit"),
statusPages: flag("statusPages"),
users: flag("users"),
})
}

Expand Down
1 change: 0 additions & 1 deletion apps/web/src/update.customer-grace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const snapshot: ClerkSnapshot = {
releases: false,
securityAudit: false,
statusPages: false,
users: false,
},
}),
memberships: [],
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ describe("re-render on change only", () => {
releases: false,
securityAudit: false,
statusPages: false,
users: false,
},
}),
memberships: [],
Expand Down
5 changes: 0 additions & 5 deletions packages/domain/src/organization-feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,12 @@ export const OrganizationFeatureFlags = Schema.Struct({
securityAudit: DisabledByDefaultFeatureFlag,
/** Gates customer status pages: the nav row, the `/status-pages` routes, the API and MCP tools. */
statusPages: DisabledByDefaultFeatureFlag,
/** Gates the Users row under Analyze and the `/users` routes. */
users: DisabledByDefaultFeatureFlag,
}).pipe(
Schema.encodeKeys({
aiAutoTriage: "aiautotriage",
releases: "releases",
securityAudit: "securityaudit",
statusPages: "statuspages",
users: "users",
}),
)

Expand All @@ -51,7 +48,6 @@ export const DISABLED_ORGANIZATION_FEATURE_FLAGS: OrganizationFeatureFlags = {
releases: false,
securityAudit: false,
statusPages: false,
users: false,
}

/**
Expand All @@ -65,7 +61,6 @@ export const ENABLED_ORGANIZATION_FEATURE_FLAGS: OrganizationFeatureFlags = {
releases: true,
securityAudit: true,
statusPages: true,
users: true,
}

/** Decode Clerk metadata, falling back to every rollout disabled for non-object input. */
Expand Down
Loading