Skip to content
Closed
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
12 changes: 2 additions & 10 deletions apps/webapp/app/v3/featureFlags.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
FeatureFlagCatalog,
GLOBAL_LOCKED_FLAGS,
GRACED_FLAG_GROUPS,
validatePartialFeatureFlags,
mergeOrgFeatureFlags,
} from "~/v3/featureFlags";
import { env } from "~/env.server";
import { isAlertsEnabledEnv } from "~/v3/canUseAlertsList";
Expand Down Expand Up @@ -396,15 +396,7 @@ export async function globalFeatureFlags() {
}

/** The global set with one org's overrides on top. */
export function mergeOrgFeatureFlags(
globalFlags: Partial<FeatureFlagCatalog>,
orgFeatureFlags: unknown
) {
const parsed = orgFeatureFlags
? validatePartialFeatureFlags(orgFeatureFlags as Record<string, unknown>)
: ({ success: false } as const);
return { ...globalFlags, ...(parsed.success ? parsed.data : {}) };
}
export { mergeOrgFeatureFlags } from "~/v3/featureFlags";

/**
* The flags that apply to one organization. Server-side callers that need the
Expand Down
9 changes: 9 additions & 0 deletions apps/webapp/app/v3/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,15 @@ export function parseStoredOrgFlags(raw: unknown): StoredOrgFlags {
return result;
}

/** The global set with one org's overrides on top. */
export function mergeOrgFeatureFlags(
globalFlags: Partial<FeatureFlagCatalog>,
orgFeatureFlags: unknown
): Partial<FeatureFlagCatalog> {
const validOrgFlags = orgFeatureFlags ? parseStoredOrgFlags(orgFeatureFlags).valid : {};
return { ...globalFlags, ...validOrgFlags };
Comment on lines +397 to +398

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Flag fix missing server release note

When stored flags contain an invalid entry, mergeOrgFeatureFlags now restores valid overrides and changes users' navigation. The required server release note is absent.

Learn more

The server-only release-note rule requires a .server-changes/ entry for a user-visible webapp bug fix. OrganizationsPresenter passes these flags to the side menu, where valid overrides reveal or hide navigation. No release-note file was added with this change.

Example: An org has hasLogsPageAccess: true and an invalid snapshotStoreMode. Its Logs navigation returns after this fix; users notice that change.

Recommended fix: Add a .server-changes/ file with area: webapp, type: fix, and a one-line description of restored organization features.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

}

/** Whether `key` still holds the invalid value the org has stored, i.e. the admin hasn't touched it. */
export function isUnchangedInvalidFlag(
key: string,
Expand Down
44 changes: 44 additions & 0 deletions apps/webapp/test/storedOrgFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ORG_LOCKED_FLAGS,
ORG_LOCKED_FLAG_EFFECT,
isUnchangedInvalidFlag,
mergeOrgFeatureFlags,
parseStoredOrgFlags,
validatePartialFeatureFlags,
} from "~/v3/featureFlags";
Expand Down Expand Up @@ -94,3 +95,46 @@ describe("isUnchangedInvalidFlag", () => {
expect(isUnchangedInvalidFlag(KEY, { [KEY]: { x: null } }, {})).toBe(false);
});
});

describe("mergeOrgFeatureFlags", () => {
const globalFlags = {
[FEATURE_FLAG.hasLogsPageAccess]: false,
[FEATURE_FLAG.hasQueryAccess]: false,
};

it("merges valid org overrides on top of global flags", () => {
const result = mergeOrgFeatureFlags(globalFlags, {
[FEATURE_FLAG.hasLogsPageAccess]: true,
});
expect(result).toEqual({
[FEATURE_FLAG.hasLogsPageAccess]: true,
[FEATURE_FLAG.hasQueryAccess]: false,
});
});

it("preserves valid org flags when an invalid flag is present in stored org flags", () => {
const result = mergeOrgFeatureFlags(globalFlags, {
[FEATURE_FLAG.hasLogsPageAccess]: true,
[FEATURE_FLAG.snapshotStoreMode]: "invalid-mode",
});
Comment on lines +115 to +119

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 New flag tests lack regression comments

The new mergeOrgFeatureFlags tests omit the required // Catches: regression comments. Their intended regressions remain undocumented for future reviewers.

Learn more

Repository testing guidance requires a one-line // Catches: <bug> comment naming the specific regression each new unit test protects against. The added merge tests contain no such comments, including the test that protects the invalid-stored-flag regression.

Example: The test with hasLogsPageAccess: true and snapshotStoreMode: "invalid-mode" protects against one invalid stored flag suppressing a valid override, but its regression is not named.

Recommended fix: Add the required // Catches: comments to retained tests and remove any added cases that cannot identify a distinct regression.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

expect(result).toEqual({
[FEATURE_FLAG.hasLogsPageAccess]: true,
[FEATURE_FLAG.hasQueryAccess]: false,
});
});

it("ignores unknown keys in stored org flags when merging", () => {
const result = mergeOrgFeatureFlags(globalFlags, {
[FEATURE_FLAG.hasLogsPageAccess]: true,
unknownKey: "should-be-ignored",
});
expect(result).toEqual({
[FEATURE_FLAG.hasLogsPageAccess]: true,
[FEATURE_FLAG.hasQueryAccess]: false,
});
});

it.each([null, undefined, "", 123, []])("returns global flags unchanged for %j", (raw) => {
expect(mergeOrgFeatureFlags(globalFlags, raw)).toEqual(globalFlags);
});
});