Skip to content

OCPBUGS-85545: Fix guided tour modal flash on page reload#16462

Open
Cragsmann wants to merge 1 commit into
openshift:mainfrom
Cragsmann:OCPBUGS-85545
Open

OCPBUGS-85545: Fix guided tour modal flash on page reload#16462
Cragsmann wants to merge 1 commit into
openshift:mainfrom
Cragsmann:OCPBUGS-85545

Conversation

@Cragsmann
Copy link
Copy Markdown
Contributor

@Cragsmann Cragsmann commented May 19, 2026

Summary

  • Fix the "Welcome to the new OpenShift experience" guided tour modal briefly appearing on every page reload for users who have already completed or skipped the tour
  • Root cause: stale-frame race between useReducer initialization (defaults startTour: true while completed is undefined) and useEffect-based sync with user preferences
  • Gate tour context on reducer state reflecting the loaded completion flag, not just on loaded being true

Jira

https://redhat.atlassian.net/browse/OCPBUGS-85545

Test plan

  • Unit tests added for tour-context initialization behavior
  • Log in as a user who has completed/skipped the guided tour
  • Hard reload the page (F5 / Cmd+R)
  • Verify the "Welcome to the new OpenShift experience" modal does not flash
  • Verify the tour still works correctly for first-time users

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where the tour feature would incorrectly auto-start when application data finished loading, even if the user had already completed the tour.
  • Tests

    • Added test coverage to verify the tour does not automatically start upon data loading when previously marked as completed.

Prevent the "Welcome to the new OpenShift experience" modal from
briefly appearing on every page reload for users who have already
completed or skipped the tour. The stale-frame race between
useReducer initialization and useEffect-based sync with user
preferences caused startTour to default to true before the loaded
completion state was applied.

Co-Authored-By: Claude Opus 4.6 <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 May 19, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@Cragsmann: This pull request references Jira Issue OCPBUGS-85545, 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 ASSIGNED, 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:

Summary

  • Fix the "Welcome to the new OpenShift experience" guided tour modal briefly appearing on every page reload for users who have already completed or skipped the tour
  • Root cause: stale-frame race between useReducer initialization (defaults startTour: true while completed is undefined) and useEffect-based sync with user preferences
  • Gate tour context on reducer state reflecting the loaded completion flag, not just on loaded being true

Jira

https://redhat.atlassian.net/browse/OCPBUGS-85545

Test plan

  • Unit tests added for tour-context initialization behavior
  • Log in as a user who has completed/skipped the guided tour
  • Hard reload the page (F5 / Cmd+R)
  • Verify the "Welcome to the new OpenShift experience" modal does not flash
  • Verify the tour still works correctly for first-time users

🤖 Generated with Claude Code

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 spadgett May 19, 2026 12:42
@openshift-ci openshift-ci Bot added the component/core Related to console core functionality label May 19, 2026
@openshift-ci-robot
Copy link
Copy Markdown
Contributor

@Cragsmann: This pull request references Jira Issue OCPBUGS-85545, 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:

Summary

  • Fix the "Welcome to the new OpenShift experience" guided tour modal briefly appearing on every page reload for users who have already completed or skipped the tour
  • Root cause: stale-frame race between useReducer initialization (defaults startTour: true while completed is undefined) and useEffect-based sync with user preferences
  • Gate tour context on reducer state reflecting the loaded completion flag, not just on loaded being true

Jira

https://redhat.atlassian.net/browse/OCPBUGS-85545

Test plan

  • Unit tests added for tour-context initialization behavior
  • Log in as a user who has completed/skipped the guided tour
  • Hard reload the page (F5 / Cmd+R)
  • Verify the "Welcome to the new OpenShift experience" modal does not flash
  • Verify the tour still works correctly for first-time users

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

  • Fixed an issue where the tour feature would incorrectly auto-start when application data finished loading, even if the user had already completed the tour.

  • Tests

  • Added test coverage to verify the tour does not automatically start upon data loading when previously marked as completed.

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.

Copy link
Copy Markdown
Member

@jhadvig jhadvig left a comment

Choose a reason for hiding this comment

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

One nit otherwise LGTM

useUserPreferenceMock.mockReturnValue([{ dev: { completed: true } }, () => null, true]);
rerender();
const { tourState } = result.current;
expect(tourState?.startTour).not.toBe(true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
expect(tourState?.startTour).not.toBe(true);
expect(tourState?.startTour).toBe(false);

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 19, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Cragsmann, jhadvig

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 May 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

This PR fixes a state initialization race condition in the tour context hook. The change introduces an initialization guard using useRef to ensure that useTourValuesForContext does not return early with context data until both the tour data is loaded and the hook has explicitly observed the loaded state. A new test case verifies the hook correctly handles the transition from unloaded to loaded state when the tour is already marked completed, preventing an unwanted startTour signal from being sent during data fetch.

🚥 Pre-merge checks | ✅ 12
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly references the Jira issue and accurately describes the main fix: preventing the guided tour modal from flashing on page reload.
Description check ✅ Passed The description covers root cause analysis, solution, Jira reference, and test plan. However, several template sections are incomplete: missing screenshots/recordings, browser conformance testing, and reviewer/assignee tags.
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.
Stable And Deterministic Test Names ✅ Passed Custom check is for Ginkgo tests (Go framework). PR changes are in a Jest test file (TypeScript/React) with stable, deterministic test names. Check not applicable.
Test Structure And Quality ✅ Passed Custom check for Ginkgo test structure not applicable. This PR modifies React/TypeScript frontend code with Jest tests, not Go/Ginkgo tests. Check criteria are irrelevant.
Microshift Test Compatibility ✅ Passed PR contains JavaScript/TypeScript Jest unit tests for frontend. MicroShift check applies only to Go-based Ginkgo e2e tests. Not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Check applies only to Ginkgo e2e tests. PR adds a Jest unit test for React frontend code, not a Go-based Ginkgo e2e test. Check not applicable.
Topology-Aware Scheduling Compatibility ✅ Passed Check is not applicable. PR modifies only React component files (tour-context.ts and test), not deployment manifests, operator code, or controllers. No scheduling constraints are introduced.
Ote Binary Stdout Contract ✅ Passed OTE Binary Stdout Contract is for Go binaries. PR contains only TypeScript/React frontend code with no Go binary, main(), or test suite setup—check not applicable.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests added. PR contains only Jest unit tests for a React component. The IPv6/disconnected network check applies only to Go Ginkgo e2e tests, not frontend unit tests.

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

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/packages/console-app/src/components/tour/tour-context.ts (1)

161-163: 💤 Low value

Update the effect comment to reflect the loaded dependency.

The comment states the effect should "only run when the active perspective changes," but the effect now explicitly depends on loaded and contains conditional logic based on that flag. The fix requires the effect to run when loaded transitions to true.

📝 Suggested comment update
-    // only run effect when the active perspective changes
+    // run effect when the active perspective or loaded state changes
     // eslint-disable-next-line react-hooks/exhaustive-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/tour/tour-context.ts` around
lines 161 - 163, Update the misleading effect comment to mention both
dependencies: change the comment above the useEffect that currently references
only "active perspective" to state that the effect runs when either
activePerspective or loaded changes and that it specifically handles the
transition when loaded becomes true; locate the comment immediately above the
effect that includes the dependency array [activePerspective, loaded] and update
it to accurately reflect that the effect depends on and reacts to loaded as well
as activePerspective.
🤖 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/tour/__tests__/tour-context.spec.ts`:
- Around line 143-156: Update the test for useTourValuesForContext so it asserts
the tour context is actually returned and that startTour is explicitly false:
after rerender, add an assertion that result.current.tour is not null (ensuring
the context was initialized) and change the startTour assertion to expect(false)
(i.e., expect(result.current.tourState?.startTour).toBe(false)) instead of only
checking it is not true; reference useTourValuesForContext, tour, tourState, and
startTour when locating the test to modify.

---

Nitpick comments:
In `@frontend/packages/console-app/src/components/tour/tour-context.ts`:
- Around line 161-163: Update the misleading effect comment to mention both
dependencies: change the comment above the useEffect that currently references
only "active perspective" to state that the effect runs when either
activePerspective or loaded changes and that it specifically handles the
transition when loaded becomes true; locate the comment immediately above the
effect that includes the dependency array [activePerspective, loaded] and update
it to accurately reflect that the effect depends on and reacts to loaded as well
as activePerspective.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: b1454efb-1472-4a6f-a695-d933e924125d

📥 Commits

Reviewing files that changed from the base of the PR and between ed9a644 and 40556c0.

📒 Files selected for processing (2)
  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (11)
frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/**/*.{ts,tsx,js,jsx}: Never import from package index files (e.g., @console/shared) in new code, as they can create circular dependencies and slow builds. Import from specific file paths instead.
Do not use backticks in t() calls for i18n strings, as the i18n parser cannot extract keys from template literals. Use single or double quotes instead.

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import from deprecated packages or use code with the @deprecated TSdoc tag in new code.

**/*.{ts,tsx}: Use React functional components with hooks instead of class components
State Management: Use React hooks and Context API (migrating away from legacy Redux/Immutable.js)
Hooks: Use existing hooks from console-shared when possible (useK8sWatchResource, useUserSettings, etc.)
API calls: Use k8s resource hooks for data fetching, consoleFetchJSON for HTTP requests
Extensions: Use console extension points for plugin integration
Types: Check existing types in console-shared before creating new ones
Dynamic Plugins: Use console extension points for plugin integration
Styling: Use SCSS modules co-located with components, PatternFly design system components, avoid any SCSS/CSS if possible
Accessibility: Follow WCAG 2.1 AA standards, use semantic HTML, ARIA labels where needed, ensure keyboard navigation, test with screen readers
i18n: Use useTranslation('namespace') hook with key format for translation keys
Error Handling: Use ErrorBoundary components and graceful degradation patterns
Optimize re-renders: Use useCallback for memoized callbacks to avoid function recreation every render
Optimize re-renders: Use useMemo for expensive computations to avoid recalculating on every render
Lazy loading: Use React.lazy() to lazy load heavy components
TypeScript type safety: Avoid using any type; suggest proper type definitions and verify null/undefined are handled properly
Type component props properly: Reuse existing component prop types instead of duplicating type definitions
Use proper hooks: Use specialized hooks like usePluginInfo for plugin data instead of generic data fetching patterns
Avoid deprecated components: Check for JSDoc @deprecated tags, import paths containing /deprecated, and DEPRECATED_ file name prefix before using components
Importing from barrel files and circular dependencies: Import directly from specific files instead...

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
frontend/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Never use absolute URLs or paths in the console code. The console runs behind a proxy under an arbitrary path.

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

When writing code for static plugins, ensure that all $codeRef reference the corresponding extension type from the @console/dynamic-plugin-sdk package.

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (TESTING.md)

**/*.{tsx,ts}: Always use page.getByTestId('x') for Playwright selectors which queries [data-test="x"]. If a React element only has a legacy test attribute, add data-test to the element. Never remove legacy attributes
Prefer data-test attributes in Cypress selectors (e.g., cy.get('[data-test="create-deployment"]')) over brittle CSS/ARIA selectors

File Naming: PascalCase for components, kebab-case for utilities, *.spec.ts(x) for tests

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.{go,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Use lowercase dash-separated names for all files to avoid git issues with case-insensitive file systems

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

**/*.{ts,tsx,js,jsx}: New code MUST be written in TypeScript, not JavaScript
Prefer functional programming patterns and immutable data structures
Run the linter and follow all rules defined in .eslintrc
Never use absolute paths in code - the app should be able to run behind a proxy under an arbitrary path

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Tests should follow a similar 'test tables' convention as used in Go where applicable

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
**/*.ts

📄 CodeRabbit inference engine (STYLEGUIDE.md)

Plugin SDK Changes: Any updates to console-dynamic-plugin-sdk should aim to maintain backward compatibility as it's a public API - use the plugin-api-review skill to vet changes for public API impact and ensure proper documentation updates

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
frontend/**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (README.md)

frontend/**/*.{js,ts,tsx}: Support only the latest versions of Edge, Chrome, Safari, and Firefox browsers; IE 11 and earlier are not supported
CSP violations should be automatically reported to telemetry by parsing dynamic plugin names from securitypolicyviolation events, with throttling to prevent duplicate reports within a day

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (INTERNATIONALIZATION.md)

For dynamic translation keys that cannot be parsed by i18next-parser (t(key), t('key' + id), t(key${id})), specify possible static values in comments for the parser to extract

Files:

  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
🔇 Additional comments (3)
frontend/packages/console-app/src/components/tour/tour-context.ts (3)

2-2: LGTM!

Also applies to: 154-154


158-160: LGTM!


165-165: LGTM!

Comment on lines +143 to +156
it('should not flash startTour when loaded transitions to true with completed tour', () => {
useSelectorMock.mockReturnValue({ A: true, B: false });
useResolvedExtensionsMock.mockReturnValue(mockTourExtension);
// Start with loaded: false (async ConfigMap fetch in progress)
useUserPreferenceMock.mockReturnValue([{ dev: { completed: false } }, () => null, false]);
const { result, rerender } = renderHook(() => useTourValuesForContext());
expect(result.current.tour).toEqual(null);

// Simulate ConfigMap load completing with completed: true
useUserPreferenceMock.mockReturnValue([{ dev: { completed: true } }, () => null, true]);
rerender();
const { tourState } = result.current;
expect(tourState?.startTour).not.toBe(true);
});
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Strengthen the test assertion to verify the tour context is returned.

The test currently only checks tourState?.startTour is not true, which would pass even if tour remains null (since undefined !== true). To properly verify the initialization guard allows the context through after loading, assert that:

  1. tour is not null (context was returned)
  2. startTour is explicitly false
🧪 Strengthened test assertions
     // Simulate ConfigMap load completing with completed: true
     useUserPreferenceMock.mockReturnValue([{ dev: { completed: true } }, () => null, true]);
     rerender();
-    const { tourState } = result.current;
-    expect(tourState?.startTour).not.toBe(true);
+    const { tourState, tour } = result.current;
+    expect(tour).not.toBeNull();
+    expect(tourState?.startTour).toBe(false);
📝 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
it('should not flash startTour when loaded transitions to true with completed tour', () => {
useSelectorMock.mockReturnValue({ A: true, B: false });
useResolvedExtensionsMock.mockReturnValue(mockTourExtension);
// Start with loaded: false (async ConfigMap fetch in progress)
useUserPreferenceMock.mockReturnValue([{ dev: { completed: false } }, () => null, false]);
const { result, rerender } = renderHook(() => useTourValuesForContext());
expect(result.current.tour).toEqual(null);
// Simulate ConfigMap load completing with completed: true
useUserPreferenceMock.mockReturnValue([{ dev: { completed: true } }, () => null, true]);
rerender();
const { tourState } = result.current;
expect(tourState?.startTour).not.toBe(true);
});
it('should not flash startTour when loaded transitions to true with completed tour', () => {
useSelectorMock.mockReturnValue({ A: true, B: false });
useResolvedExtensionsMock.mockReturnValue(mockTourExtension);
// Start with loaded: false (async ConfigMap fetch in progress)
useUserPreferenceMock.mockReturnValue([{ dev: { completed: false } }, () => null, false]);
const { result, rerender } = renderHook(() => useTourValuesForContext());
expect(result.current.tour).toEqual(null);
// Simulate ConfigMap load completing with completed: true
useUserPreferenceMock.mockReturnValue([{ dev: { completed: true } }, () => null, true]);
rerender();
const { tourState, tour } = result.current;
expect(tour).not.toBeNull();
expect(tourState?.startTour).toBe(false);
});
🤖 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/tour/__tests__/tour-context.spec.ts`
around lines 143 - 156, Update the test for useTourValuesForContext so it
asserts the tour context is actually returned and that startTour is explicitly
false: after rerender, add an assertion that result.current.tour is not null
(ensuring the context was initialized) and change the startTour assertion to
expect(false) (i.e., expect(result.current.tourState?.startTour).toBe(false))
instead of only checking it is not true; reference useTourValuesForContext,
tour, tourState, and startTour when locating the test to modify.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 19, 2026

@Cragsmann: 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/backend 40556c0 link true /test backend

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.

3 participants