Skip to content

OCPBUGS-85121: Skip redundant navigate() in setPerspective when target matches current URL#16683

Open
TheRealJon wants to merge 2 commits into
openshift:mainfrom
TheRealJon:OCPBUGS-85121
Open

OCPBUGS-85121: Skip redundant navigate() in setPerspective when target matches current URL#16683
TheRealJon wants to merge 2 commits into
openshift:mainfrom
TheRealJon:OCPBUGS-85121

Conversation

@TheRealJon

@TheRealJon TheRealJon commented Jun 25, 2026

Copy link
Copy Markdown
Member

Closes #16254

Analysis / Root cause:

setPerspective() always called navigate() unconditionally, causing redundant page reloads for custom perspective plugins. When the target URL matched the current location, this triggered namespace handler validation loops and visible re-renders.

This also stripped the ?perspective= param in DetectPerspective to prevent it from persisting and re-triggering the effect.

Solution description:

Skip navigate() when the target URL matches the current location

Screenshots / screen recording:

Test setup:

Refer to reproduction steps in linked issue

Test cases:

Ensure reproduction steps in linked issue are no longer reproducible

Summary by CodeRabbit

  • Bug Fixes
    • Improved perspective handling by removing the perspective query parameter from the URL when present, keeping the address bar clean.
    • Prevented redundant navigation by only updating routes when the perspective value actually changes.
    • Refined route updates to ensure the current view stays consistent without extra page refreshes.
  • Tests
    • Updated router mocking and URL path/search handling to align with the improved navigation behavior.

…t matches current URL

setActivePerspective() always called navigate() unconditionally, causing
redundant page reloads for custom perspective plugins. When the target URL
matched the current location, this triggered namespace handler validation
loops and visible re-renders. This also stripped the ?perspective= param
in DetectContext to prevent it from persisting and re-triggering the effect.

Adapted from PR openshift#16410 by logonoff after files were merged into DetectContext.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. labels Jun 25, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@TheRealJon: This pull request references Jira Issue OCPBUGS-85121, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Closes #16254

Analysis / Root cause:

setPerspective() always called navigate() unconditionally, causing redundant page reloads for custom perspective plugins. When the target URL matched the current location, this triggered namespace handler validation loops and visible re-renders.

This also stripped the ?perspective= param in DetectPerspective to prevent it from persisting and re-triggering the effect.

Solution description:

Skip navigate() when the target URL matches the current location

Screenshots / screen recording:

Test setup:

Refer to reproduction steps in linked issue

Test cases:

Ensure reproduction steps in linked issue are no longer reproducible

Summary by CodeRabbit

  • Bug Fixes
  • Improved perspective selection and URL query parameter synchronization.
  • Optimized navigation behavior when switching perspectives.

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 openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Perspective handling now removes the perspective query parameter after processing it, and perspective updates skip navigation when the destination matches the current path. The router test mock was updated to reflect the new path string shape.

Changes

Perspective URL flow

Layer / File(s) Summary
Perspective parameter cleanup
frontend/packages/console-app/src/components/detect-context/DetectContext.tsx, frontend/packages/console-app/src/components/detect-context/__tests__/DetectContext.spec.tsx
The initialization effect cleans the URL, updates activePerspective when the query param changes the perspective, and replaces the URL when it does not; the test mock now includes useNavigate and full path serialization.
Perspective setter guard
frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts
setPerspective compares the computed target path with the current location and only calls navigate() when they differ, while preserving telemetry and perspective state updates.

Sequence Diagram(s)

Perspective parameter cleanup

sequenceDiagram
  participant DetectContext
  participant react-router
  participant window.location

  DetectContext->>window.location: read perspective query param
  DetectContext->>react-router: createPath(cleaned location)
  alt perspective differs from activePerspective
    DetectContext->>DetectContext: update activePerspective
  else perspective matches activePerspective
    DetectContext->>react-router: navigate(cleanedPath, { replace: true })
  end
Loading

Perspective setter guard

sequenceDiagram
  participant useValuesForPerspectiveContext
  participant react-router
  participant window.location

  useValuesForPerspectiveContext->>window.location: read current location
  useValuesForPerspectiveContext->>react-router: createPath(window.location)
  alt targetPath differs from current path
    useValuesForPerspectiveContext->>react-router: navigate(targetPath)
  else targetPath matches current path
    useValuesForPerspectiveContext-->>useValuesForPerspectiveContext: skip navigate()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the bug and the main behavior change, so it matches the changeset.
Description check ✅ Passed The description covers root cause, solution, test setup, and test cases, though several template sections are left blank.
Linked Issues check ✅ Passed The code matches #16254 by skipping redundant navigation, cleaning ?perspective, and preserving normal perspective switching.
Out of Scope Changes check ✅ Passed All changes are focused on perspective navigation and related tests, with no unrelated code paths added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Stable And Deterministic Test Names ✅ Passed Changed test file only uses static titles; no dynamic or time-varying data appears in any PR-added test name.
Test Structure And Quality ✅ Passed No Ginkgo/cluster tests were changed; the PR only updates frontend Jest/RTL tests and component logic, so the check is not applicable.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added; the PR only changes frontend TS/TSX code and Jest unit tests, with no Go test files or MicroShift-relevant APIs.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests were added; the PR only changes React/TypeScript frontend code and a RTL spec, with no multi-node assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Only frontend perspective-routing files and tests changed; no manifests, controllers, replicas, affinity, or node selectors were introduced.
Ote Binary Stdout Contract ✅ Passed Only frontend React components/tests changed; no main/init/suite code or stdout writes were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the PR only changes React components and a Jest unit test, with no IPv4 or external connectivity assumptions.
No-Weak-Crypto ✅ Passed Touched files only adjust routing/state; no MD5/SHA1/DES/RC4/3DES/Blowfish, ECB, custom crypto, or secret comparisons were found.
Container-Privileges ✅ Passed PR only changes frontend TSX/test files; no container/K8s manifests or privilege settings were added.
No-Sensitive-Data-In-Logs ✅ Passed No new console logging or similar sensitive-output paths were added; the only emitted value is a perspective ID in telemetry, not sensitive data.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@TheRealJon: This pull request references Jira Issue OCPBUGS-85121, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

Closes #16254

Analysis / Root cause:

setPerspective() always called navigate() unconditionally, causing redundant page reloads for custom perspective plugins. When the target URL matched the current location, this triggered namespace handler validation loops and visible re-renders.

This also stripped the ?perspective= param in DetectPerspective to prevent it from persisting and re-triggering the effect.

Solution description:

Skip navigate() when the target URL matches the current location

Screenshots / screen recording:

Test setup:

Refer to reproduction steps in linked issue

Test cases:

Ensure reproduction steps in linked issue are no longer reproducible

Summary by CodeRabbit

  • Bug Fixes
  • Improved context/perspective handling so the app now removes unnecessary URL parameters and keeps the address bar clean.
  • Prevented redundant navigation when the current perspective already matches the URL, reducing extra page updates.
  • Refined perspective switching to avoid unnecessary route changes while preserving the existing behavior.

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci Bot requested review from jhadvig and rhamilto June 25, 2026 18:30
@openshift-ci openshift-ci Bot added the component/core Related to console core functionality label Jun 25, 2026
@openshift-ci

openshift-ci Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: TheRealJon

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

The pull request process is described 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 approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jun 25, 2026

@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: 1

🤖 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/detect-context/useValuesForPerspectiveContext.ts`:
- Around line 41-44: The navigation guard in useValuesForPerspectiveContext is
comparing next against createPath(window.location), but next is built from the
router location and is basename-relative, so this can incorrectly trigger
navigate on the same route. Update the comparison to use the current router
location from useLocation() instead of window.location, and make sure the
location value is included in the callback dependencies alongside navigate and
createPath.
🪄 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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 23758d72-fae5-4e63-8584-9ee6616614ff

📥 Commits

Reviewing files that changed from the base of the PR and between 8242e4c and 2128951.

📒 Files selected for processing (3)
  • frontend/packages/console-app/src/components/detect-context/DetectContext.tsx
  • frontend/packages/console-app/src/components/detect-context/__tests__/DetectContext.spec.tsx
  • frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts

Comment on lines +41 to +44
// Only navigate if a path is provided to avoid unnecessary navigation
if (next && next !== createPath(window.location)) {
navigate(next);
}

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.

🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# How basename is configured for the router and how setActivePerspective `next` args are produced
rg -nP '\bbasename\b' frontend/public frontend/packages -g '*.ts*' -C2
rg -nP 'setActivePerspective\s*\(' frontend/public frontend/packages -g '*.ts*' -C2
rg -nP 'createPath\s*\(' frontend/packages -g '*.ts*' -C2

Repository: openshift/console

Length of output: 9757


🏁 Script executed:

sed -n '1,220p' frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts
printf '\n---\n'
sed -n '1,240p' frontend/packages/console-app/src/components/detect-context/DetectContext.tsx
printf '\n---\n'
sed -n '1,140p' frontend/packages/console-app/src/components/detect-context/PerspectiveDetector.tsx
printf '\n---\n'
sed -n '1,120p' frontend/public/components/app.tsx

Repository: openshift/console

Length of output: 19626


🏁 Script executed:

sed -n '1,220p' frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts
printf '\n---\n'
sed -n '1,240p' frontend/packages/console-app/src/components/detect-context/DetectContext.tsx
printf '\n---\n'
sed -n '1,140p' frontend/packages/console-app/src/components/detect-context/PerspectiveDetector.tsx
printf '\n---\n'
sed -n '330,360p' frontend/public/components/app.tsx

Repository: openshift/console

Length of output: 14933


🌐 Web query:

React Router BrowserRouter basename useLocation pathname basename-relative createPath window.location

💡 Result:

In React Router (v6 and later), the basename is integrated into the routing system to simplify path management [1][2]. When you configure a BrowserRouter with a basename (e.g., ), the router handles the basename automatically for most routing operations [2][3]. Key behaviors regarding basename and paths: 1. useLocation and basename: The useLocation hook returns a location object where the basename is stripped from the pathname [1]. For instance, if the browser URL is /app/home, useLocation will return a pathname of /home [1]. This ensures that your routes, defined as path="/home", match correctly without manual adjustment [1]. 2. Raw path access: If you specifically need the "full" URL including the basename, you should rely on the browser's native window.location.pathname [4]. The location object provided by React Router is intentionally abstracted to be relative to the application's root (the basename). 3. Relative Path Resolution (useResolvedPath and createPath): - The useResolvedPath hook resolves relative paths against the current location's pathname [5][6]. Because React Router handles the basename internally, paths resolved via this hook correctly respect the application structure [3]. - The createPath utility creates a string URL path from pathname, search, and hash components [7]. It is generally used for constructing paths to be consumed by navigation APIs; it does not automatically prepend a basename unless that basename is part of the provided path structure or handled by the router's internal navigation logic [8]. 4. Best Practices: - Avoid using window.location.pathname for routing logic inside your components; always use hooks like useLocation and useResolvedPath to ensure compatibility with your basename configuration [4]. - When performing manual path matching, use the location object provided by the router, as it provides the basename-stripped path, ensuring your logic remains consistent across different environments (e.g., development vs. production under different sub-paths) [4]. In summary, React Router v6+ abstracts the basename away from the location pathname, making it easier to write path-agnostic code while maintaining correct routing behavior [1][4].

Citations:


🏁 Script executed:

rg -n "createPath\(window\.location\)|window\.location\.pathname|createPath\(location\)" frontend/packages/console-app frontend/public frontend/packages/console-shared -C2

Repository: openshift/console

Length of output: 8225


🌐 Web query:

React Router basename useLocation pathname includes basename or stripped createPath(location)

💡 Result:

In React Router (v6 and later), the behavior of useLocation and basename is as follows: 1. useLocation pathname: The pathname returned by the useLocation hook has the basename stripped [1]. React Router automatically removes the configured basename from the current URL pathname so that your application code can work with paths relative to your application root [1][2]. 2. createPath behavior: The createPath utility function is a low-level helper that simply takes a partial location object (containing pathname, search, and hash) and joins them into a string [3]. It does not automatically prepend or strip a basename; it is basename-agnostic [3]. When navigating or creating links, React Router’s internal navigation logic (such as the navigate function or Link components) handles the basename automatically by prepending it when performing actions [4][5]. If you are manually constructing paths for navigation and want to respect the basename, you should rely on React Router's navigation hooks or utilities that are aware of the router context rather than manually appending the basename yourself [4][5]. If you specifically need to use createPath in a scenario involving a basename, you must manually prepend the basename to the pathname if necessary, as createPath will not perform this transformation for you [4][5].

Citations:


🏁 Script executed:

rg -n '"react-router|react-router-dom"' frontend/package.json frontend/packages/**/package.json -C1

Repository: openshift/console

Length of output: 321


Compare against the router location, not window.location. This app uses BrowserRouter basename={window.SERVER_FLAGS.basePath}, and the next values passed here are built from createPath(location), so they’re basename-relative. window.location includes the basename, which makes the guard miss same-route matches and trigger an unnecessary navigate. Use useLocation() for the comparison and add it to the callback deps.

🤖 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/detect-context/useValuesForPerspectiveContext.ts`
around lines 41 - 44, The navigation guard in useValuesForPerspectiveContext is
comparing next against createPath(window.location), but next is built from the
router location and is basename-relative, so this can incorrectly trigger
navigate on the same route. Update the comparison to use the current router
location from useLocation() instead of window.location, and make sure the
location value is included in the callback dependencies alongside navigate and
createPath.

@TheRealJon

Copy link
Copy Markdown
Member Author

/hold

Needs more verification. I think this breaks the behavior where we automatically navigate to the landing page for the perspective you switch to.

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 25, 2026
Previous fix prevented redundant navigations but broke perspective
switching. When user switches perspective via UI, setPerspective gets
called with no next path. Code skipped navigate entirely, preventing
landing page redirect.

Now navigate to '/' when perspective changes and no explicit path
provided. Router handles landing page redirect. Still skip navigate
when perspective unchanged to avoid redundant reloads.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@TheRealJon

Copy link
Copy Markdown
Member Author

/unhold

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 25, 2026

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

♻️ Duplicate comments (1)
frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts (1)

39-46: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use the router location for the no-op navigation guard.

Line 45 compares a router-relative targetPath against createPath(window.location). Under a BrowserRouter basename, that can miss same-route matches and still call navigate, which brings back the redundant reload/re-render path this fix is trying to remove. Compare against createPath(location) from useLocation() and add location to the callback deps.

Suggested fix
+ const location = useLocation();

  const setPerspective = useCallback<SetActivePerspective>(
    (newPerspective, next) => {
      const perspectiveChanged = newPerspective !== perspective;
      setLastPerspective(newPerspective);
      setActivePerspective(newPerspective);
      // Only navigate if perspective changed
      if (perspectiveChanged) {
        const targetPath = next || '/';
-       if (targetPath !== createPath(window.location)) {
+       if (targetPath !== createPath(location)) {
          navigate(targetPath);
        }
      }
      fireTelemetryEvent('Perspective Changed', { perspective: newPerspective });
    },
-   [setLastPerspective, setActivePerspective, navigate, fireTelemetryEvent, perspective],
+   [setLastPerspective, setActivePerspective, navigate, fireTelemetryEvent, perspective, location],
  );
🤖 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/detect-context/useValuesForPerspectiveContext.ts`
around lines 39 - 46, The no-op navigation guard in
useValuesForPerspectiveContext should compare the computed targetPath against
the router’s current location instead of window.location, since BrowserRouter
basename can make same-route checks miss and still trigger navigate. Update the
guard to use location from useLocation() inside the callback, and include
location in the callback’s dependency list so the check stays in sync with
router state.
🤖 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.

Duplicate comments:
In
`@frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts`:
- Around line 39-46: The no-op navigation guard in
useValuesForPerspectiveContext should compare the computed targetPath against
the router’s current location instead of window.location, since BrowserRouter
basename can make same-route checks miss and still trigger navigate. Update the
guard to use location from useLocation() inside the callback, and include
location in the callback’s dependency list so the check stays in sync with
router state.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: b9cedfa1-3cbf-4a8d-bf13-95f2e3462b6f

📥 Commits

Reviewing files that changed from the base of the PR and between 2128951 and 4c0b96e.

📒 Files selected for processing (1)
  • frontend/packages/console-app/src/components/detect-context/useValuesForPerspectiveContext.ts

@TheRealJon

Copy link
Copy Markdown
Member Author

/retest

@openshift-ci

openshift-ci Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@TheRealJon: 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 4c0b96e link false /test e2e-playwright

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

approved Indicates a PR has been approved by an approver from all required OWNERS files. component/core Related to console core functionality jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

setActivePerspective always triggers navigate(), causing redundant page reloads for custom perspective plugins

3 participants