Skip to content

fix(@redhat-cloud-services/frontend-components-config-utilities): reload nav on CRD change - #2391

Merged
karelhala merged 4 commits into
RedHatInsights:masterfrom
platex-rehor-bot:bot/RHCLOUD-49555
Jul 24, 2026
Merged

fix(@redhat-cloud-services/frontend-components-config-utilities): reload nav on CRD change#2391
karelhala merged 4 commits into
RedHatInsights:masterfrom
platex-rehor-bot:bot/RHCLOUD-49555

Conversation

@platex-rehor-bot

@platex-rehor-bot platex-rehor-bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description

When feoConfigEnabled is true and a developer edits deploy/frontend.yaml, the chokidar file watcher correctly reloads the CRD object in memory, but the browser never re-fetches navigation data — requiring a full dev-server restart to see changes.

This PR fixes the issue with two small changes in proxy.ts:

  1. Add watchFiles for the CRD path — webpack-dev-server now watches frontend.yaml and sends a live-reload signal to the browser when it changes.
  2. Strip caching headers on intercepted FEO responses — removes ETag/Last-Modified and sets Cache-Control: no-store so the browser cannot serve stale navigation data from its HTTP cache on page reload.

Also removes an unused matchNavigationRequest import.

RHCLOUD-49555


Anything reviewers should know?

  • The watchFiles option is only added when FEOFeaturesEnabled is true, so non-FEO setups are unaffected.
  • Cache-busting headers are only applied to interceptable requests (bundles-generated.json, search-index-generated.json, etc.) — other proxied responses retain their original caching behavior.
  • The onProxyRes handler already read frontendCrdRef.current at request time (not a snapshot), so the data path was correct — only the browser notification/caching was missing.

Checklist

  • Accessibility: color contrast, keyboard nav, screen reader tested (or N/A)
  • All PR checks pass locally (build, lint, test)
  • No unrelated changes included

AI disclosure

Assisted by: Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for an optional local CRD interception server in “dev-proxy” mode, including automatic proxy route setup when enabled.
  • Bug Fixes

    • Improved reload behavior after frontend CRD updates by ensuring connected browsers refresh after changes.
    • Prevented intercepted responses from being skipped or reused by disabling conditional caching behavior (avoiding 304 bypass) and forcing no-cache responses.

…oad nav on CRD change

RHCLOUD-49555
When feoConfigEnabled is true and the user edits deploy/frontend.yaml,
the chokidar watcher correctly reloads the CRD object in memory but the
browser never re-fetches navigation data.

Two changes fix this:

1. Add watchFiles for the CRD path so webpack-dev-server sends a
   live-reload signal to the browser on file change.
2. Strip ETag/Last-Modified and set Cache-Control: no-store on
   intercepted FEO responses so the browser cannot serve stale
   navigation from its HTTP cache.

Also removes unused matchNavigationRequest import.
@platex-rehor-bot
platex-rehor-bot requested a review from a team as a code owner July 23, 2026 09:56
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Walkthrough

The change adds a local FEO CRD interceptor, routes eligible dev-proxy requests through it, prevents conditional caching from bypassing modifications, and reloads connected browsers after CRD updates.

Changes

FEO CRD interception and reload

Layer / File(s) Summary
CRD interceptor server
packages/config/src/bin/crd-interceptor-server.ts
Watches and loads the frontend CRD, modifies eligible upstream responses, supports passthrough behavior, removes conditional request headers, and handles upstream errors and shutdown.
Dev-proxy interceptor wiring
packages/config/src/bin/dev-proxy-script.ts
Starts the interceptor when FEO is enabled, adds routes for interceptable paths, passes its port into route generation, and closes it during cleanup.
Proxy cache handling and browser reload
packages/config-utils/src/proxy.ts
Removes conditional and response caching headers for intercepted requests and broadcasts content-changed after the in-memory CRD is refreshed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant DevProxy
  participant CRDInterceptor
  participant Upstream
  participant WebpackDevServer

  DevProxy->>DevProxy: Detect FEO and load frontend CRD
  DevProxy->>CRDInterceptor: Start interceptor server
  Browser->>DevProxy: Request interceptable API path
  DevProxy->>CRDInterceptor: Forward request
  CRDInterceptor->>Upstream: Send request without conditional headers
  Upstream-->>CRDInterceptor: Return response
  CRDInterceptor-->>Browser: Return CRD-modified response with no-cache headers
  CRDInterceptor->>DevProxy: Detect CRD update
  DevProxy->>WebpackDevServer: Send content-changed
  WebpackDevServer-->>Browser: Trigger reload
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main CRD-change reload fix.
Description check ✅ Passed The description matches the template and includes the summary, issue link, reviewer notes, checklist, and AI disclosure.
✨ 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

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 `@packages/config-utils/src/proxy.ts`:
- Around line 246-250: Update the intercepted-request handling in proxy.ts to
remove incoming If-None-Match and If-Modified-Since headers before forwarding
the request, ensuring conditional requests cannot produce reusable 304 responses
for CRD content. Keep the existing response cache-header removal in place.
- Around line 342-346: Coordinate the webpack-dev-server reload configured by
watchFiles with the chokidar callback that updates frontendCrdRef.current,
ensuring reloads occur only after a successful CRD read and assignment. Update
the existing frontend CRD watcher flow and avoid relying on the independent
watchFiles trigger when it can race the in-memory update.
🪄 Autofix (Beta)

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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 09821cd3-1a5d-416a-af00-68b7401b7abd

📥 Commits

Reviewing files that changed from the base of the PR and between 5a544b0 and 4aad0a3.

📒 Files selected for processing (1)
  • packages/config-utils/src/proxy.ts

Comment thread packages/config-utils/src/proxy.ts
Comment thread packages/config-utils/src/proxy.ts Outdated
…vent stale CRD responses

RHCLOUD-49555

Strip If-None-Match and If-Modified-Since request headers on
intercepted FEO responses so upstream 304s cannot bypass CRD content
modifications.

Replace independent watchFiles reload with a coordinated trigger
from the chokidar CRD watcher, ensuring the browser only reloads
after the in-memory CRD has been updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@charlesmulder charlesmulder changed the title fix(config-utils): reload navigation on CRD file change fix(@redhat-cloud-services/frontend-components-config-utilities): reload navigation on CRD file change Jul 23, 2026
@karelhala

karelhala commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Investigation: This PR only fixes fec dev, not fec dev-proxy

I investigated the CRD navigation interception flow end-to-end and found that this PR correctly fixes fec dev mode, but the more commonly used fec dev-proxy mode has no CRD interception at all — by design, not by bug.

Root Cause Analysis

fec dev mode (what this PR fixes):

  • Uses webpack-dev-server with proxy.ts middleware
  • proxy.ts intercepts responses via onProxyRes, calls modifyRequest() which applies navigationInterceptor(), searchInterceptor(), moduleInterceptor(), serviceTilesInterceptor()
  • The CRD file watcher updates frontendCrdRef.current and this PR adds watchFiles + cache-busting headers ✅

fec dev-proxy mode (not addressed):

  • Runs 3 concurrent processes: webpack watch, http-server, and a Docker container (frontend-development-proxy) running Caddy
  • dev-proxy.webpack.config.ts explicitly deletes the devServer config: delete devConfig.devServer
  • No webpack-dev-server proxy middleware runs → proxy.ts is never loaded
  • Caddy routes /api/* directly to console.stage.redhat.com as a passthrough
  • Navigation API responses (/api/chrome-service/v1/static/bundles-generated.json, etc.) come straight from stage — local frontend.yaml is never consulted

Architecture Diagram

fec dev (this PR fixes):
  Browser → webpack-dev-server (:1337)
               └── proxy.ts intercepts /api/chrome-service/* responses
                   └── modifyRequest() applies local CRD changes ✅

fec dev-proxy (NOT fixed):
  Browser → Caddy container (:1337)
               ├── /apps/<app>/* → http-server (:8003) → local build
               ├── /apps/chrome/* → chrome container (:9998)
               ├── /api/* → console.stage.redhat.com (passthrough, no interception) ❌
               └── * → console.stage.redhat.com

Proposed Solution for fec dev-proxy

Add a Node interceptor server as a 4th concurrent process in dev-proxy-script.ts:

  1. New file crd-interceptor-server.ts in packages/config/src/bin/:

    • Express/http server on a configurable port (e.g., 9997)
    • Reads frontendCRDPath from fec config
    • Watches frontend.yaml with chokidar (same pattern as proxy.ts)
    • Intercepts the 4 URL patterns identified by isInterceptAbleRequest():
      • /api/chrome-service/v1/static/bundles-generated.json
      • /api/chrome-service/v1/static/search-index-generated.json
      • /api/chrome-service/v1/static/service-tiles-generated.json
      • /api/chrome-service/v1/static/fed-modules-generated.json
    • Proxies requests to HCC_ENV_URL with forwarded cookies/headers
    • Applies modifyRequest() from config-utils to response body
    • Returns modified JSON to browser
  2. Modify dev-proxy-script.ts:

    • Start the interceptor server before concurrently
    • Add the 4 interceptable routes to createRoutesConfig() pointing to host.docker.internal:<interceptor-port>
    • Caddy's {$LOCAL_ROUTES} is injected before the generic /api/* handler in the Caddyfile, so these specific routes will take precedence
    • Include interceptor in cleanup
  3. No changes needed to frontend-development-proxy container — all interception happens host-side

This reuses 100% of the existing interception code (modifyRequest, navigationInterceptor, searchInterceptor, moduleInterceptor, serviceTilesInterceptor) and fits naturally into the existing concurrent process architecture.

Why not a Caddy plugin?

  • All interception logic is TypeScript — rewriting in Go for a custom Caddy module would duplicate significant code
  • Caddy has no native JSON response body modification
  • Container image rebuild on every change during development
  • Node interceptor reuses the existing codebase with zero duplication

Related

  • JIRA: RHCLOUD-49555
  • Tested with insights-rbac-ui running fec dev-proxy — confirmed navigation changes to deploy/frontend.yaml are not reflected

TL;DR: This PR is correct for fec dev. A separate feature is needed to bring CRD interception to fec dev-proxy via a Node interceptor server that sits between Caddy and stage for the 4 chrome-service API endpoints.

…rception to dev-proxy mode

RHCLOUD-49555
When feoConfigEnabled is true, fec dev-proxy now starts a CRD
interceptor server that proxies the 4 chrome-service API endpoints
through a local Node server which applies modifyRequest() to inject
local frontend.yaml changes. This brings CRD interception parity
between fec dev and fec dev-proxy, reusing 100% of the existing
interception code from config-utils.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@platex-rehor-bot

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough investigation @karelhala! Your analysis of the fec dev vs fec dev-proxy architecture is spot on.

I've implemented your proposed solution — a CRD interceptor server that runs as a host-side Node process alongside the existing dev-proxy concurrent processes. Here's what the new commit adds:

New file: crd-interceptor-server.ts

  • HTTP server on port 9997 that intercepts the 4 chrome-service API endpoints
  • Reads and watches frontend.yaml with chokidar (same pattern as proxy.ts)
  • Proxies requests to HCC_ENV_URL, applies modifyRequest() to responses
  • Returns modified JSON with no-store cache headers
  • Handles stage proxy via HttpsProxyAgent
  • Reuses 100% of the existing interception code from config-utils

Modified: dev-proxy-script.ts

  • On startup, checks if feoConfigEnabled is true in the CRD
  • If enabled, starts the interceptor server before route generation
  • Adds the 4 interceptable API paths to createRoutesConfig() pointing to host.docker.internal:9997
  • Caddy's LOCAL_ROUTES take precedence over the generic /api/* handler, so these specific paths are intercepted while everything else flows to stage as before
  • Interceptor server is cleaned up on shutdown

Architecture

fec dev-proxy (now with CRD interception):
  Browser → Caddy container (:1337)
               ├── /apps/<app>/* → http-server (:8003) → local build
               ├── /apps/chrome/* → chrome container (:9998)
               ├── /api/chrome-service/v1/static/*-generated.json → interceptor (:9997)
               │       └── upstream (HCC_ENV_URL) → modifyRequest() → modified JSON ✅
               ├── /api/* → console.stage.redhat.com (passthrough)
               └── * → console.stage.redhat.com

No changes needed to the frontend-development-proxy container — all interception happens host-side.

Build passes, all 55 config + 128 config-utils unit tests pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/config/src/bin/crd-interceptor-server.ts (1)

49-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer unknown[] over any[] for log data.

log's ...data: any[] bypasses type checking on log payloads.

-  const log = (logType: LogType, ...data: any[]) => {
+  const log = (logType: LogType, ...data: unknown[]) => {

As per path instructions, "Flag use of 'any' types - suggest specific types or 'unknown' with type guards" for packages/*/src/**/*.{ts,tsx}.

🤖 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 `@packages/config/src/bin/crd-interceptor-server.ts` around lines 49 - 52,
Update the log function’s variadic data parameter from any[] to unknown[] while
preserving the existing debug filtering and fecLogger delegation behavior.

Source: Path instructions

🤖 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 `@packages/config-utils/src/proxy.ts`:
- Around line 247-254: Add comprehensive unit tests for the proxy’s onProxyReq
handler and triggerReload flow. Verify conditional headers are removed only when
FEOFeaturesEnabled, frontendCrdRef.current, and isInterceptAbleRequest(req.url)
all permit interception, while preserving them otherwise; also verify
triggerReload invokes server.sendMessage with the content-changed event. Use the
existing proxy test setup and mocks.

In `@packages/config/src/bin/crd-interceptor-server.ts`:
- Around line 96-137: Configure explicit timeouts for both upstream requests
created in the main handler and proxyPassthrough, using the existing
https.request calls as the change points. Handle timeout events by terminating
the stalled proxy request and completing the client response with the
established upstream-failure behavior, ensuring no request remains pending
indefinitely.
- Around line 62-69: Add an 'error' event handler to the chokidar watcher
created in the frontendCRDPath watch chain, logging the watcher error through
the existing log(LogType.error, ...) mechanism. Keep the existing change handler
and CRD reload behavior unchanged.
- Around line 86-106: Update the interceptor server setup and request handler to
listen explicitly on 127.0.0.1, validate that the incoming URL is a relative
path included in INTERCEPTABLE_PATHS, and reject or normalize absolute req.url
values before constructing upstreamUrl with hccEnvUrl. Preserve proxying only
for allowed interceptable paths and prevent arbitrary upstream hosts.

In `@packages/config/src/bin/dev-proxy-script.ts`:
- Around line 230-249: Update the CRD interception startup flow around
readFrontendCRD, hasFEOFeaturesEnabled, and startCRDInterceptorServer so
expected CRD-read failures retain the benign “features not available” handling,
while failures from startCRDInterceptorServer are logged separately with an
accurate message and the underlying error details. Ensure interceptorPort
remains unset when server startup fails.

---

Nitpick comments:
In `@packages/config/src/bin/crd-interceptor-server.ts`:
- Around line 49-52: Update the log function’s variadic data parameter from
any[] to unknown[] while preserving the existing debug filtering and fecLogger
delegation behavior.
🪄 Autofix (Beta)

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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: b6c433e1-0675-485a-ade3-33825836ec7e

📥 Commits

Reviewing files that changed from the base of the PR and between 4aad0a3 and 2dca5ad.

📒 Files selected for processing (3)
  • packages/config-utils/src/proxy.ts
  • packages/config/src/bin/crd-interceptor-server.ts
  • packages/config/src/bin/dev-proxy-script.ts

Comment on lines +247 to +254
onProxyReq: (proxyReq, req) => {
// Strip conditional request headers for intercepted FEO responses to
// prevent 304 responses that would bypass CRD content modifications
if (FEOFeaturesEnabled && frontendCrdRef.current && isInterceptAbleRequest(req.url)) {
proxyReq.removeHeader('if-none-match');
proxyReq.removeHeader('if-modified-since');
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add unit tests for the new onProxyReq header-stripping and triggerReload reload signaling.

Neither the conditional-request header removal (Lines 247-254) nor the triggerReloadserver.sendMessage(..., 'content-changed') wiring (Lines 357-365) appear to have accompanying tests. As per coding guidelines, "All functions in @redhat-cloud-services/frontend-components-config-utilities must have comprehensive unit tests."

Also applies to: 357-365

🤖 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 `@packages/config-utils/src/proxy.ts` around lines 247 - 254, Add comprehensive
unit tests for the proxy’s onProxyReq handler and triggerReload flow. Verify
conditional headers are removed only when FEOFeaturesEnabled,
frontendCrdRef.current, and isInterceptAbleRequest(req.url) all permit
interception, while preserving them otherwise; also verify triggerReload invokes
server.sendMessage with the content-changed event. Use the existing proxy test
setup and mocks.

Source: Coding guidelines

Comment thread packages/config/src/bin/crd-interceptor-server.ts Outdated
Comment thread packages/config/src/bin/crd-interceptor-server.ts
Comment thread packages/config/src/bin/crd-interceptor-server.ts
Comment thread packages/config/src/bin/dev-proxy-script.ts
…terceptor

RHCLOUD-49555
Address CodeRabbit review: bind to 127.0.0.1, validate interceptable
paths, add request timeouts, chokidar error handler, unknown[] types,
separate error handling for interceptor startup failures. Fix prettier
formatting for createRoutesConfig params.
@platex-rehor-bot platex-rehor-bot changed the title fix(@redhat-cloud-services/frontend-components-config-utilities): reload navigation on CRD file change fix(@redhat-cloud-services/frontend-components-config-utilities): reload nav on CRD change Jul 23, 2026
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.

2 participants