Skip to content

Port existing Feature Flag implementation into Cellix - #317

Open
aaron-rabinowitz wants to merge 4 commits into
mainfrom
312-port-existing-feature-flag-implementation-into-cellix
Open

Port existing Feature Flag implementation into Cellix#317
aaron-rabinowitz wants to merge 4 commits into
mainfrom
312-port-existing-feature-flag-implementation-into-cellix

Conversation

@aaron-rabinowitz

@aaron-rabinowitz aaron-rabinowitz commented Aug 10, 2026

Copy link
Copy Markdown

Summary by Sourcery

Introduce backend and frontend feature flag support wired to blob storage and shared UI provider, with validation, caching, and fallbacks for missing or invalid data.

New Features:

  • Add ServiceBlobStorage.getFeatureFlags() to read and validate feature flag JSON from the public blob container with a local fallback payload.
  • Expose feature flag types from the blob-storage adapter and document how to consume feature flags in application code.
  • Introduce a shared FeatureFlagProvider and hook in @ocom/ui-shared to fetch, cache, and expose feature flags to React applications, with Storybook-safe defaults.
  • Wire the Community and Staff UI apps to the shared FeatureFlagProvider using environment-based configuration and JSON default values for portal maintenance flags.

Enhancements:

  • Add JSON schema validation for feature flag blobs using Ajv to ensure EFDO-compatible payloads.
  • Extend blob storage contracts so backend consumers can access feature flags alongside existing blob operations.

Build:

  • Update pnpm-workspace overrides and audit configuration for dependency versions and security advisories.
  • Enable importing JSON config files in the ui-community and ui-staff TypeScript configs.

Documentation:

  • Update @ocom/service-blob-storage readme to describe feature flag retrieval and usage with the new getFeatureFlags() API.

Tests:

  • Add unit tests for ServiceBlobStorage feature flag behavior covering valid blobs, missing blobs, invalid payloads, and malformed JSON.
  • Add unit tests for the FeatureFlagProvider covering remote fetching, caching behavior, fallback usage, and Storybook environment handling.
  • Add tests to ensure Community and Staff portal feature-flag default JSON includes the expected maintenance flag keys.

Chores:

  • Add lru-cache dependency to @ocom/ui-shared to support feature flag caching.

@aaron-rabinowitz aaron-rabinowitz linked an issue Aug 10, 2026 that may be closed by this pull request
@aaron-rabinowitz
aaron-rabinowitz requested a review from a team August 10, 2026 17:08
@sourcery-ai

sourcery-ai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Ports EFDO-style feature flag support into the OCOM Cellix stack by adding backend blob-based feature flag retrieval with JSON-schema validation, a shared React feature flag provider with caching and fallbacks, wiring both UI apps to use it, and updating dependencies/config for security and JSON imports.

Sequence diagram for backend blob feature flag retrieval

sequenceDiagram
    actor AppService
    participant ServiceBlobStorage
    participant BlobServiceClient
    participant AjvValidator

    AppService->>ServiceBlobStorage: getFeatureFlags()
    ServiceBlobStorage->>ServiceBlobStorage: downloadBlobToString("public", FEATURE_FLAG_BLOB_NAME)
    ServiceBlobStorage->>BlobServiceClient: getContainerClient("public")
    BlobServiceClient-->>ServiceBlobStorage: containerClient
    ServiceBlobStorage->>BlobServiceClient: getBlockBlobClient(FEATURE_FLAG_BLOB_NAME)
    BlobServiceClient-->>ServiceBlobStorage: blockBlobClient
    ServiceBlobStorage->>BlobServiceClient: downloadToBuffer()

    alt blob exists
        BlobServiceClient-->>ServiceBlobStorage: Buffer
        ServiceBlobStorage->>ServiceBlobStorage: buffer.toString("utf-8")
        ServiceBlobStorage-->>ServiceBlobStorage: featureFlagsRaw
    else isBlobNotFoundError(error)
        ServiceBlobStorage-->>ServiceBlobStorage: featureFlagsRaw = FeatureFlagsLocal
    end

    ServiceBlobStorage->>AjvValidator: compile(FeatureFlagsSchema)
    AjvValidator-->>ServiceBlobStorage: validate
    ServiceBlobStorage->>AjvValidator: validate(dataJson)
    alt validation passes
        ServiceBlobStorage-->>AppService: FeatureFlagsPayloadType
    else validation fails
        ServiceBlobStorage-->>AppService: Error("Feature flag payload validation failed ...")
    end
Loading

Sequence diagram for React feature flag provider with caching and fallbacks

sequenceDiagram
    actor ReactComponent
    participant FeatureFlagProvider
    participant LRUCache
    participant BrowserFetch as fetch

    ReactComponent->>FeatureFlagProvider: useFeatureFlags()
    FeatureFlagProvider->>FeatureFlagProvider: useEffect(config)

    alt isInStorybookEnv()
        FeatureFlagProvider->>FeatureFlagProvider: setFeatureFlags(config.fallbackFlagValues)
        FeatureFlagProvider-->>ReactComponent: GetFeatureFlagByName(name)
    else [not Storybook]
        alt [config.url is empty]
            FeatureFlagProvider->>FeatureFlagProvider: setFeatureFlags(config.fallbackFlagValues)
            FeatureFlagProvider-->>ReactComponent: GetFeatureFlagByName(name)
        else [config.url provided]
            FeatureFlagProvider->>LRUCache: get("featureFlags")
            alt [cachedFeatureFlags]
                LRUCache-->>FeatureFlagProvider: FeatureFlags
                FeatureFlagProvider->>FeatureFlagProvider: setFeatureFlags(cachedFeatureFlags)
                FeatureFlagProvider-->>ReactComponent: GetFeatureFlagByName(name)
            else [no cache]
                FeatureFlagProvider->>FeatureFlagProvider: fetchFeatureFlags(config.url)
                FeatureFlagProvider->>BrowserFetch: fetch(urlWithTimestamp, { cache: "no-store" })
                BrowserFetch-->>FeatureFlagProvider: response
                alt [response.ok]
                    FeatureFlagProvider->>BrowserFetch: response.json()
                    BrowserFetch-->>FeatureFlagProvider: remote FeatureFlags
                    FeatureFlagProvider->>LRUCache: set("featureFlags", remote FeatureFlags)
                    FeatureFlagProvider->>FeatureFlagProvider: setFeatureFlags(remote FeatureFlags)
                    FeatureFlagProvider-->>ReactComponent: GetFeatureFlagByName(name)
                else [fetch/json error or !ok]
                    FeatureFlagProvider->>FeatureFlagProvider: setFeatureFlags(config.fallbackFlagValues)
                    FeatureFlagProvider-->>ReactComponent: GetFeatureFlagByName(name)
                end
            end
        end
    end

    Note over FeatureFlagProvider,LRUCache: setInterval refreshFeatureFlags() runs every cacheMilliseconds / 2
Loading

File-Level Changes

Change Details Files
Add validated feature-flag retrieval to the OCOM ServiceBlobStorage wrapper.
  • Replace direct re-export of Cellix ServiceBlobStorage with a subclass that adds feature flag support.
  • Introduce JSON-schema (Ajv 2020) validation helper that parses blob content and throws on invalid payloads.
  • Implement getFeatureFlags() to read feature-flags.json from the public container, falling back to a local JSON string when the blob is missing.
  • Add a private downloadBlobToString() helper with BlobNotFound handling and an isBlobNotFoundError type guard.
packages/ocom/service-blob-storage/src/service-blob-storage.ts
packages/ocom/service-blob-storage/src/feature-flags.ts
packages/ocom/service-blob-storage/src/feature-flags.local.ts
packages/ocom/service-blob-storage/package.json
Expose feature-flag types and contract surface from the OCOM blob-storage adapter.
  • Extend BlobStorageOperations to include getFeatureFlags so backend callers can consume flags via the adapter.
  • Export FeatureFlag and FeatureFlagsPayloadType from the package public index for external use.
  • Document getFeatureFlags() usage and behavior in the package README.
packages/ocom/service-blob-storage/src/blob-storage.contract.ts
packages/ocom/service-blob-storage/src/index.ts
packages/ocom/service-blob-storage/readme.md
Add unit tests that exercise feature-flag blob behavior in the ServiceBlobStorage wrapper.
  • Create a TestServiceBlobStorage subclass to inject a mocked BlobServiceClient.
  • Mock container/blob clients to simulate successful flag download, blob-not-found, invalid payload shape, and malformed JSON.
  • Add expectations on resolved FeatureFlagsPayloadType and thrown error types/messages.
packages/ocom/service-blob-storage/src/index.test.ts
Introduce a shared React feature flag provider with caching, retries, Storybook behavior, and a hook-based API.
  • Define a FeatureFlagsContext and interface with GetFeatureFlagByName helper and a protective stub when provider is missing.
  • Implement FeatureFlagProvider that loads flags from a remote URL with LRU-based TTL caching, retry logic, and interval refresh, falling back to local defaults on errors.
  • Detect Storybook iframe environment to always use local fallback values instead of fetching remote configuration.
  • Expose a useFeatureFlags() hook and barrel exports for config, provider, types, and hook.
  • Add lru-cache as a dependency and tests covering remote load, fallbacks, caching behavior, JSON parse failures, Storybook handling, and missing flags.
packages/ocom/ui-shared/package.json
packages/ocom/ui-shared/src/components/organisms/feature-flag/feature-flag-context.tsx
packages/ocom/ui-shared/src/components/organisms/feature-flag/feature-flag-provider.tsx
packages/ocom/ui-shared/src/components/organisms/feature-flag/use-feature-flags.tsx
packages/ocom/ui-shared/src/components/organisms/feature-flag/is-in-storybook-env.ts
packages/ocom/ui-shared/src/components/organisms/feature-flag/index.tsx
packages/ocom/ui-shared/src/components/organisms/feature-flag/feature-flag-provider.test.tsx
packages/ocom/ui-shared/src/components/organisms/index.tsx
Wire Community and Staff UI apps to the shared feature flag provider with app-specific default values and configuration.
  • Create feature-flag-config.ts in each app that wires FeatureFlagConfig to VITE_COMMON_FEATURE_FLAG_URL and app-local JSON default values with a 30s cache.
  • Add JSON default files for maintenance-related feature flags in each app plus tests ensuring required flags are present.
  • Wrap each app's root tree in FeatureFlagProvider using the app-specific config.
  • Update tsconfig includes to allow importing JSON config files and add @ocom/ui-shared dependency for ui-community.
apps/ui-community/src/main.tsx
apps/ui-staff/src/main.tsx
apps/ui-community/src/config/feature-flag-config.ts
apps/ui-staff/src/config/feature-flag-config.ts
apps/ui-community/src/config/feature-flag-default-values.json
apps/ui-staff/src/config/feature-flag-default-values.json
apps/ui-community/src/config/feature-flag-default-values.test.ts
apps/ui-staff/src/config/feature-flag-default-values.test.ts
apps/ui-community/tsconfig.json
apps/ui-staff/tsconfig.json
apps/ui-community/package.json
Adjust workspace security/audit and dependency overrides to account for new or updated dependencies.
  • Extend pnpm auditConfig ignore list for two image-size advisories without patched releases.
  • Bump brace-expansion, protobuf-related packages, js-yaml, fast-uri, and nanoid overrides to more secure versions, and add an override for @apollo/protobufjs.
pnpm-workspace.yaml

Assessment against linked issues

Issue Objective Addressed Explanation
#312 Introduce strongly typed feature-flag configuration for Cellix/Owner Community, including local JSON files with the standard maintenance-related flags for both Staff and Community portals, and support loading feature flags from a public Azure Blob Storage JSON file for deployed environments with validation and safe fallbacks.
#312 Implement shared frontend infrastructure for both Owner Community portals to load feature flags at startup (from a configured blob URL), expose a typed abstraction for retrieving flag values, and gracefully handle missing, malformed, or unavailable configuration using local fallback values.
#312 Provide backend and frontend automated tests covering feature flag loading, retrieval, and failure scenarios (missing values, missing/malformed configuration, and configuration source/fallback behavior).

Possibly linked issues

  • #N/A: PR introduces typed feature flag loading via Azure Blob, local JSON defaults for both portals, context APIs, and tests per issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 2 issues, and left some high level feedback:

Fixed security issues:

  • brace-expansion (link) · Dashboard

  • fast-uri (link) · Dashboard

  • js-yaml (link)

  • nanoid (link)

  • In ServiceBlobStorage.getValidatedBlobDataObject, you compile the AJV schema on every call; consider pre-compiling FeatureFlagsSchema once (e.g., const validateFeatureFlags = ajv.compile(FeatureFlagsSchema)) to avoid repeated compilation overhead on each getFeatureFlags invocation.

  • The FeatureFlagProvider useEffect depends on the whole config object, which will retrigger the effect whenever a new object reference is passed; to avoid unnecessary refetches, either narrow the dependency array to specific primitive fields (e.g., config.url, config.cache) or ensure callers memoize the config.

Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ServiceBlobStorage.getValidatedBlobDataObject`, you compile the AJV schema on every call; consider pre-compiling `FeatureFlagsSchema` once (e.g., `const validateFeatureFlags = ajv.compile(FeatureFlagsSchema)`) to avoid repeated compilation overhead on each `getFeatureFlags` invocation.
- The `FeatureFlagProvider` useEffect depends on the whole `config` object, which will retrigger the effect whenever a new object reference is passed; to avoid unnecessary refetches, either narrow the dependency array to specific primitive fields (e.g., `config.url`, `config.cache`) or ensure callers memoize the config.

## Individual Comments

### Comment 1
<location path="packages/ocom/service-blob-storage/src/service-blob-storage.ts" line_range="9-11" />
<code_context>
+const ajv = new Ajv2020({ allErrors: true });
+
+export class ServiceBlobStorage extends CellixServiceBlobStorage {
+	private getValidatedBlobDataObject<T>(schema: object, dataRaw: string): T {
+		const dataJson: unknown = JSON.parse(dataRaw);
+		const validate = ajv.compile(schema);
+		if (!validate(dataJson)) {
+			throw new Error(`Feature flag payload validation failed: ${ajv.errorsText(validate.errors)}`);
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid recompiling the AJV schema on every call to improve performance.

`getValidatedBlobDataObject` currently compiles the JSON schema on every call. Since `FeatureFlagsSchema` is static and this helper is only used for feature flags, compile the schema once (e.g., at module scope) and reuse the validator. If you plan to use this helper with multiple schemas, consider caching compiled validators in a map keyed by schema identity.

Suggested implementation:

```typescript
const ajv = new Ajv2020({ allErrors: true });
const validateFeatureFlags = ajv.compile(FeatureFlagsSchema);

```

```typescript
export class ServiceBlobStorage extends CellixServiceBlobStorage {
	private getValidatedBlobDataObject<T>(dataRaw: string): T {
		const dataJson: unknown = JSON.parse(dataRaw);
		if (!validateFeatureFlags(dataJson)) {
			throw new Error(`Feature flag payload validation failed: ${ajv.errorsText(validateFeatureFlags.errors)}`);
		}
		return dataJson as T;
	}

```

```typescript
	public async getFeatureFlags(): Promise<FeatureFlagsPayloadType> {
		const featureFlagsRaw = (await this.downloadBlobToString('public', FEATURE_FLAG_BLOB_NAME)) ?? FeatureFlagsLocal;
		return this.getValidatedBlobDataObject<FeatureFlagsPayloadType>(featureFlagsRaw);
	}

```
</issue_to_address>

### Comment 2
<location path="packages/ocom/service-blob-storage/src/feature-flags.ts" line_range="15-24" />
<code_context>
+	readonly FeatureFlags: readonly FeatureFlag[];
+}
+
+export const FeatureFlagsSchema = {
+	$schema: 'https://json-schema.org/draft/2020-12/schema',
+	type: 'object',
+	properties: {
+		FeatureFlags: {
+			type: 'array',
+			items: {
+				type: 'object',
+				properties: {
+					Name: { type: 'string' },
+					Description: { type: 'string' },
+					Value: { type: 'string' },
+					AllowedValues: {
+						type: 'array',
+						items: { type: 'string' },
+					},
+					RetirementDate: { type: 'string' },
+				},
+				additionalProperties: false,
+			},
+		},
+	},
+	additionalProperties: false,
+} as const;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Strengthen the feature flag schema by requiring the FeatureFlags property.

Right now `{}` passes this schema because `FeatureFlags` isn’t required. If the backend always expects a `FeatureFlags` array in the payload, add `required: ['FeatureFlags']` and, if needed, a `minItems` constraint (e.g. `0` or `1`) so invalid payloads are rejected instead of silently accepted.

Suggested implementation:

```typescript
export const FeatureFlagsSchema = {
	$schema: 'https://json-schema.org/draft/2020-12/schema',
	type: 'object',
	required: ['FeatureFlags'],
	properties: {
		FeatureFlags: {
			type: 'array',
			minItems: 0,

```

```typescript
					RetirementDate: { type: 'string' },
				},
				additionalProperties: false,
			},
		},
	},

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/ocom/service-blob-storage/src/service-blob-storage.ts Outdated
Comment on lines +15 to +24
export const FeatureFlagsSchema = {
$schema: 'https://json-schema.org/draft/2020-12/schema',
type: 'object',
properties: {
FeatureFlags: {
type: 'array',
items: {
type: 'object',
properties: {
Name: { type: 'string' },

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.

suggestion (bug_risk): Strengthen the feature flag schema by requiring the FeatureFlags property.

Right now {} passes this schema because FeatureFlags isn’t required. If the backend always expects a FeatureFlags array in the payload, add required: ['FeatureFlags'] and, if needed, a minItems constraint (e.g. 0 or 1) so invalid payloads are rejected instead of silently accepted.

Suggested implementation:

export const FeatureFlagsSchema = {
	$schema: 'https://json-schema.org/draft/2020-12/schema',
	type: 'object',
	required: ['FeatureFlags'],
	properties: {
		FeatureFlags: {
			type: 'array',
			minItems: 0,
					RetirementDate: { type: 'string' },
				},
				additionalProperties: false,
			},
		},
	},

@aaron-rabinowitz
aaron-rabinowitz requested a review from a team as a code owner August 11, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Port existing Feature Flag implementation into Cellix

1 participant