Skip to content

OCPBUGS-44612: Remove useUserPreferenceCompatibility and withUserPreferenceCompatibility#16076

Open
vojtechszocs wants to merge 2 commits intoopenshift:mainfrom
vojtechszocs:remove-UserPreferenceCompatibility
Open

OCPBUGS-44612: Remove useUserPreferenceCompatibility and withUserPreferenceCompatibility#16076
vojtechszocs wants to merge 2 commits intoopenshift:mainfrom
vojtechszocs:remove-UserPreferenceCompatibility

Conversation

@vojtechszocs
Copy link
Contributor

@vojtechszocs vojtechszocs commented Feb 26, 2026

Depends on #16057

  • remove useUserPreferenceCompatibility and withUserPreferenceCompatibility
  • replace calls to useUserPreferenceCompatibility with useUserPreference
  • replace calls to withUserPreferenceCompatibility with withUserPreference
  • enhance withUserPreference - added sync parameter
  • update tests
Claude Code - Analysis on code removal

The hook at packages/console-shared/src/hooks/useUserPreferenceCompatibility.ts:7-32 is a thin wrapper around useUserPreference that does exactly two extra things:

  1. Migration of legacy localStorage data (lines 15-17): On mount, it checks if data exists under the old storageKey in localStorage.
    If it does, it deserializes that value and passes it as the defaultValue to useUserPreference (instead of the caller's defaultValue).
    This means old localStorage data gets picked up and written into the ConfigMap on first use.
  2. Cleanup of legacy localStorage key (lines 21-29): On unmount (via a cleanup function in useEffect), once the data has loaded
    successfully from the ConfigMap, it removes the old storageKey from localStorage.

In essence, it's a one-time migration bridge: it moves data from a legacy per-key localStorage entry into the ConfigMap-backed useUserPreference system, then deletes the old localStorage entry.

Yes, useUserPreferenceCompatibility can be safely removed in favor of useUserPreference. The compatibility hook's only purpose — migrating data from legacy per-feature localStorage keys to ConfigMap storage — is a one-time operation that has already occurred for active users. After migration, the compatibility hook is functionally identical to useUserPreference. The withUserPreferenceCompatibility HOC (which wraps the hook) can similarly be replaced with a withUserPreference HOC or refactored to use the hook directly.

The one edge case to consider: a user who hasn't logged in since the migration was introduced would lose their old localStorage preferences. Given the time that has likely elapsed, this is an acceptable trade-off, and the data loss would be limited to UI preferences (column ordering, pinned resources, etc.) that are easily re-set.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Unified user preference storage mechanism across console components.
    • Consolidated user settings persistence to a single storage system.
    • Updated internal preference management for consistency across the application.

vojtechszocs and others added 2 commits February 26, 2026 20:03
The hook handles a single user preference value, so rename it from
useUserSettings to useUserPreference for clarity and consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lity

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/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Feb 26, 2026
@openshift-ci-robot
Copy link
Contributor

@vojtechszocs: This pull request references Jira Issue OCPBUGS-44612, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

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

Details

In response to this:

  • remove useUserPreferenceCompatibility and withUserPreferenceCompatibility
  • replace calls to useUserPreferenceCompatibility with useUserPreference
  • replace calls to withUserPreferenceCompatibility with withUserPreference
  • enhance withUserPreference - added sync parameter
  • update tests
Claude Code - Analysis on code removal

The hook at packages/console-shared/src/hooks/useUserPreferenceCompatibility.ts:7-32 is a thin wrapper around useUserPreference that does exactly two extra things:

  1. Migration of legacy localStorage data (lines 15-17): On mount, it checks if data exists under the old storageKey in localStorage.
    If it does, it deserializes that value and passes it as the defaultValue to useUserPreference (instead of the caller's defaultValue).
    This means old localStorage data gets picked up and written into the ConfigMap on first use.
  2. Cleanup of legacy localStorage key (lines 21-29): On unmount (via a cleanup function in useEffect), once the data has loaded
    successfully from the ConfigMap, it removes the old storageKey from localStorage.

In essence, it's a one-time migration bridge: it moves data from a legacy per-key localStorage entry into the ConfigMap-backed useUserPreference system, then deletes the old localStorage entry.

Yes, useUserPreferenceCompatibility can be safely removed in favor of useUserPreference. The compatibility hook's only purpose — migrating data from legacy per-feature localStorage keys to ConfigMap storage — is a one-time operation that has already occurred for active users. After migration, the compatibility hook is functionally identical to useUserPreference. The withUserPreferenceCompatibility HOC (which wraps the hook) can similarly be replaced with a withUserPreference HOC or refactored to use the hook directly.

The one edge case to consider: a user who hasn't logged in since the migration was introduced would lose their old localStorage preferences. Given the time that has likely elapsed, this is an acceptable trade-off, and the data loss would be limited to UI preferences (column ordering, pinned resources, etc.) that are easily re-set.

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 Leo6Leo and rawagner February 26, 2026 21:37
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 26, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: vojtechszocs

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 component/core Related to console core functionality component/dashboard Related to dashboard approved Indicates a PR has been approved by an approver from all required OWNERS files. component/dev-console Related to dev-console component/helm Related to helm-plugin component/knative Related to knative-plugin component/olm Related to OLM component/sdk Related to console-plugin-sdk component/shared Related to console-shared component/topology Related to topology plugin-api-changed Categorizes a PR as containing plugin API changes labels Feb 26, 2026
import { useUserPreferenceCompatibility } from '@console/shared/src/hooks/useUserPreferenceCompatibility';
import { FAVORITES_CONFIG_MAP_KEY, FAVORITES_LOCAL_STORAGE_KEY } from '../../consts';
import { useUserPreference } from '@console/shared/src/hooks/useUserPreference';
import { FAVORITES_CONFIG_MAP_KEY } from '../../consts';
Copy link
Member

Choose a reason for hiding this comment

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

should we be consistently calling these user_settings_key or even user_preference_key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR is meant to remove/replace existing legacy code but I'm OK with adding more improvements.

  • remove unused *_LOCAL_STORAGE_KEY constants (former useUserPreferenceCompatibility hook storageKey parameter)
  • apply consistent naming to user preference key constants (currently a mix of *_USER_SETTINGS_KEY, *_USERSETTINGS_KEY, *_CONFIG_MAP_KEY, *_CONFIGMAP_KEY etc.)

Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

@logonoff logonoff left a comment

Choose a reason for hiding this comment

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

nice

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the user preferences system across the codebase. The deprecated useUserSettings and useUserSettingsCompatibility hooks are being replaced with a new useUserPreference hook throughout the frontend packages. Related higher-order components (withUserSettings, withUserSettingsCompatibility) are removed and replaced with withUserPreference. Associated type definitions are renamed accordingly. Local storage key arguments are removed from hook invocations in favor of a simplified API. The changes affect the shared hooks module, multiple console packages, and their respective test suites. Public exports and documentation are updated to reflect the new hook names and signatures.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective: removing deprecated useUserSettingsCompatibility and withUserSettingsCompatibility hooks and replacing them with useUserPreference throughout the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx (1)

19-33: ⚠️ Potential issue | 🟡 Minor

Duplicate mock declarations — first mock is dead code.

Both mocks target @console/shared/src/hooks/useUserPreference. Jest uses the last mock declaration, so lines 19-21 are never applied. The "For internal used Dropdowns" comment suggests these were originally two different hooks being mocked (likely useUserSettingsCompatibility and useUserSettings), but after the migration they now reference the same module.

Additionally, the static mock at line 20 returns only 2 elements ['', () => {}], but useUserPreference returns a 3-element tuple [value, setter, loaded].

Consider removing the redundant mock block:

🧹 Proposed fix to remove dead code
-// For internal used Dropdowns
-jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
-  useUserPreference: () => ['', () => {}],
-}));
-
 jest.mock('@console/shared/src/hooks/useResizeObserver', () => ({
   useResizeObserver: () => {},
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx`
around lines 19 - 33, Remove the dead/duplicate mock for useUserPreference (the
static jest.mock that returns ['', () => {}]) and keep a single mock declaration
for the module (the jest.fn() one); also ensure the retained mock for
useUserPreference returns the correct 3-tuple shape [value, setter, loaded]
whenever referenced in tests (e.g., adjust the mock implementation used by tests
that reference useUserPreference to return [<string>, <jest.fn()>, <boolean>]).
Locate the mocks by the module name useUserPreference and the test file's mocked
symbols to update or delete the redundant block and fix the tuple shape.
frontend/packages/console-shared/src/hooks/useUserPreference.ts (1)

107-115: ⚠️ Potential issue | 🟠 Major

Potential legacy preference loss after compatibility removal

Line 107 now only reads the consolidated console-user-settings* payload path. Users who still only have legacy per-key localStorage entries (never logged in during migration window) will not get migrated and can silently lose saved preferences.

Please add a one-time legacy fallback read (or explicit migration step) before returning defaults.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/console-shared/src/hooks/useUserPreference.ts` around lines
107 - 115, Before returning defaults in useUserPreference, add a one-time legacy
fallback read/migration that checks window.localStorage for the old per-key
entry (using keyRef.current) and, if found and the consolidated payload (the
value returned by useUserPreferenceLocalStorage / lsData) is empty, migrate that
value into the consolidated store via setLsDataCallback and remove the legacy
key; ensure the migration respects impersonate/alwaysUseFallbackLocalStorage and
uses defaultValueRef.current only if neither legacy nor consolidated data exist.
frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx (1)

24-30: ⚠️ Potential issue | 🟡 Minor

Falsy coalescing here can override a legitimate false value.

On Line 29, || true will coerce a stored false to true. Use nullish coalescing so only null/undefined fall back to default visibility.

Proposed fix
   const [showAlert, setShowAlert] = useUserPreferenceLocalStorage(
     `${element.getId()}`,
     `${USERSETTINGS_PREFIX}/${SIDEBAR_ALERTS}/${id}`,
     deserializeData(
       cfData?.data?.[`${USERSETTINGS_PREFIX}.${SIDEBAR_ALERTS}.${id}.${element.getId()}`],
-    ) || true,
+    ) ?? true,
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx`
around lines 24 - 30, The fallback for the initial showAlert value currently
uses `|| true`, which will turn a stored false into true; update the
initialization in the useUserPreferenceLocalStorage call (where
showAlert/setShowAlert are created) to use nullish coalescing instead (replace
`|| true` with `?? true`) so only null or undefined fall back to the default
after calling `deserializeData` on `cfData?.data?...`.
frontend/packages/console-app/src/components/user-preferences/UserPreferenceCheckboxField.tsx (1)

34-36: ⚠️ Potential issue | 🟡 Minor

Potential bug: falsy preference values may be incorrectly overwritten.

The condition !currentUserPreferenceValue will be true for any falsy value including false, 0, or ''. If a user intentionally sets their checkbox preference to false, this logic will overwrite it with the defaultValue.

Consider checking for undefined explicitly, or passing defaultValue directly to useUserPreference:

🐛 Proposed fix

Option 1: Pass defaultValue to the hook (preferred)

   const [
     currentUserPreferenceValue,
     setCurrentUserPreferenceValue,
     currentUserPreferenceValueLoaded,
-  ] = useUserPreference<UserPreferenceCheckboxFieldValue>(userSettingsKey);
+  ] = useUserPreference<UserPreferenceCheckboxFieldValue>(userSettingsKey, defaultValue);
   const fireTelemetryEvent = useTelemetry();

   const loaded: boolean = currentUserPreferenceValueLoaded;

-  if (defaultValue && loaded && !currentUserPreferenceValue) {
-    setCurrentUserPreferenceValue(defaultValue);
-  }

Option 2: Check for undefined explicitly

-  if (defaultValue && loaded && !currentUserPreferenceValue) {
+  if (defaultValue !== undefined && loaded && currentUserPreferenceValue === undefined) {
     setCurrentUserPreferenceValue(defaultValue);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/user-preferences/UserPreferenceCheckboxField.tsx`
around lines 34 - 36, The current check in UserPreferenceCheckboxField.tsx uses
"!currentUserPreferenceValue" which treats false/0/'' as unset and will
overwrite explicit falsy preferences; change this to check for undefined
explicitly (e.g., currentUserPreferenceValue === undefined) before calling
setCurrentUserPreferenceValue, or better, pass defaultValue into the
useUserPreference hook so the hook initializes the preference correctly and you
can remove this overwrite logic; update references to defaultValue, loaded,
currentUserPreferenceValue, and setCurrentUserPreferenceValue accordingly.
frontend/packages/topology/src/components/page/TopologyPage.tsx (1)

28-32: ⚠️ Potential issue | 🔴 Critical

Remove activeViewStorageKey prop and update test calls—it's no longer used after the useUserPreference migration.

The activeViewStorageKey prop is declared in the interface and destructured but never used in the component implementation. However, the test suite still passes this prop in three test cases (TopologyPage.spec.tsx lines 116, 123, 131), so the cleanup requires updating both the component and tests.

♻️ Required changes

Component changes (TopologyPage.tsx):

 interface TopologyPageProps {
-  activeViewStorageKey?: string;
   hideProjects?: boolean;
   defaultViewType?: TopologyViewType;
 }
 export const TopologyPage: FC<TopologyPageProps> = ({
-  activeViewStorageKey = LAST_TOPOLOGY_VIEW_LOCAL_STORAGE_KEY,
   hideProjects = false,
   defaultViewType = TopologyViewType.graph,
 }) => {

Test updates (TopologyPage.spec.tsx):
Remove activeViewStorageKey="fake-key" prop from the three test renders at lines 116, 123, and 131.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/topology/src/components/page/TopologyPage.tsx` around lines
28 - 32, The TopologyPage component still declares and destructures the
now-unused prop activeViewStorageKey in the TopologyPageProps interface and
component signature; remove activeViewStorageKey from the TopologyPageProps
interface and from the destructured props in the TopologyPage component (search
for TopologyPageProps and TopologyPage function/component). Then update the
tests in TopologyPage.spec.tsx by removing the activeViewStorageKey="fake-key"
prop from the three renders that pass it so the tests match the new component
signature. Ensure no other references to activeViewStorageKey remain.
🧹 Nitpick comments (8)
frontend/public/components/global-telemetry-notifications.tsx (1)

14-17: Hook migration looks correct; consider using the loaded state.

The hook invocation is correct: key, default value (null), and sync: true all match the expected useUserPreference signature.

One minor observation: the hook returns a third value (loaded) that isn't destructured here. Currently, showTelemetryNotification relies on !currentUserPreferenceTelemetryValue, which would be truthy while the preference is still loading (since it starts as null/undefined). This could cause a brief flash of the banner for users who have already responded, until the ConfigMap value loads.

If this is intentional or acceptable UX, no action needed. Otherwise, you could gate on loaded to prevent the flash:

♻️ Optional: Gate on loaded state to prevent flash
   const [
     currentUserPreferenceTelemetryValue,
     setCurrentUserPreferenceTelemetryValue,
-  ] = useUserPreference<USER_TELEMETRY_ANALYTICS>(PREFERRED_TELEMETRY_USER_SETTING_KEY, null, true);
+    loaded,
+  ] = useUserPreference<USER_TELEMETRY_ANALYTICS>(PREFERRED_TELEMETRY_USER_SETTING_KEY, null, true);

Then update the condition:

   const showTelemetryNotification =
+    loaded &&
     !currentUserPreferenceTelemetryValue &&
     (window.SERVER_FLAGS.telemetry?.STATE === CLUSTER_TELEMETRY_ANALYTICS.OPTIN ||
       window.SERVER_FLAGS.telemetry?.STATE === CLUSTER_TELEMETRY_ANALYTICS.OPTOUT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/global-telemetry-notifications.tsx` around lines
14 - 17, The hook useUserPreference returns a third "loaded" flag that's not
being used, causing showTelemetryNotification to rely on
currentUserPreferenceTelemetryValue which may be null while the preference is
still loading and cause a flash; update the destructuring to include the loaded
value (e.g., const [currentUserPreferenceTelemetryValue,
setCurrentUserPreferenceTelemetryValue, loaded] = useUserPreference(...)) and
then change the condition that computes showTelemetryNotification to require
loaded (e.g., showTelemetryNotification only when loaded is true and
currentUserPreferenceTelemetryValue indicates no preference) so the banner is
not shown until the preference has finished loading.
frontend/public/components/__tests__/markdown-view.spec.tsx (1)

18-22: Mock from the public API surface, not internal paths, to prevent brittle Jest wiring.

Your test imports useUserPreference from @console/shared (line 4), but mocks the deep internal path @console/shared/src/hooks/useUserPreference (lines 18–20). Since useUserPreference is already publicly re-exported from @console/shared, use a partial mock instead so the test remains stable if internal re-exports are refactored.

Suggested refactor
-jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
-  useUserPreference: jest.fn(),
-}));
+jest.mock('@console/shared', () => ({
+  ...jest.requireActual('@console/shared'),
+  useUserPreference: jest.fn(),
+}));

-const mockUserPreference = useUserPreference as jest.Mock;
+const mockUserPreference = useUserPreference as jest.MockedFunction<typeof useUserPreference>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/__tests__/markdown-view.spec.tsx` around lines 18
- 22, Summary: The test currently mocks the internal module path
'@console/shared/src/hooks/useUserPreference' instead of the public API, which
makes the Jest wiring brittle; change it to partially mock the public
'@console/shared' export and keep other exports intact. Replace the existing
jest.mock call that targets '@console/shared/src/hooks/useUserPreference' with a
partial mock of '@console/shared' that spreads
jest.requireActual('@console/shared') and overrides useUserPreference with
jest.fn(), and keep the test's mock reference (mockUserPreference =
useUserPreference as jest.Mock) so it points to the public re-export; update any
import lines that reference useUserPreference to import from '@console/shared'
if not already.
frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx (1)

2-16: Consider importing useUserPreference from the same module path you mock.

useUserPreference is imported from the public barrel export @console/shared, but mocked directly from @console/shared/src/hooks/useUserPreference. While this works correctly—Jest intercepts the source module before re-exports—aligning both paths improves consistency. Either import from @console/shared/src/hooks/useUserPreference or mock from the public path for stylistic clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx`
around lines 2 - 16, The import path for useUserPreference in
TopologyHelmReleaseNotesPanel.spec.tsx is inconsistent with the module path used
in the jest.mock; update either the import or the mock so both reference the
same module path (e.g., import useUserPreference from
'@console/shared/src/hooks/useUserPreference' to match the existing jest.mock,
or change the jest.mock to '@console/shared' to match the current import);
adjust the mock alias (mockUserPreference = useUserPreference as jest.Mock)
accordingly and ensure the mocked symbol name and jest.mock target refer to the
identical module path.
frontend/public/components/dashboard/project-dashboard/getting-started/__tests__/GettingStartedSection.spec.tsx (1)

4-4: Mock the specific hook instead of the entire @console/shared/src directory for consistency.

Line 4 imports useUserPreference from @console/shared, but lines 27-30 mock the entire @console/shared/src directory. The sibling test file getting-started-section.spec.tsx uses a more specific and maintainable pattern by mocking the hook directly. Align this file to match:

Proposed fix
-jest.mock('@console/shared/src', () => ({
-  ...jest.requireActual('@console/shared/src'),
+jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
   useUserPreference: jest.fn(() => [true, jest.fn()]),
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/public/components/dashboard/project-dashboard/getting-started/__tests__/GettingStartedSection.spec.tsx`
at line 4, The test currently mocks the entire '@console/shared/src' module;
instead, update the test to mock only the useUserPreference hook so it's
consistent and maintainable: replace the broad mock with a specific jest.mock
for '@console/shared' that stubs useUserPreference (the hook imported as
useUserPreference) and return the desired value(s) in the test setup; ensure any
existing references to the previous mock are removed and tests import
useUserPreference from '@console/shared' as in the file's import.
frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts (1)

798-802: Maintain type-level backward compatibility for the renamed export.

A deprecated function alias (useUserSettings) exists in dynamic-core-api.ts, which is good for plugins using the hook directly. However, the type UseUserSettings<T> has been completely removed from console-types.ts. External plugins that import this type for their own type definitions will break without an explicit type alias.

Add the deprecated type alias to console-types.ts alongside the function alias for complete backward compatibility across both usage patterns:

Suggested addition
/** `@deprecated` Use UseUserPreference instead */
export type UseUserSettings<T> = UseUserPreference<T>;

This ensures plugins can migrate incrementally rather than all at once.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts`
around lines 798 - 802, The exported hook type UseUserPreference was renamed but
the deprecated type alias UseUserSettings is missing, breaking downstream type
imports; to fix, add a deprecated type alias that maps UseUserSettings<T> to
UseUserPreference<T> (e.g., add a line declaring "export type UseUserSettings<T>
= UseUserPreference<T>;" with a `@deprecated` JSDoc) so both the new
UseUserPreference type and the old UseUserSettings name coexist for backward
compatibility.
frontend/packages/console-app/src/components/quick-starts/utils/quick-start-context.tsx (1)

48-51: Missing type parameter and loading state handling.

Two observations:

  1. useAllQuickStartStates (line 51) lacks an explicit type parameter. Consider adding one for clarity and type safety.

  2. Neither hook destructures the loaded boolean (third element). The quick start states are used immediately in callbacks without confirming data has loaded. This was likely the same in the previous implementation, but worth noting for consistency with other migrations in this PR.

♻️ Optional: Add explicit type for clarity
+type AllQuickStartStates = Record<string, unknown>;
+
 const useActiveQuickStartId = () =>
   useUserPreference<string>(ACTIVE_QUICK_START_ID_KEY, getInitialState()?.activeQuickStartId ?? '');
 const useAllQuickStartStates = () =>
-  useUserPreference(ALL_QUICK_START_STATE_KEY, getInitialState()?.allQuickStartStates ?? {});
+  useUserPreference<AllQuickStartStates>(ALL_QUICK_START_STATE_KEY, getInitialState()?.allQuickStartStates ?? {});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/quick-starts/utils/quick-start-context.tsx`
around lines 48 - 51, useAllQuickStartStates is missing an explicit generic type
and both hooks (useActiveQuickStartId and useAllQuickStartStates) fail to
destructure the third returned "loaded" value from useUserPreference; update
useAllQuickStartStates to call useUserPreference<Record<string,
QuickStartState>>(...) (or the appropriate type used elsewhere) and change both
hooks to destructure [value, setValue, loaded] = useUserPreference<...>(...) and
return or check the loaded flag where these hooks are consumed so callbacks
using quick start state only run after loaded is true (or guard early with the
loaded boolean inside the hook consumers such as in any callback that reads the
state).
frontend/packages/console-dynamic-plugin-sdk/src/api/dynamic-core-api.ts (1)

936-939: Consider adding a target removal release to the deprecation note.

Including an expected removal milestone/version improves upgrade planning for plugin maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/console-dynamic-plugin-sdk/src/api/dynamic-core-api.ts`
around lines 936 - 939, Update the deprecation JSDoc for useUserSettings to
include a target removal release/version and migration hint: modify the comment
above the export of useUserSettings (symbol useUserSettings) to say it's
deprecated in favor of useUserPreference (symbol useUserPreference) and include
the expected removal milestone (e.g., "Removed in vX.Y.0") plus a short
migration note to use useUserPreference instead so plugin maintainers can plan
upgrades.
frontend/packages/console-dynamic-plugin-sdk/docs/api.md (1)

3141-3148: Minor: Example variable names could better reflect the new terminology.

The example uses userPreferenceState and setUserPreferenceState, which is good, but consider using more concise names like preference/setPreference to match typical React conventions and the hook's simplified API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/console-dynamic-plugin-sdk/docs/api.md` around lines 3141 -
3148, Rename the example variables to be more concise: replace
userPreferenceState and setUserPreferenceState with preference and setPreference
respectively where useUserPreference is called and where props are passed into
WrappedComponent; update the prop name passed (e.g.,
userPreferenceState={preference} setUserPreferenceState={setPreference}) and any
corresponding prop references in the example to match the new names so the
sample reflects the hook's simplified API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/packages/console-app/src/components/user-preferences/search/useExactSearch.ts`:
- Around line 6-9: The hook useExactSearch currently returns a boolean but calls
useUserPreference<boolean>(PREFERRED_EXACT_SEARCH_USER_SETTING_KEY) without a
default, which can yield undefined on first render; update the call in
useExactSearch to pass a safe default (false) to useUserPreference so
exactSearch is always a boolean, and keep returning [exactSearch,
exactSearchLoaded] from useExactSearch unchanged (useUserPreference must be
invoked with the default false for PREFERRED_EXACT_SEARCH_USER_SETTING_KEY).

In
`@frontend/packages/console-app/src/components/user-preferences/synced-editor/usePreferredCreateEditMethod.ts`:
- Around line 7-9: The hook can return undefined because useUserPreference is
called without a default; update the call to useUserPreference in
usePreferredCreateEditMethod (the line that sets preferredCreateEditMethod using
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY) to pass an explicit default
string (e.g., '') as the second argument so the hook always returns a string and
satisfies the declared [string, boolean] contract.

In
`@frontend/packages/console-shared/src/components/dropdown/__tests__/ResourceDropdown.spec.tsx`:
- Around line 6-9: The mock for useUserPreference should match the real hook
signature [value, setter, loaded]; update the jest.mock in
ResourceDropdown.spec.tsx so the mocked useUserPreference returns a 3-tuple
(e.g. ['', () => {}, true]) instead of two items, ensuring the loaded flag is
present; locate the mock definition for useUserPreference and adjust its return
to include the third boolean element.

In
`@frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx`:
- Around line 230-236: The current replacement of legacy migration with direct
useUserPreference (keys favoritesUserPreferenceKey and
systemNamespacesSettingsKey) can drop dormant-user settings; add a one-time
migration fallback: when useUserPreference returns undefined for favorites or
system namespace, read the legacy localStorage keys (the old
bookmarks/system-namespace keys), validate/parse them, then call the
corresponding setter (setFavorites or the system namespace setter used near
lines referencing systemNamespacesSettingsKey) to persist the migrated value and
optionally mark a migrated flag so this runs only once; alternatively,
explicitly document/emit a telemetry/upgrade flag if you intend to accept loss
of legacy data.

In `@frontend/packages/console-shared/src/hoc/withUserPreference.tsx`:
- Around line 4-7: Update the WithUserPreferenceProps generic to type the setter
as React's Dispatch<SetStateAction<T>> instead of (v: T) => void so consumers
can pass functional updaters; specifically change the setUserSettingState
declaration in WithUserPreferenceProps to use Dispatch<SetStateAction<T>>
(matching useUserPreference's return type and internal handling) so components
like ColumnManagementModal can call setTableColumns(prev => ...) without type
errors.

In
`@frontend/packages/helm-plugin/src/components/forms/__tests__/HelmInstallUpgradeForm.spec.tsx`:
- Around line 54-56: The mock for useUserPreference in the test returns only a
two-element tuple which leaves the third `loaded` value undefined and can change
render branches; update the jest.mock for
'@console/shared/src/hooks/useUserPreference' so useUserPreference returns the
full three-element tuple (e.g. [value, setter, loaded])—set the third element to
an explicit boolean (true or false) appropriate for the test scenario—to ensure
component rendering paths are deterministic.

In `@frontend/packages/topology/src/__tests__/TopologyPage.spec.tsx`:
- Around line 97-103: Mocks for useUserPreference and usePreferredTopologyView
in TopologyPage.spec.tsx are returning only two items and must include the third
`loaded` boolean; update the mocked returns (e.g., change (useUserPreference as
jest.Mock).mockReturnValue(['', () => {}]) to mockReturnValue(['', () => {},
true]) and change (usePreferredTopologyView as jest.Mock).mockReturnValue(['',
true]) to mockReturnValue(['', true, true]) (and apply the same 3-element tuple
fix for the other test block mentioned) so the component sees the full [value,
setter, loaded] tuple during tests.

In `@frontend/packages/topology/src/filters/FilterProvider.tsx`:
- Around line 40-43: The current useUserPreference call for
TOPOLOGY_DISPLAY_FILTERS_USER_SETTINGS_KEY loses any legacy localStorage-stored
filters; add a one-time fallback migration: keep the
useUserPreference([appliedFilters, setAppliedFilters, appliedFiltersLoaded])
call, then in a useEffect that runs when appliedFiltersLoaded becomes true and
appliedFilters equals getAppliedFilters(DEFAULT_TOPOLOGY_FILTERS), read the
legacy storage key (e.g., LEGACY_TOPOLOGY_FILTERS_KEY) from localStorage, parse
and validate it via getAppliedFilters, call setAppliedFilters(migratedValue) if
valid, and then remove the legacy key so the migration runs only once; reference
appliedFilters, setAppliedFilters, appliedFiltersLoaded, useUserPreference,
TOPOLOGY_DISPLAY_FILTERS_USER_SETTINGS_KEY, getAppliedFilters, and
DEFAULT_TOPOLOGY_FILTERS in the change.

In
`@frontend/public/components/dashboard/project-dashboard/getting-started/GettingStartedSection.tsx`:
- Around line 24-27: The call to useUserPreference can briefly return undefined
while loading, causing isGettingStartedSectionOpen (from useUserPreference) to
be undefined when consumers expect a boolean; update the initialization in
GettingStartedSection.tsx to guard against transient undefined by using a safe
fallback (e.g., coalesce isGettingStartedSectionOpen with true via `?? true`) or
gate rendering/behavior on the preference's loaded flag returned by
useUserPreference; update references to isGettingStartedSectionOpen and
setIsGettingStartedSectionOpen accordingly so consumers always see a boolean.

In `@frontend/public/components/utils/console-select.tsx`:
- Around line 147-157: The code calls useUserPreference with
bookmarkUserPreferenceKey which can be undefined; either stop calling
useUserPreference when bookmarkUserPreferenceKey is undefined (wrap the
useUserPreference call behind the existing enableBookmarks check so bookmarks
and setBookmarks are only created when enableBookmarks is true), or update the
useUserPreference implementation to early-return a no-op pair (empty state and a
setter that does nothing) whenever its key parameter is undefined (ensure
keyRef.current and any cfData access and updateConfigMap calls are guarded
against undefined). Reference: bookmarkUserPreferenceKey, enableBookmarks,
useUserPreference, keyRef.current, cfData, updateConfigMap.

---

Outside diff comments:
In
`@frontend/packages/console-app/src/components/user-preferences/UserPreferenceCheckboxField.tsx`:
- Around line 34-36: The current check in UserPreferenceCheckboxField.tsx uses
"!currentUserPreferenceValue" which treats false/0/'' as unset and will
overwrite explicit falsy preferences; change this to check for undefined
explicitly (e.g., currentUserPreferenceValue === undefined) before calling
setCurrentUserPreferenceValue, or better, pass defaultValue into the
useUserPreference hook so the hook initializes the preference correctly and you
can remove this overwrite logic; update references to defaultValue, loaded,
currentUserPreferenceValue, and setCurrentUserPreferenceValue accordingly.

In `@frontend/packages/console-shared/src/hooks/useUserPreference.ts`:
- Around line 107-115: Before returning defaults in useUserPreference, add a
one-time legacy fallback read/migration that checks window.localStorage for the
old per-key entry (using keyRef.current) and, if found and the consolidated
payload (the value returned by useUserPreferenceLocalStorage / lsData) is empty,
migrate that value into the consolidated store via setLsDataCallback and remove
the legacy key; ensure the migration respects
impersonate/alwaysUseFallbackLocalStorage and uses defaultValueRef.current only
if neither legacy nor consolidated data exist.

In
`@frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx`:
- Around line 19-33: Remove the dead/duplicate mock for useUserPreference (the
static jest.mock that returns ['', () => {}]) and keep a single mock declaration
for the module (the jest.fn() one); also ensure the retained mock for
useUserPreference returns the correct 3-tuple shape [value, setter, loaded]
whenever referenced in tests (e.g., adjust the mock implementation used by tests
that reference useUserPreference to return [<string>, <jest.fn()>, <boolean>]).
Locate the mocks by the module name useUserPreference and the test file's mocked
symbols to update or delete the redundant block and fix the tuple shape.

In `@frontend/packages/topology/src/components/page/TopologyPage.tsx`:
- Around line 28-32: The TopologyPage component still declares and destructures
the now-unused prop activeViewStorageKey in the TopologyPageProps interface and
component signature; remove activeViewStorageKey from the TopologyPageProps
interface and from the destructured props in the TopologyPage component (search
for TopologyPageProps and TopologyPage function/component). Then update the
tests in TopologyPage.spec.tsx by removing the activeViewStorageKey="fake-key"
prop from the three renders that pass it so the tests match the new component
signature. Ensure no other references to activeViewStorageKey remain.

In
`@frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx`:
- Around line 24-30: The fallback for the initial showAlert value currently uses
`|| true`, which will turn a stored false into true; update the initialization
in the useUserPreferenceLocalStorage call (where showAlert/setShowAlert are
created) to use nullish coalescing instead (replace `|| true` with `?? true`) so
only null or undefined fall back to the default after calling `deserializeData`
on `cfData?.data?...`.

---

Nitpick comments:
In
`@frontend/packages/console-app/src/components/quick-starts/utils/quick-start-context.tsx`:
- Around line 48-51: useAllQuickStartStates is missing an explicit generic type
and both hooks (useActiveQuickStartId and useAllQuickStartStates) fail to
destructure the third returned "loaded" value from useUserPreference; update
useAllQuickStartStates to call useUserPreference<Record<string,
QuickStartState>>(...) (or the appropriate type used elsewhere) and change both
hooks to destructure [value, setValue, loaded] = useUserPreference<...>(...) and
return or check the loaded flag where these hooks are consumed so callbacks
using quick start state only run after loaded is true (or guard early with the
loaded boolean inside the hook consumers such as in any callback that reads the
state).

In `@frontend/packages/console-dynamic-plugin-sdk/docs/api.md`:
- Around line 3141-3148: Rename the example variables to be more concise:
replace userPreferenceState and setUserPreferenceState with preference and
setPreference respectively where useUserPreference is called and where props are
passed into WrappedComponent; update the prop name passed (e.g.,
userPreferenceState={preference} setUserPreferenceState={setPreference}) and any
corresponding prop references in the example to match the new names so the
sample reflects the hook's simplified API.

In `@frontend/packages/console-dynamic-plugin-sdk/src/api/dynamic-core-api.ts`:
- Around line 936-939: Update the deprecation JSDoc for useUserSettings to
include a target removal release/version and migration hint: modify the comment
above the export of useUserSettings (symbol useUserSettings) to say it's
deprecated in favor of useUserPreference (symbol useUserPreference) and include
the expected removal milestone (e.g., "Removed in vX.Y.0") plus a short
migration note to use useUserPreference instead so plugin maintainers can plan
upgrades.

In
`@frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts`:
- Around line 798-802: The exported hook type UseUserPreference was renamed but
the deprecated type alias UseUserSettings is missing, breaking downstream type
imports; to fix, add a deprecated type alias that maps UseUserSettings<T> to
UseUserPreference<T> (e.g., add a line declaring "export type UseUserSettings<T>
= UseUserPreference<T>;" with a `@deprecated` JSDoc) so both the new
UseUserPreference type and the old UseUserSettings name coexist for backward
compatibility.

In
`@frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx`:
- Around line 2-16: The import path for useUserPreference in
TopologyHelmReleaseNotesPanel.spec.tsx is inconsistent with the module path used
in the jest.mock; update either the import or the mock so both reference the
same module path (e.g., import useUserPreference from
'@console/shared/src/hooks/useUserPreference' to match the existing jest.mock,
or change the jest.mock to '@console/shared' to match the current import);
adjust the mock alias (mockUserPreference = useUserPreference as jest.Mock)
accordingly and ensure the mocked symbol name and jest.mock target refer to the
identical module path.

In `@frontend/public/components/__tests__/markdown-view.spec.tsx`:
- Around line 18-22: Summary: The test currently mocks the internal module path
'@console/shared/src/hooks/useUserPreference' instead of the public API, which
makes the Jest wiring brittle; change it to partially mock the public
'@console/shared' export and keep other exports intact. Replace the existing
jest.mock call that targets '@console/shared/src/hooks/useUserPreference' with a
partial mock of '@console/shared' that spreads
jest.requireActual('@console/shared') and overrides useUserPreference with
jest.fn(), and keep the test's mock reference (mockUserPreference =
useUserPreference as jest.Mock) so it points to the public re-export; update any
import lines that reference useUserPreference to import from '@console/shared'
if not already.

In
`@frontend/public/components/dashboard/project-dashboard/getting-started/__tests__/GettingStartedSection.spec.tsx`:
- Line 4: The test currently mocks the entire '@console/shared/src' module;
instead, update the test to mock only the useUserPreference hook so it's
consistent and maintainable: replace the broad mock with a specific jest.mock
for '@console/shared' that stubs useUserPreference (the hook imported as
useUserPreference) and return the desired value(s) in the test setup; ensure any
existing references to the previous mock are removed and tests import
useUserPreference from '@console/shared' as in the file's import.

In `@frontend/public/components/global-telemetry-notifications.tsx`:
- Around line 14-17: The hook useUserPreference returns a third "loaded" flag
that's not being used, causing showTelemetryNotification to rely on
currentUserPreferenceTelemetryValue which may be null while the preference is
still loading and cause a flash; update the destructuring to include the loaded
value (e.g., const [currentUserPreferenceTelemetryValue,
setCurrentUserPreferenceTelemetryValue, loaded] = useUserPreference(...)) and
then change the condition that computes showTelemetryNotification to require
loaded (e.g., showTelemetryNotification only when loaded is true and
currentUserPreferenceTelemetryValue indicates no preference) so the banner is
not shown until the preference has finished loading.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between eb23957 and 8524c03.

📒 Files selected for processing (95)
  • frontend/packages/console-app/src/components/detect-namespace/useLastNamespace.ts
  • frontend/packages/console-app/src/components/detect-perspective/useLastPerspective.ts
  • frontend/packages/console-app/src/components/favorite/FavoriteButton.tsx
  • frontend/packages/console-app/src/components/favorite/FavoriteNavItems.tsx
  • frontend/packages/console-app/src/components/nodes/NodeLogs.tsx
  • frontend/packages/console-app/src/components/nodes/NodesPage.tsx
  • frontend/packages/console-app/src/components/quick-starts/utils/quick-start-context.tsx
  • frontend/packages/console-app/src/components/tour/__tests__/tour-context.spec.ts
  • frontend/packages/console-app/src/components/tour/tour-context.ts
  • frontend/packages/console-app/src/components/user-preferences/UserPreferenceCheckboxField.tsx
  • frontend/packages/console-app/src/components/user-preferences/UserPreferenceDropdownField.tsx
  • frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceCheckboxField.spec.tsx
  • frontend/packages/console-app/src/components/user-preferences/__tests__/UserPreferenceDropdownField.spec.tsx
  • frontend/packages/console-app/src/components/user-preferences/language/usePreferredLanguage.ts
  • frontend/packages/console-app/src/components/user-preferences/lightspeed/useHideLightspeed.ts
  • frontend/packages/console-app/src/components/user-preferences/namespace/usePreferredNamespace.ts
  • frontend/packages/console-app/src/components/user-preferences/perspective/usePreferredPerspective.ts
  • frontend/packages/console-app/src/components/user-preferences/search/useExactSearch.ts
  • frontend/packages/console-app/src/components/user-preferences/synced-editor/usePreferredCreateEditMethod.ts
  • frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-core.md
  • frontend/packages/console-dynamic-plugin-sdk/docs/api.md
  • frontend/packages/console-dynamic-plugin-sdk/src/api/dynamic-core-api.ts
  • frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts
  • frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts
  • frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx
  • frontend/packages/console-shared/src/components/dropdown/__tests__/ResourceDropdown.spec.tsx
  • frontend/packages/console-shared/src/components/getting-started/useGettingStartedShowState.tsx
  • frontend/packages/console-shared/src/components/health-checks/HealthChecksAlert.tsx
  • frontend/packages/console-shared/src/components/health-checks/__tests__/HealthChecksAlert.spec.tsx
  • frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx
  • frontend/packages/console-shared/src/components/synced-editor/__tests__/useEditorType.spec.ts
  • frontend/packages/console-shared/src/components/synced-editor/useEditorType.ts
  • frontend/packages/console-shared/src/hoc/index.ts
  • frontend/packages/console-shared/src/hoc/withUserPreference.tsx
  • frontend/packages/console-shared/src/hoc/withUserSettings.tsx
  • frontend/packages/console-shared/src/hoc/withUserSettingsCompatibility.tsx
  • frontend/packages/console-shared/src/hooks/__tests__/usePinnedResources.spec.ts
  • frontend/packages/console-shared/src/hooks/__tests__/useTelemetry.spec.ts
  • frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts
  • frontend/packages/console-shared/src/hooks/index.ts
  • frontend/packages/console-shared/src/hooks/useNotificationAlerts.ts
  • frontend/packages/console-shared/src/hooks/usePinnedResources.ts
  • frontend/packages/console-shared/src/hooks/useTelemetry.ts
  • frontend/packages/console-shared/src/hooks/useUserPreference.ts
  • frontend/packages/console-shared/src/hooks/useUserPreferenceLocalStorage.ts
  • frontend/packages/console-shared/src/hooks/useUserSettingsCompatibility.ts
  • frontend/packages/console-telemetry-plugin/src/components/TelemetryUserPreferenceDropdown.tsx
  • frontend/packages/dev-console/src/components/add/hooks/__tests__/useShowAddCardItemDetails.spec.ts
  • frontend/packages/dev-console/src/components/add/hooks/useShowAddCardItemDetails.ts
  • frontend/packages/dev-console/src/components/buildconfig/__tests__/BuildConfigFormPage.spec.tsx
  • frontend/packages/dev-console/src/components/builds/BuildsTabListPage.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/DeploymentForm.spec.tsx
  • frontend/packages/dev-console/src/components/deployments/__tests__/EditDeployment.spec.tsx
  • frontend/packages/dev-console/src/components/import/section/useResourceType.ts
  • frontend/packages/dev-console/src/components/user-preferences/usePreferredRoutingOptions.tsx
  • frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseNotes.spec.tsx
  • frontend/packages/helm-plugin/src/components/forms/__tests__/HelmInstallUpgradeForm.spec.tsx
  • frontend/packages/helm-plugin/src/topology/__tests__/TopologyHelmReleaseNotesPanel.spec.tsx
  • frontend/packages/knative-plugin/src/components/functions/GettingStartedSection.tsx
  • frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Switch.tsx
  • frontend/packages/operator-lifecycle-manager-v1/src/hooks/useOLMv1FlagProvider.ts
  • frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-hub-items.tsx
  • frontend/packages/topology/src/__tests__/TopologyPage.spec.tsx
  • frontend/packages/topology/src/__tests__/TopologySideBar.spec.tsx
  • frontend/packages/topology/src/components/dropdowns/NamespaceBarApplicationSelector.tsx
  • frontend/packages/topology/src/components/export-app/ExportApplicationModal.tsx
  • frontend/packages/topology/src/components/export-app/__tests__/ExportApplicationModal.spec.tsx
  • frontend/packages/topology/src/components/export-app/export-app-context.ts
  • frontend/packages/topology/src/components/graph-view/Topology.tsx
  • frontend/packages/topology/src/components/page/TopologyPage.tsx
  • frontend/packages/topology/src/components/side-bar/TopologySideBar.tsx
  • frontend/packages/topology/src/components/side-bar/components/SideBarAlerts.tsx
  • frontend/packages/topology/src/filters/FilterProvider.tsx
  • frontend/packages/topology/src/user-preferences/usePreferredTopologyView.ts
  • frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellTerminal.tsx
  • frontend/packages/webterminal-plugin/src/components/cloud-shell/__tests__/CloudShellTerminal.spec.tsx
  • frontend/public/components/ThemeProvider.tsx
  • frontend/public/components/__tests__/markdown-view.spec.tsx
  • frontend/public/components/app-contents.tsx
  • frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/__tests__/getting-started-section.spec.tsx
  • frontend/public/components/dashboard/dashboards-page/cluster-dashboard/getting-started/getting-started-section.tsx
  • frontend/public/components/dashboard/dashboards-page/cluster-dashboard/utilization-card.tsx
  • frontend/public/components/dashboard/project-dashboard/getting-started/GettingStartedSection.tsx
  • frontend/public/components/dashboard/project-dashboard/getting-started/__tests__/GettingStartedSection.spec.tsx
  • frontend/public/components/factory/Table/active-columns-hook.ts
  • frontend/public/components/global-telemetry-notifications.tsx
  • frontend/public/components/modals/column-management-modal.tsx
  • frontend/public/components/modals/delete-namespace-modal.tsx
  • frontend/public/components/modals/edit-yaml-settings-modal.tsx
  • frontend/public/components/namespace.jsx
  • frontend/public/components/pod-list.tsx
  • frontend/public/components/resource-dropdown.tsx
  • frontend/public/components/useFavoritesOptions.ts
  • frontend/public/components/utils/console-select.tsx
  • frontend/public/components/utils/resource-log.tsx
💤 Files with no reviewable changes (6)
  • frontend/packages/console-dynamic-plugin-sdk/src/api/internal-api.ts
  • frontend/packages/console-shared/src/hoc/withUserSettings.tsx
  • frontend/packages/console-shared/src/hoc/withUserSettingsCompatibility.tsx
  • frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx
  • frontend/packages/topology/src/components/dropdowns/NamespaceBarApplicationSelector.tsx
  • frontend/packages/console-shared/src/hooks/useUserSettingsCompatibility.ts

Comment on lines +6 to 9
const [exactSearch, , exactSearchLoaded] = useUserPreference<boolean>(
PREFERRED_EXACT_SEARCH_USER_SETTING_KEY,
);
return [exactSearch, exactSearchLoaded];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Return type contract can leak undefined on initial render.

On Line 6, useUserPreference<boolean> is called without a default, but useExactSearch advertises a strict boolean value. Consider defaulting to false to keep the contract truthful.

Proposed fix
 export const useExactSearch = (): [boolean, boolean] => {
   const [exactSearch, , exactSearchLoaded] = useUserPreference<boolean>(
     PREFERRED_EXACT_SEARCH_USER_SETTING_KEY,
+    false,
   );
   return [exactSearch, exactSearchLoaded];
 };
📝 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 [exactSearch, , exactSearchLoaded] = useUserPreference<boolean>(
PREFERRED_EXACT_SEARCH_USER_SETTING_KEY,
);
return [exactSearch, exactSearchLoaded];
const [exactSearch, , exactSearchLoaded] = useUserPreference<boolean>(
PREFERRED_EXACT_SEARCH_USER_SETTING_KEY,
false,
);
return [exactSearch, exactSearchLoaded];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/user-preferences/search/useExactSearch.ts`
around lines 6 - 9, The hook useExactSearch currently returns a boolean but
calls useUserPreference<boolean>(PREFERRED_EXACT_SEARCH_USER_SETTING_KEY)
without a default, which can yield undefined on first render; update the call in
useExactSearch to pass a safe default (false) to useUserPreference so
exactSearch is always a boolean, and keep returning [exactSearch,
exactSearchLoaded] from useExactSearch unchanged (useUserPreference must be
invoked with the default false for PREFERRED_EXACT_SEARCH_USER_SETTING_KEY).

Comment on lines +7 to 9
const [preferredCreateEditMethod, , preferredCreateEditMethodLoaded] = useUserPreference<string>(
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, examine the file in question
echo "=== File content ==="
cat -n frontend/packages/console-app/src/components/user-preferences/synced-editor/usePreferredCreateEditMethod.ts

echo -e "\n=== Finding useUserPreference definition ==="
# Find where useUserPreference is defined
rg -n "export.*useUserPreference|function useUserPreference|const useUserPreference" --type ts

echo -e "\n=== Check for the constant ==="
# Look for the constant mentioned in the proposed fix
rg -n "PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_VALUE_LATEST" --type ts

echo -e "\n=== Check call sites of usePreferredCreateEditMethod ==="
rg -n "usePreferredCreateEditMethod" --type ts -B2 -A5

Repository: openshift/console

Length of output: 18380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== useUserPreference hook definition ==="
cat -n frontend/packages/console-shared/src/hooks/useUserPreference.ts | head -80

echo -e "\n=== Check type definition ==="
rg -n "UseUserPreference|useUserPreference.*=" frontend/packages/console-shared/src/hooks/useUserPreference.ts -A5 | head -40

Repository: openshift/console

Length of output: 3996


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== useEditorType implementation ==="
cat -n frontend/packages/console-shared/src/components/synced-editor/useEditorType.ts

echo -e "\n=== Check if undefined is guarded in actual usage ==="
rg -n "preferredEditorType" frontend/packages/console-shared/src/components/synced-editor/useEditorType.ts -B2 -A2

Repository: openshift/console

Length of output: 3681


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Direct usages of preferredCreateEditMethod in non-test files ==="
rg -n "usePreferredCreateEditMethod" frontend/packages/console-shared/src/components/synced-editor/useEditorType.ts -A20 | grep -E "(preferredEditorType|preferredCreateEditMethod)" 

echo -e "\n=== Check for non-guarded access ==="
# Look for access patterns that don't guard
rg -n "preferredEditorType\." frontend/packages/console-shared/src/components/synced-editor/useEditorType.ts

echo -e "\n=== Summary: All call sites for usePreferredCreateEditMethod ==="
rg -l "usePreferredCreateEditMethod" --type ts | grep -v spec | grep -v test

Repository: openshift/console

Length of output: 593


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== All non-test files that use usePreferredCreateEditMethod ==="
rg -l "usePreferredCreateEditMethod" --type ts | grep -v spec | grep -v test

echo -e "\n=== Full context for each non-test usage ==="
for file in $(rg -l "usePreferredCreateEditMethod" --type ts | grep -v spec | grep -v test); do
  echo "File: $file"
  rg -n "usePreferredCreateEditMethod" "$file" -B5 -A10
  echo "---"
done

Repository: openshift/console

Length of output: 2492


Add a default value to honor the function's type contract.

The hook declares a return type of [string, boolean] (line 6) but can return [undefined, boolean] when no preference is persisted, since useUserPreference is called without a default value. While the only current call site in useEditorType.ts guards against undefined on line 27, this fragile pattern invites bugs in future consumers who may not add such guards.

Provide the default explicitly:

Proposed fix
 export const usePreferredCreateEditMethod = (): [string, boolean] => {
   const [preferredCreateEditMethod, , preferredCreateEditMethodLoaded] = useUserPreference<string>(
     PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY,
+    PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_VALUE_LATEST,
   );
   return [preferredCreateEditMethod, preferredCreateEditMethodLoaded];
 };
📝 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 [preferredCreateEditMethod, , preferredCreateEditMethodLoaded] = useUserPreference<string>(
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY,
);
const [preferredCreateEditMethod, , preferredCreateEditMethodLoaded] = useUserPreference<string>(
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY,
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_VALUE_LATEST,
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-app/src/components/user-preferences/synced-editor/usePreferredCreateEditMethod.ts`
around lines 7 - 9, The hook can return undefined because useUserPreference is
called without a default; update the call to useUserPreference in
usePreferredCreateEditMethod (the line that sets preferredCreateEditMethod using
PREFERRED_CREATE_EDIT_METHOD_USER_SETTING_KEY) to pass an explicit default
string (e.g., '') as the second argument so the hook always returns a string and
satisfies the declared [string, boolean] contract.

Comment on lines +6 to 9
jest.mock('@console/shared/src/hooks/useUserPreference', () => {
return {
useUserSettingsCompatibility: () => ['', () => {}],
useUserPreference: () => ['', () => {}],
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name 'useUserPreference.ts' -o -name 'useUserPreference.tsx' | head -20

Repository: openshift/console

Length of output: 126


🏁 Script executed:

find frontend/packages/console-shared/src/components/dropdown -type f -name '*.spec.tsx' | head -10

Repository: openshift/console

Length of output: 153


🏁 Script executed:

cat frontend/packages/console-shared/src/components/dropdown/__tests__/ResourceDropdown.spec.tsx | head -30

Repository: openshift/console

Length of output: 949


🏁 Script executed:

cat ./frontend/packages/console-shared/src/hooks/useUserPreference.ts

Repository: openshift/console

Length of output: 8038


🏁 Script executed:

rg "useUserPreference" frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx -C3

Repository: openshift/console

Length of output: 43


🏁 Script executed:

cat frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx | head -80

Repository: openshift/console

Length of output: 3062


🏁 Script executed:

rg "useUserPreference" frontend/packages/console-shared/src/components/dropdown/ -C3

Repository: openshift/console

Length of output: 1153


🏁 Script executed:

rg "useUserPreference" frontend/packages/console-shared/src/components/dropdown/ --type tsx --type ts

Repository: openshift/console

Length of output: 88


🏁 Script executed:

grep -r "useUserPreference" frontend/packages/console-shared/src/components/utils/console-select.tsx 2>/dev/null | head -20

Repository: openshift/console

Length of output: 43


🏁 Script executed:

find frontend/packages/console-shared -name 'ConsoleSelect.tsx' -o -name 'console-select.tsx'

Repository: openshift/console

Length of output: 43


🏁 Script executed:

rg "useUserPreference" frontend/packages/console-shared/src/components/ -t ts -t jsx -t tsx

Repository: openshift/console

Length of output: 88


🏁 Script executed:

find frontend -name 'ConsoleSelect*' | head -5

Repository: openshift/console

Length of output: 43


🏁 Script executed:

cat frontend/packages/internal/src/components/utils/console-select.tsx | head -100

Repository: openshift/console

Length of output: 159


🏁 Script executed:

rg "useUserPreference" frontend/packages/console-shared/src/components/

Repository: openshift/console

Length of output: 2605


🏁 Script executed:

find frontend/packages -name '*console-select*'

Repository: openshift/console

Length of output: 43


🏁 Script executed:

cat frontend/packages/internal/src/components/utils/console-select/ConsoleSelect.tsx | head -150

Repository: openshift/console

Length of output: 173


🏁 Script executed:

rg "useUserPreference" frontend/packages/internal/src/components/utils/console-select/ -C 2

Repository: openshift/console

Length of output: 164


🏁 Script executed:

cat frontend/packages/internal/src/components/utils/console-select/ConsoleSelect.tsx | grep -A 5 "useUserPreference"

Repository: openshift/console

Length of output: 173


🏁 Script executed:

grep -r "useUserPreference" frontend/packages/internal/src/components/utils/console-select/ 2>/dev/null

Repository: openshift/console

Length of output: 43


🏁 Script executed:

find frontend -name 'ConsoleSelect*' -type f

Repository: openshift/console

Length of output: 43


🏁 Script executed:

find frontend -name '*console-select*' -type f

Repository: openshift/console

Length of output: 112


🏁 Script executed:

rg "class ConsoleSelect|export.*ConsoleSelect" frontend/ --max-count=5

Repository: openshift/console

Length of output: 543


🏁 Script executed:

cat frontend/public/components/utils/console-select.tsx | grep -A 50 "export const ConsoleSelect"

Repository: openshift/console

Length of output: 1478


🏁 Script executed:

rg "useUserPreference" frontend/public/components/utils/console-select.tsx

Repository: openshift/console

Length of output: 196


🏁 Script executed:

cat frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx | grep -A 20 "class ResourceDropdownInternal"

Repository: openshift/console

Length of output: 767


Update mock tuple to match useUserPreference contract.

The hook returns [value, setter, loaded], but this mock returns only [value, setter]. The loaded flag is part of the real API signature. While the component may not destructure it in this particular usage, the mock should faithfully represent the actual hook contract to prevent test drift and potential loading-state bugs.

Proposed fix
 jest.mock('@console/shared/src/hooks/useUserPreference', () => {
   return {
-    useUserPreference: () => ['', () => {}],
+    useUserPreference: () => ['', () => {}, true],
   };
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-shared/src/components/dropdown/__tests__/ResourceDropdown.spec.tsx`
around lines 6 - 9, The mock for useUserPreference should match the real hook
signature [value, setter, loaded]; update the jest.mock in
ResourceDropdown.spec.tsx so the mocked useUserPreference returns a 3-tuple
(e.g. ['', () => {}, true]) instead of two items, ensuring the loaded flag is
present; locate the mock definition for useUserPreference and adjust its return
to include the third boolean element.

Comment on lines +230 to 236
const favoritesUserPreferenceKey = `${NAMESPACE_USERSETTINGS_PREFIX}.bookmarks`;
const systemNamespacesSettingsKey = `${NAMESPACE_USERSETTINGS_PREFIX}.systemNamespace`;
const favoriteStorageKey = `${NAMESPACE_LOCAL_STORAGE_KEY}-bookmarks`;
const systemNamespaceKey = `${NAMESPACE_LOCAL_STORAGE_KEY}-systemNamespace`;
const [favorites, setFavorites] = useUserSettingsCompatibility(
favoritesUserSettingsKey,
favoriteStorageKey,
const [favorites, setFavorites] = useUserPreference(
favoritesUserPreferenceKey,
undefined,
true,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dormant-user legacy preferences can be dropped with this direct hook switch.

Line 232 and Line 280 now rely on useUserPreference directly, so the old one-time localStorage migration path is no longer exercised in this component. Users returning after a long gap may lose legacy bookmarks/system-namespace settings.

Please either add a one-time fallback for these two legacy values or explicitly track/accept this upgrade behavior.

Also applies to: 280-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/console-shared/src/components/namespace/NamespaceDropdown.tsx`
around lines 230 - 236, The current replacement of legacy migration with direct
useUserPreference (keys favoritesUserPreferenceKey and
systemNamespacesSettingsKey) can drop dormant-user settings; add a one-time
migration fallback: when useUserPreference returns undefined for favorites or
system namespace, read the legacy localStorage keys (the old
bookmarks/system-namespace keys), validate/parse them, then call the
corresponding setter (setFavorites or the system namespace setter used near
lines referencing systemNamespacesSettingsKey) to persist the migrated value and
optionally mark a migrated flag so this runs only once; alternatively,
explicitly document/emit a telemetry/upgrade flag if you intend to accept loss
of legacy data.

Comment on lines +4 to +7
export type WithUserPreferenceProps<T> = {
userSettingState: T;
setUserSettingState: (v: T) => void;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Examine the withUserPreference HOC file around lines 4-7 and 17-20
cat -n frontend/packages/console-shared/src/hoc/withUserPreference.tsx | head -25

Repository: openshift/console

Length of output: 1113


🏁 Script executed:

#!/bin/bash
# Find and examine the useUserPreference hook
find frontend -name '*useUserPreference*' -type f | head -10

Repository: openshift/console

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Check if column-management-modal uses updater functions
rg -A 5 -B 5 'setTableColumns' frontend/public/components/modals/column-management-modal.tsx | head -40

Repository: openshift/console

Length of output: 978


🏁 Script executed:

#!/bin/bash
cat -n frontend/packages/console-shared/src/hooks/useUserPreference.ts

Repository: openshift/console

Length of output: 9662


Use React state-dispatch typing for setUserSettingState.

The HOC currently narrows the setter to (v: T) => void, but useUserPreference actually returns Dispatch<SetStateAction<T>> (line 194 of the hook). The hook explicitly handles functional updaters (line 197–198), yet consumers like ColumnManagementModal (which uses setTableColumns((prevState) => {...})) are forced to work around the narrowed type contract. Fix the HOC's public type to preserve the full React setter semantics.

🔧 Proposed fix
-import type { ComponentType, FC } from 'react';
+import type { ComponentType, Dispatch, FC, SetStateAction } from 'react';
 import { useUserPreference } from '../hooks';

 export type WithUserPreferenceProps<T> = {
   userSettingState: T;
-  setUserSettingState: (v: T) => void;
+  setUserSettingState: Dispatch<SetStateAction<T>>;
 };
@@
-  const Component = (props: Props) => {
+  const Component = (props: Omit<Props, keyof WithUserPreferenceProps<T>>) => {
     const [state, setState, loaded] = useUserPreference(key, defaultValue, sync);
     return loaded ? (
       <WrappedComponent {...props} userSettingState={state} setUserSettingState={setState} />
     ) : null;
   };

Also applies to: 17–20

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/console-shared/src/hoc/withUserPreference.tsx` around lines
4 - 7, Update the WithUserPreferenceProps generic to type the setter as React's
Dispatch<SetStateAction<T>> instead of (v: T) => void so consumers can pass
functional updaters; specifically change the setUserSettingState declaration in
WithUserPreferenceProps to use Dispatch<SetStateAction<T>> (matching
useUserPreference's return type and internal handling) so components like
ColumnManagementModal can call setTableColumns(prev => ...) without type errors.

Comment on lines +54 to 56
jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
useUserPreference: () => ['', () => {}],
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Mock the full useUserPreference tuple to avoid test-path drift.

The mock currently returns only [value, setter]. useUserPreference returns [value, setter, loaded], so loaded becomes undefined and can alter render branches in tests.

✅ Proposed fix
 jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
-  useUserPreference: () => ['', () => {}],
+  useUserPreference: () => ['', () => {}, true],
 }));
📝 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
jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
useUserPreference: () => ['', () => {}],
}));
jest.mock('@console/shared/src/hooks/useUserPreference', () => ({
useUserPreference: () => ['', () => {}, true],
}));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/packages/helm-plugin/src/components/forms/__tests__/HelmInstallUpgradeForm.spec.tsx`
around lines 54 - 56, The mock for useUserPreference in the test returns only a
two-element tuple which leaves the third `loaded` value undefined and can change
render branches; update the jest.mock for
'@console/shared/src/hooks/useUserPreference' so useUserPreference returns the
full three-element tuple (e.g. [value, setter, loaded])—set the third element to
an explicit boolean (true or false) appropriate for the test scenario—to ensure
component rendering paths are deterministic.

Comment on lines 97 to 103
it('should allow setting default to list view', () => {
(useUserSettingsCompatibility as jest.Mock).mockReturnValue(['', () => {}]);
(useUserPreference as jest.Mock).mockReturnValue(['', () => {}]);
(usePreferredTopologyView as jest.Mock).mockReturnValue(['', true]);
(useQueryParams as jest.Mock).mockReturnValue(new URLSearchParams('view=list'));
renderWithProviders(<TopologyPage defaultViewType={TopologyViewType.list} />);
expect(screen.getByTestId('topology-list-page')).toBeInTheDocument();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent mock return values missing loaded boolean.

Lines 98 and 152 return only two elements ['', () => {}] while other test cases correctly return the full tuple including the loaded boolean. This inconsistency could mask bugs in components that rely on the loaded state.

🐛 Proposed fix for consistent mock returns
   it('should allow setting default to list view', () => {
-    (useUserPreference as jest.Mock).mockReturnValue(['', () => {}]);
+    (useUserPreference as jest.Mock).mockReturnValue(['', () => {}, true]);
     (usePreferredTopologyView as jest.Mock).mockReturnValue(['', true]);
   it('should support URL view=list', () => {
-    (useUserPreference as jest.Mock).mockReturnValue(['', () => {}]);
+    (useUserPreference as jest.Mock).mockReturnValue(['', () => {}, true]);
     (usePreferredTopologyView as jest.Mock).mockReturnValue(['', true]);

Also applies to: 151-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/topology/src/__tests__/TopologyPage.spec.tsx` around lines
97 - 103, Mocks for useUserPreference and usePreferredTopologyView in
TopologyPage.spec.tsx are returning only two items and must include the third
`loaded` boolean; update the mocked returns (e.g., change (useUserPreference as
jest.Mock).mockReturnValue(['', () => {}]) to mockReturnValue(['', () => {},
true]) and change (usePreferredTopologyView as jest.Mock).mockReturnValue(['',
true]) to mockReturnValue(['', true, true]) (and apply the same 3-element tuple
fix for the other test block mentioned) so the component sees the full [value,
setter, loaded] tuple during tests.

Comment on lines +40 to 43
const [appliedFilters, setAppliedFilters, appliedFiltersLoaded] = useUserPreference(
TOPOLOGY_DISPLAY_FILTERS_USER_SETTINGS_KEY,
TOPOLOGY_DISPLAY_FILTERS_LOCAL_STORAGE_KEY,
getAppliedFilters(DEFAULT_TOPOLOGY_FILTERS),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential legacy preference loss after removing compatibility migration.

At Line 40, replacing the compatibility hook with useUserPreference drops the implicit one-time localStorage migration path. Users who never logged in during the migration window can silently lose saved topology filters after upgrade. Please add a one-time fallback migration in this flow (or an explicit, documented product decision with guardrails).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/packages/topology/src/filters/FilterProvider.tsx` around lines 40 -
43, The current useUserPreference call for
TOPOLOGY_DISPLAY_FILTERS_USER_SETTINGS_KEY loses any legacy localStorage-stored
filters; add a one-time fallback migration: keep the
useUserPreference([appliedFilters, setAppliedFilters, appliedFiltersLoaded])
call, then in a useEffect that runs when appliedFiltersLoaded becomes true and
appliedFilters equals getAppliedFilters(DEFAULT_TOPOLOGY_FILTERS), read the
legacy storage key (e.g., LEGACY_TOPOLOGY_FILTERS_KEY) from localStorage, parse
and validate it via getAppliedFilters, call setAppliedFilters(migratedValue) if
valid, and then remove the legacy key so the migration runs only once; reference
appliedFilters, setAppliedFilters, appliedFiltersLoaded, useUserPreference,
TOPOLOGY_DISPLAY_FILTERS_USER_SETTINGS_KEY, getAppliedFilters, and
DEFAULT_TOPOLOGY_FILTERS in the change.

Comment on lines +24 to 27
const [isGettingStartedSectionOpen, setIsGettingStartedSectionOpen] = useUserPreference<boolean>(
`${userSettingKey}.expanded`,
true,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against transient undefined from useUserPreference.

useUserPreference may yield undefined before initial load, but isOpen expects a boolean. Consider a safe fallback (?? true) or gate on the loaded flag to avoid first-render state glitches.

Suggested fix
-  const [isGettingStartedSectionOpen, setIsGettingStartedSectionOpen] = useUserPreference<boolean>(
+  const [isGettingStartedSectionOpen, setIsGettingStartedSectionOpen] = useUserPreference<boolean>(
     `${userSettingKey}.expanded`,
     true,
   );
@@
-        isOpen={isGettingStartedSectionOpen}
+        isOpen={isGettingStartedSectionOpen ?? true}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/public/components/dashboard/project-dashboard/getting-started/GettingStartedSection.tsx`
around lines 24 - 27, The call to useUserPreference can briefly return undefined
while loading, causing isGettingStartedSectionOpen (from useUserPreference) to
be undefined when consumers expect a boolean; update the initialization in
GettingStartedSection.tsx to guard against transient undefined by using a safe
fallback (e.g., coalesce isGettingStartedSectionOpen with true via `?? true`) or
gate rendering/behavior on the preference's loaded flag returned by
useUserPreference; update references to isGettingStartedSectionOpen and
setIsGettingStartedSectionOpen accordingly so consumers always see a boolean.

Comment on lines +147 to 157
const bookmarkUserPreferenceKey = userSettingsPrefix
? `${userSettingsPrefix}.bookmarks`
: undefined;
const bookmarkStorageKey = storageKey ? `${storageKey}-bookmarks` : undefined;

const enableBookmarks = !!bookmarkUserSettingsKey || !!bookmarkStorageKey;
const enableBookmarks = !!bookmarkUserPreferenceKey;

const [bookmarks, setBookmarks] = useUserSettingsCompatibility(
bookmarkUserSettingsKey,
bookmarkStorageKey,
const [bookmarks, setBookmarks] = useUserPreference(
bookmarkUserPreferenceKey,
{},
true,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check useUserPreference implementation for undefined key handling
echo "=== Check useUserPreference implementation for undefined key handling ==="
rg -n -A 30 "export const useUserPreference" --type ts --type tsx | head -100

Repository: openshift/console

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Check the file structure to locate the hook properly
echo "=== Find useUserPreference hook ==="
fd -e ts -e tsx | xargs rg -l "useUserPreference" | grep -i hook | head -10

Repository: openshift/console

Length of output: 794


🏁 Script executed:

#!/bin/bash
# Look for the hook in console-shared package
echo "=== Search in console-shared for useUserPreference ==="
find . -path "*/console-shared/*" -name "*.ts" -o -name "*.tsx" | xargs rg -n "export.*useUserPreference" 2>/dev/null | head -20

Repository: openshift/console

Length of output: 638


🏁 Script executed:

#!/bin/bash
# Read the useUserPreference hook implementation
echo "=== useUserPreference hook implementation ==="
cat -n frontend/packages/console-shared/src/hooks/useUserPreference.ts | head -100

Repository: openshift/console

Length of output: 4136


🏁 Script executed:

#!/bin/bash
# Read the test file to understand expected behavior
echo "=== useUserPreference test file ==="
cat -n frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts

Repository: openshift/console

Length of output: 32388


🏁 Script executed:

#!/bin/bash
# Read more of the hook to see how keyRef is used
cat -n frontend/packages/console-shared/src/hooks/useUserPreference.ts | sed -n '100,250p'

Repository: openshift/console

Length of output: 5644


🏁 Script executed:

#!/bin/bash
# Check specifically for keyRef usage
rg -n "keyRef" frontend/packages/console-shared/src/hooks/useUserPreference.ts

Repository: openshift/console

Length of output: 812


🏁 Script executed:

#!/bin/bash
# Check if there's any test case that passes undefined as key
rg -n "undefined" frontend/packages/console-shared/src/hooks/__tests__/useUserPreference.spec.ts | head -20

Repository: openshift/console

Length of output: 879


Conditionally invoke useUserPreference or handle undefined keys in the hook.

When userSettingsPrefix is not provided, bookmarkUserPreferenceKey will be undefined, and useUserPreference is called with an undefined key. The hook implementation uses optional chaining on the cfData object but not on keyRef.current itself. This results in operations like cfData.data[keyRef.current] attempting to access ConfigMap data with an undefined key, and updateConfigMap being invoked with undefined as the key parameter—both incorrect behaviors. While the enableBookmarks check guards downstream usage, the hook is invoked unconditionally, wasting resources and potentially creating invalid ConfigMap or localStorage entries.

Either conditionally call the hook only when userSettingsPrefix is defined, or modify the hook to return no-op values (empty state and setter) when the key parameter is undefined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/public/components/utils/console-select.tsx` around lines 147 - 157,
The code calls useUserPreference with bookmarkUserPreferenceKey which can be
undefined; either stop calling useUserPreference when bookmarkUserPreferenceKey
is undefined (wrap the useUserPreference call behind the existing
enableBookmarks check so bookmarks and setBookmarks are only created when
enableBookmarks is true), or update the useUserPreference implementation to
early-return a no-op pair (empty state and a setter that does nothing) whenever
its key parameter is undefined (ensure keyRef.current and any cfData access and
updateConfigMap calls are guarded against undefined). Reference:
bookmarkUserPreferenceKey, enableBookmarks, useUserPreference, keyRef.current,
cfData, updateConfigMap.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 27, 2026

@vojtechszocs: 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/frontend 8524c03 link true /test frontend

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 component/dashboard Related to dashboard component/dev-console Related to dev-console component/helm Related to helm-plugin component/knative Related to knative-plugin component/olm Related to OLM component/sdk Related to console-plugin-sdk component/shared Related to console-shared component/topology Related to topology jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. plugin-api-changed Categorizes a PR as containing plugin API changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants