Skip to content

Flush async feature flag updates immediately - #16949

Open
kchawlani19 wants to merge 1 commit into
openshift:mainfrom
kchawlani19:fix-16922-async-feature-flag
Open

Flush async feature flag updates immediately#16949
kchawlani19 wants to merge 1 commit into
openshift:mainfrom
kchawlani19:fix-16922-async-feature-flag

Conversation

@kchawlani19

@kchawlani19 kchawlani19 commented Aug 11, 2026

Copy link
Copy Markdown

Summary

  • Fixes #16922: flag-gated plugin routes/nav items stayed 404/missing for ~10–15s after an async console.flag / console.flag/hookProvider resolved.
  • Root cause: useFeatureFlagController only queued setFeatureFlag into a ref and flushed on the next render, so async updates (e.g. after a backend probe) waited on an unrelated re-render.
  • Keep render-time updates deferred (react-redux safety), but flush immediately when called outside render. Adds unit coverage for sync deferral and async immediate dispatch.

Test plan

  • Unit: jest packages/console-app/src/components/flags/__tests__/FeatureFlagExtensionLoader.spec.tsx
  • Install a dynamic plugin with an async console.flag/hookProvider that gates a console.page/route and nav item
  • Confirm the flag probe completes quickly and the route/nav appear without a ~10s wait
  • Confirm sync flag handlers that set flags during render still work (no "Cannot update a component while rendering" errors)

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes

    • Improved feature flag updates to apply at the appropriate time, including immediate updates after rendering.
    • Prevented unnecessary updates when a feature flag is already set to the requested value.
  • Tests

    • Added coverage for deferred updates, asynchronous updates, and redundant update prevention.

Deferring all setFeatureFlag calls until the next render left
flag-gated plugin routes and nav items missing for ~10s after an
async console.flag/hookProvider resolved. Keep render-time updates
deferred, but dispatch async updates right away.

Fixes openshift#16922

Co-authored-by: Cursor <cursoragent@cursor.com>
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci
openshift-ci Bot requested review from fsgreco and rhamilto August 11, 2026 12:48
@openshift-ci

openshift-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: kchawlani19
Once this PR has been reviewed and has the lgtm label, please assign rhamilto for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the component/core Related to console core functionality label Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The feature flag controller is now exported, tracks current flag values, defers render-time updates until the layout effect, flushes post-render updates immediately, and skips redundant dispatches. Tests cover each update path.

Changes

Feature flag controller

Layer / File(s) Summary
Controller update flow
frontend/packages/console-app/src/components/flags/FeatureFlagExtensionLoader.tsx, frontend/packages/console-app/src/components/flags/__tests__/FeatureFlagExtensionLoader.spec.tsx
useFeatureFlagController now tracks flag state with refs, queues updates during render, flushes them in the layout effect, handles asynchronous updates immediately, and avoids dispatching unchanged values. Tests validate these behaviors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: logonoff

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Flush async feature flag updates immediately' directly describes the main change: making asynchronous feature flag updates dispatch immediately instead of waiting for the next render.
Description check ✅ Passed The description includes analysis (root cause of the ~10-15s delay), solution details (deferred render-time updates, immediate async dispatch), test plan with unit and manual testing steps, and confirms reviewer guidance.
Linked Issues check ✅ Passed The PR directly addresses issue #16922: flag-gated plugin routes/nav items that remained unavailable for ~10-15s after async flag resolution. The implementation keeps render-time updates deferred while dispatching async updates immediately, which resolves the root cause of the delayed manifest refetch.
Out of Scope Changes check ✅ Passed All changes are within scope: exporting useFeatureFlagController, implementing deferred/immediate flush logic based on call context, and adding unit tests for the new behavior. No unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Stable And Deterministic Test Names ✅ Passed The changed tests use Jest describe/it, not Ginkgo, and all three titles are static descriptive strings with no dynamic values.
Test Structure And Quality ✅ Passed The changed test is a Jest/React Testing Library suite with three focused it blocks and mocked hooks; it contains no Ginkgo tests, cluster operations, or Eventually/Consistently waits.
Microshift Test Compatibility ✅ Passed The PR changes only a TypeScript implementation and Jest spec; it adds no Ginkgo e2e tests, so MicroShift API compatibility checks do not apply.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The PR adds only frontend Jest tests using renderHook and describe/it; it adds no Ginkgo e2e tests or multi-node/HA assumptions requiring SNO protection.
Topology-Aware Scheduling Compatibility ✅ Passed The pull request changes only a React feature-flag hook and unit tests; it adds no deployment manifests, operators, controllers, replicas, affinity, tolerations, node selectors, or topology constra...
Ote Binary Stdout Contract ✅ Passed The PR changes only two frontend TS/TSX files; the diff contains no OTE binary, Go process-level setup, or stdout write.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The only added test is a Jest React .spec.tsx test, not a Ginkgo e2e test, and changed files contain no IPv4 literals or external network access.
No-Weak-Crypto ✅ Passed The patch only changes feature-flag controller logic and tests; the exact diff contains no weak-crypto APIs, custom cryptography, or secret/token comparisons.
Container-Privileges ✅ Passed The PR changes only TypeScript hook code and tests; no container/Kubernetes manifests or privileged, host namespace, SYS_ADMIN, or escalation settings were added.
No-Sensitive-Data-In-Logs ✅ Passed The PR adds no logging or sensitive values; it only dispatches boolean flags. The existing plugin-name/error console.error is unchanged from the parent.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/packages/console-app/src/components/flags/__tests__/FeatureFlagExtensionLoader.spec.tsx`:
- Around line 36-47: Update the renderHook test around useFeatureFlagController
to record mockDispatch’s call count immediately after setFeatureFlag runs during
render, assert it is unchanged before layout effects flush, then retain the
post-render assertions verifying the deferred update dispatches once with the
expected flag.

In
`@frontend/packages/console-app/src/components/flags/FeatureFlagExtensionLoader.tsx`:
- Around line 45-51: Update flushPendingUpdates so that after dispatching each
changed flag via setFlag, flagsRef.current records the dispatched enabled value
before pendingUpdatesRef.current is cleared. Add a regression test covering
consecutive true then false updates without a selector re-render, verifying both
dispatches occur.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f564b80-5d5d-47a1-a0b9-c82d1c721727

📥 Commits

Reviewing files that changed from the base of the PR and between b143108 and 66cbc8d.

📒 Files selected for processing (2)
  • frontend/packages/console-app/src/components/flags/FeatureFlagExtensionLoader.tsx
  • frontend/packages/console-app/src/components/flags/__tests__/FeatureFlagExtensionLoader.spec.tsx

Comment on lines +36 to +47
it('defers flag updates made during render until after layout effects', () => {
const { result } = renderHook(() => {
const setFeatureFlag = useFeatureFlagController();
// Simulate console.flag/hookProvider handlers that set flags during render.
setFeatureFlag('SYNC_FLAG', true);
return setFeatureFlag;
});

expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockSetFlag).toHaveBeenCalledWith('SYNC_FLAG', true);
expect(result.current).toEqual(expect.any(Function));
});

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that no dispatch occurs during render.

Line 44 runs after layout effects complete. A regression that dispatches directly during render still produces one dispatch and passes this test. Capture the dispatch count immediately after setFeatureFlag in the render callback, then assert that the count did not change until the layout effect flushes the update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/console-app/src/components/flags/__tests__/FeatureFlagExtensionLoader.spec.tsx`
around lines 36 - 47, Update the renderHook test around useFeatureFlagController
to record mockDispatch’s call count immediately after setFeatureFlag runs during
render, assert it is unchanged before layout effects flush, then retain the
post-render assertions verifying the deferred update dispatches once with the
expected flag.

Comment on lines +45 to 51
const flushPendingUpdates = useCallback(() => {
pendingUpdatesRef.current.forEach((enabled, flag) => {
if (flags.get(flag) !== enabled) {
if (flagsRef.current.get(flag) !== enabled) {
dispatch(setFlag(flag, enabled));
}
});
pendingUpdatesRef.current.clear();

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Update flagsRef after each dispatched flag update.

Line 47 reads the last selector snapshot, not the last dispatched value. If a flag is currently false, two post-layout calls can set it to true and then false before React re-renders. The first call dispatches true. The second call sees the stale false value and skips its required dispatch. The Redux flag remains true.

Record the dispatched value in flagsRef before clearing the pending update. Add a regression test for consecutive true then false calls without a selector re-render.

Proposed fix
       if (flagsRef.current.get(flag) !== enabled) {
         dispatch(setFlag(flag, enabled));
+        flagsRef.current = flagsRef.current.set(flag, enabled);
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const flushPendingUpdates = useCallback(() => {
pendingUpdatesRef.current.forEach((enabled, flag) => {
if (flags.get(flag) !== enabled) {
if (flagsRef.current.get(flag) !== enabled) {
dispatch(setFlag(flag, enabled));
}
});
pendingUpdatesRef.current.clear();
const flushPendingUpdates = useCallback(() => {
pendingUpdatesRef.current.forEach((enabled, flag) => {
if (flagsRef.current.get(flag) !== enabled) {
dispatch(setFlag(flag, enabled));
flagsRef.current = flagsRef.current.set(flag, enabled);
}
});
pendingUpdatesRef.current.clear();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/console-app/src/components/flags/FeatureFlagExtensionLoader.tsx`
around lines 45 - 51, Update flushPendingUpdates so that after dispatching each
changed flag via setFlag, flagsRef.current records the dispatched enabled value
before pendingUpdatesRef.current is cleared. Add a regression test covering
consecutive true then false updates without a selector re-render, verifying both
dispatches occur.

@kchawlani19

Copy link
Copy Markdown
Author

/test backend

@kchawlani19

Copy link
Copy Markdown
Author

/retest

@openshift-ci

openshift-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@kchawlani19: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-playwright-techpreview 66cbc8d link false /test e2e-playwright-techpreview

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/core Related to console core functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flag-gated plugin routes/nav items stay 404/missing for ~10-15s after the flag resolves (async console.flag/hookProvider)

1 participant