Skip to content

fix(Catalog): Select appropriate Catalog for Resource#3131

Draft
lordrip wants to merge 1 commit intoKaotoIO:mainfrom
lordrip:fix/preload-appropriate-catalog
Draft

fix(Catalog): Select appropriate Catalog for Resource#3131
lordrip wants to merge 1 commit intoKaotoIO:mainfrom
lordrip:fix/preload-appropriate-catalog

Conversation

@lordrip
Copy link
Copy Markdown
Member

@lordrip lordrip commented Apr 16, 2026

Changes

Pick the catalog based on the resource's schema type so opening a Citrus test no longer loads a Camel catalog (and vice-versa).

  • Add detectSchemaType() to infer the schema from source/path without constructing a full KaotoResource, avoiding a chicken-and-egg problem at provider mount time.
  • Add COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE as the single source of truth for resource→runtime compatibility, replacing the per-resource getCompatibleRuntimes() methods.
  • Persist the selected catalog per schema type via catalog-storage.ts (keyed map) instead of one global entry, with legacy-format detection to discard old values.
  • Simplify IntegrationTypeSelector, RuntimeSelector, and NewFlow now that runtime filtering lives in one place.

Summary by CodeRabbit

  • New Features

    • Added automatic detection of integration resource types (Camel Routes, Integrations, Kamelet Bindings, Citrus Tests, etc.) based on content and file path.
  • Bug Fixes

    • Fixed catalog selection persistence to properly handle legacy stored data and ensure compatibility across resource types.
  • Refactor

    • Simplified integration type selection by removing automatic catalog switching behavior.
    • Centralized runtime compatibility mappings for improved maintainability.

Pick the catalog based on the resource's schema type so opening a Citrus test no longer loads a Camel catalog (and vice versa).

- Add `detectSchemaType()` to infer the schema from source/path without constructing a full `KaotoResource`, avoiding a chicken-and-egg problem at provider mount time.
- Add `COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE` as the single source of truth for resource→runtime compatibility, replacing the per-resource `getCompatibleRuntimes()` methods.
- Persist the selected catalog per schema type via `catalog-storage.ts` (keyed map) instead of one global entry, with legacy-format detection to discard old values.
- Simplify `IntegrationTypeSelector`, `RuntimeSelector`, and `NewFlow` now that runtime filtering lives in one place.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This PR refactors runtime and catalog selection to centralize schema-type-based compatibility lookup. It removes per-resource getCompatibleRuntimes() methods, introduces a new COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE registry, adds schema detection via detectSchemaType(), and simplifies catalog-selection logic by removing side effects from flow-type switching.

Changes

Cohort / File(s) Summary
Integration Type Selector & Modal
packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/ChangeIntegrationTypeModal/ChangeIntegrationTypeModal.tsx, IntegrationTypeSelector.tsx, IntegrationTypeSelectorToggle/IntegrationTypeSelectorToggle.tsx
Removed changesCatalog prop and catalog-change side effects; simplified modal confirmation to only update source code via setCodeAndNotify without modifying catalog selection state.
Flow Type Selection Tests & Components
packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.test.tsx, NewFlow.test.tsx, NewFlow.tsx
Updated test assertions to verify setCodeAndNotify invocation instead of catalog changes; removed useRuntimeContext dependency and catalog-update logic on flow confirmation.
Resource Model Classes
packages/ui/src/models/camel/camel-k-resource.ts, camel-route-resource.ts, kamelet-binding-resource.ts, kamelet-resource.ts, pipe-resource.ts, packages/ui/src/models/citrus/citrus-test-resource.ts, packages/ui/src/models/kaoto-resource.ts
Removed getCompatibleRuntimes() methods from all resource classes and interface, centralizing runtime compatibility into schema-type-based lookup.
Runtime Compatibility Registry
packages/ui/src/models/camel/compatible-runtimes.ts, compatible-runtimes.test.ts
Added new COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE constant mapping schema types to runtime arrays; test coverage verifies Camel types map to Main/Quarkus/Spring Boot and Test maps to Citrus.
Schema Type Detection
packages/ui/src/models/camel/detect-schema-type.ts, detect-schema-type.test.ts
Added new detectSchemaType() function determining SourceSchemaType from file content and path, with path-based hints and content parsing fallback.
Catalog Selection Utilities
packages/ui/src/utils/catalog-helper.ts, catalog-helper.test.ts
Refactored findCatalog() to accept compatibleRuntimes array instead of source type; replaced requiresCatalogChange with isCatalogCompatible() for runtime compatibility checking.
Catalog Storage
packages/ui/src/utils/catalog-storage.ts, catalog-storage.test.ts
Added new persistent catalog storage module with per-schema-type lookup; includes helpers for reading, persisting, and migrating from legacy single-entry format.
Runtime Provider & Tests
packages/ui/src/providers/runtime.provider.tsx, runtime.provider.test.tsx
Replaced resource-based compatibility with detectSchemaType() and schema-type-based lookup; integrated persisted catalog storage with fallback to compatible runtime selection.
Runtime Selector
packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.tsx, RuntimeSelector.test.tsx
Replaced getCompatibleRuntimes() calls with COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE lookup; removed localStorage integration and updated test assertions for catalog name/version validation.
Resource Test Cleanup
packages/ui/src/models/camel/camel-route-resource.test.ts, kamelet-binding-resource.test.ts, kamelet-resource.test.ts, pipe-resource.test.ts, packages/ui/src/models/citrus/citrus-test-resource.test.ts
Removed test suites for deleted getCompatibleRuntimes() methods across all resource model tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • igarashitm
  • djelinek
  • PVinaches

🐰 Out with scattered methods, resource-bound!
Schema types make truth profound—
One map of runtimes, crystal clear,
Centralized logic, far and near,
A hop toward architecture sincere! 🎯✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(Catalog): Select appropriate Catalog for Resource' directly addresses the main objective—centralizing catalog selection based on resource schema type rather than scattered per-resource logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 95.76271% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.74%. Comparing base (ac352f4) to head (4fb7608).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
packages/ui/src/providers/runtime.provider.tsx 87.09% 4 Missing ⚠️
packages/ui/src/models/camel/detect-schema-type.ts 97.72% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3131      +/-   ##
==========================================
- Coverage   91.74%   91.74%   -0.01%     
==========================================
  Files         602      605       +3     
  Lines       22980    23017      +37     
  Branches     5406     5459      +53     
==========================================
+ Hits        21083    21116      +33     
- Misses       1787     1898     +111     
+ Partials      110        3     -107     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/ui/src/providers/runtime.provider.tsx (1)

69-103: ⚠️ Potential issue | 🟠 Major

Add AbortController and include props.catalogUrl in useEffect dependencies to prevent stale fetch responses from overwriting active catalog state.

The effect at lines 69–102 fetches when schemaType or compatibleRuntimesKey changes, but props.catalogUrl is used in the fetch call without being listed in dependencies (line 102). More critically, when schemaType changes, an older in-flight request can complete after a new one starts, overwriting the catalog state for the wrong schema. Additionally, if resolved is undefined, the previous selectedCatalogState persists, showing stale data for a different schema type.

Proposed fix
 useEffect(() => {
+  const controller = new AbortController();
+  let cancelled = false;
+  setLoadingStatus(LoadingStatus.Loading);
+
+  fetch(props.catalogUrl, { signal: controller.signal })
     .then((response) => {
-      setLoadingStatus(LoadingStatus.Loading);
       return response.json();
     })
     .then((library: CatalogLibrary) => {
+      if (cancelled) return;
       const persisted = getPersistedCatalog(schemaType);
 
       let resolved: CatalogLibraryEntry | undefined;
       if (
         isDefined(persisted) &&
         isCatalogCompatible(persisted, compatibleRuntimes) &&
         library.definitions.some((c) => c.name === persisted.name)
       ) {
         resolved = persisted;
       } else {
         resolved = findCatalog(compatibleRuntimes, library);
       }
 
       setCatalogLibrary(library);
-      if (isDefined(resolved)) {
-        setSelectedCatalogState(resolved);
-      }
+      setSelectedCatalogState(resolved);
     })
     .then(() => {
+      if (cancelled) return;
       setLoadingStatus(LoadingStatus.Loaded);
     })
     .catch((error) => {
+      if (error?.name === 'AbortError' || cancelled) return;
       setErrorMessage(error.message);
       setLoadingStatus(LoadingStatus.Error);
     });
+  return () => {
+    cancelled = true;
+    controller.abort();
+  };
-  // eslint-disable-next-line react-hooks/exhaustive-deps
-}, [schemaType, compatibleRuntimesKey]);
+}, [props.catalogUrl, schemaType, compatibleRuntimesKey]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/providers/runtime.provider.tsx` around lines 69 - 103, The
effect that fetches props.catalogUrl inside the useEffect (dependencies
currently [schemaType, compatibleRuntimesKey]) should be made abortable and
include props.catalogUrl in its dependency array; create an AbortController,
pass its signal to fetch(props.catalogUrl), and in the cleanup call
controller.abort() to avoid stale responses overwriting state in the component
that uses setCatalogLibrary and setSelectedCatalogState. Also, when a successful
fetch returns but resolved is undefined, explicitly clear selectedCatalogState
(e.g., setSelectedCatalogState(undefined)) so a previous selection does not
persist for a different schemaType; finally, in the catch handler treat abort
errors (ignored) differently from real errors before calling setErrorMessage and
setLoadingStatus. Ensure references to getPersistedCatalog, isCatalogCompatible,
findCatalog, compatibleRuntimesKey, and schemaType remain the same.
🧹 Nitpick comments (8)
packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.test.tsx (1)

164-200: Test logic is correct; consider renaming for clarity.

The test correctly validates that setCodeAndNotify is called and setSelectedCatalog is not called directly. However, the test description "should warn the user when selecting a flow type that previously changed the catalog" may be confusing since the current behavior no longer changes the catalog. Consider a name like "should call setCodeAndNotify without directly updating catalog when switching to incompatible flow type".

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

In
`@packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.test.tsx`
around lines 164 - 200, Rename the test description string in the it(...) block
for the test that asserts mockSetCodeAndNotify is called and
mockSetSelectedCatalog is not called (currently "should warn the user when
selecting a flow type that previously changed the catalog") to a clearer name
such as "should call setCodeAndNotify without directly updating catalog when
switching to incompatible flow type"; update only the first argument of the
it(...) call that wraps the assertions (the test with expectations on
mockSetCodeAndNotify and mockSetSelectedCatalog) so the test name accurately
reflects the asserted behavior.
packages/ui/src/models/camel/detect-schema-type.ts (1)

66-71: Redundant path type check in isCitrusShape.

Line 67 checks pathType === SourceSchemaType.Test, but this condition is already handled by the early return at lines 25-27 in detectSchemaType. When isCitrusShape is called (line 31), pathType is guaranteed not to be Test.

♻️ Suggested simplification
 const isCitrusShape = (parsed: unknown, pathType?: SourceSchemaType): boolean => {
-  if (pathType === SourceSchemaType.Test) return true;
   if (!isDefined(parsed) || Array.isArray(parsed) || typeof parsed !== 'object') return false;
   const actions = (parsed as { actions?: unknown }).actions;
   return Array.isArray(actions);
 };

Alternatively, if you want isCitrusShape to remain a standalone reusable utility, keep the check but add a comment explaining it's defensive for direct calls outside detectSchemaType.

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

In `@packages/ui/src/models/camel/detect-schema-type.ts` around lines 66 - 71,
Remove the redundant pathType === SourceSchemaType.Test check inside
isCitrusShape since detectSchemaType already handles the Test case before
calling isCitrusShape; update the isCitrusShape implementation to only validate
parsed (isDefined, object not array) and return Array.isArray((parsed as {
actions?: unknown }).actions); alternatively, if you intend isCitrusShape to be
a standalone defensive utility, keep the check but add a clear comment above
isCitrusShape explaining it’s defensive for direct callers outside
detectSchemaType.
packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelectorToggle/IntegrationTypeSelectorToggle.tsx (1)

18-29: Consider narrowing the dependency array.

Using [props] as the dependency works but causes the callback to be recreated on any prop change. Since only props.onSelect is used, narrowing to [props.onSelect] would be more precise.

♻️ Suggested refinement
   const onSelect = useCallback(
     (_event: MouseEvent | undefined, flowType: string | number | undefined) => {
       if (!flowType) return;
       const integrationType = sourceSchemaConfig.config[flowType as SourceSchemaType];

       setIsOpen(false);
       if (integrationType !== undefined) {
         props.onSelect?.(flowType as SourceSchemaType);
       }
     },
-    [props],
+    [props.onSelect],
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelectorToggle/IntegrationTypeSelectorToggle.tsx`
around lines 18 - 29, The useCallback for onSelect currently lists [props] which
forces recreation on any prop change; narrow it to only the actual dependencies
by replacing [props] with the specific references used inside the callback
(e.g., props.onSelect and the source schema used to compute integrationType).
Update the dependency array for onSelect to include props.onSelect and the
source schema/config reference (for example sourceSchemaConfig.config or
sourceSchemaConfig) so the callback only re-creates when those values change
while leaving out unrelated prop changes.
packages/ui/src/models/camel/detect-schema-type.test.ts (1)

88-90: Remove redundant undefined argument.

The second undefined argument is unnecessary since calling detectSchemaType(undefined) with a single argument already defaults the second parameter to undefined.

🧹 Suggested fix
 it('returns Route for undefined source and no path', () => {
-  expect(detectSchemaType(undefined, undefined)).toEqual(SourceSchemaType.Route);
+  expect(detectSchemaType(undefined)).toEqual(SourceSchemaType.Route);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/models/camel/detect-schema-type.test.ts` around lines 88 -
90, The test case "returns Route for undefined source and no path" calls
detectSchemaType with two undefineds; remove the redundant second argument so it
calls detectSchemaType(undefined) (or simply detectSchemaType() if preferred) to
rely on the function's default for the second parameter; update the assertion in
the test file so it references detectSchemaType with a single argument and keeps
the expected SourceSchemaType.Route result.
packages/ui/src/utils/catalog-helper.test.ts (1)

20-22: Remove redundant undefined argument.

Similar to the other test file, the explicit undefined is unnecessary.

🧹 Suggested fix
 it('returns undefined when catalogLibrary is missing', () => {
-  expect(findCatalog(['Main'], undefined)).toBeUndefined();
+  expect(findCatalog(['Main'])).toBeUndefined();
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/utils/catalog-helper.test.ts` around lines 20 - 22, The test
passes an explicit undefined to findCatalog which is redundant; update the test
in catalog-helper.test.ts to call findCatalog(['Main']) (remove the second
argument) so it mirrors other tests and relies on the default/omitted parameter
behavior for the missing catalogLibrary.
packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.test.tsx (1)

57-65: Conditional localStorage assertion may silently pass when persistence fails.

The if (raw !== null) guard means the test will pass even if nothing is written to localStorage. Based on the context snippet showing that RuntimeProvider.setSelectedCatalog calls setPersistedCatalog, the persistence should occur. Consider making this assertion unconditional to catch regression if persistence breaks.

💡 Suggested fix
-    const raw = localStorage.getItem(LocalStorageKeys.SelectedCatalog);
-    if (raw !== null) {
-      const parsed = JSON.parse(raw);
-      expect(parsed).toEqual(expect.any(Object));
-      expect((parsed as Record<string, unknown>).name).toBeUndefined();
-      expect((parsed as Record<string, unknown>).version).toBeUndefined();
-    }
+    const raw = localStorage.getItem(LocalStorageKeys.SelectedCatalog);
+    expect(raw).not.toBeNull();
+    const parsed = JSON.parse(raw!);
+    expect(parsed).toEqual(expect.any(Object));
+    expect((parsed as Record<string, unknown>).name).toBeUndefined();
+    expect((parsed as Record<string, unknown>).version).toBeUndefined();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.test.tsx`
around lines 57 - 65, The test currently guards the localStorage read with "if
(raw !== null)" which lets the test pass silently if persistence didn't occur;
remove the conditional and assert that
localStorage.getItem(LocalStorageKeys.SelectedCatalog) is non-null (e.g.,
expect(raw).not.toBeNull()), then parse JSON and assert parsed is an object and
its name/version are undefined. Update the test around the
RuntimeProvider.setSelectedCatalog / setPersistedCatalog call to unconditionally
read and validate LocalStorageKeys.SelectedCatalog so regressions in persistence
fail the test.
packages/ui/src/providers/runtime.provider.test.tsx (1)

145-148: Use globalThis instead of global for better cross-environment compatibility.

The SonarCloud hint is valid. Using globalThis is the modern, standardized way to access the global object across different JavaScript environments.

🧹 Suggested fix
-    (global as unknown as { fetch: jest.Mock }).fetch = jest
+    (globalThis as unknown as { fetch: jest.Mock }).fetch = jest
       .fn()
       .mockResolvedValue({ json: () => Promise.resolve(mockCatalogLibrary) });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/providers/runtime.provider.test.tsx` around lines 145 - 148,
Replace usage of the non-standard global object in the test by changing the
fetch mock assignment from (global as unknown as { fetch: jest.Mock }).fetch =
jest.fn().mockResolvedValue(...) to use globalThis instead; update the statement
that assigns the mock to the global fetch (the block that sets .fetch =
jest.fn().mockResolvedValue({ json: () => Promise.resolve(mockCatalogLibrary)
})) so it references (globalThis as unknown as { fetch: jest.Mock }).fetch to
ensure cross-environment compatibility.
packages/ui/src/utils/catalog-storage.ts (1)

39-42: Consider read-modify-write race conditions for concurrent tabs.

setPersistedCatalog performs a non-atomic read-modify-write on localStorage. If the user has multiple tabs open and changes the catalog in both, the last write wins and may overwrite changes from the other tab. This is a minor concern for this use case but worth noting.

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

In `@packages/ui/src/utils/catalog-storage.ts` around lines 39 - 42,
setPersistedCatalog does a non-atomic read-modify-write using readCatalogMap and
writeCatalogMap against localStorage which can cause lost updates across
concurrent tabs; change the update strategy to an atomic/optimistic approach:
add a version/timestamp field to the stored catalog (returned by
readCatalogMap), then in setPersistedCatalog loop: read current, merge the new
entry into the map, set a new version/timestamp, and attempt to write only if
the version you read is still current (retry a few times on mismatch) or fall
back to a merge strategy or notify the user; alternatively replace the
localStorage backing with IndexedDB transactions or use the storage event to
reconcile changes across tabs—reference the functions setPersistedCatalog,
readCatalogMap and writeCatalogMap when implementing the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ui/src/providers/runtime.provider.tsx`:
- Around line 79-83: The code currently matches a persisted catalog entry by
name only (library.definitions.some((c) => c.name === persisted.name)), which
can allow a persisted item whose version or runtime no longer exists; update the
condition that sets selectedCatalog to verify the full identity of the persisted
entry (e.g., match name AND version AND runtime/other identity fields) against
library.definitions and compatibleRuntimes and/or use an existing identity-check
helper (or extend isCatalogCompatible) so the predicate becomes something like:
library.definitions.some(c => c.name === persisted.name && c.version ===
persisted.version && c.runtime === persisted.runtime) before accepting persisted
as the selectedCatalog.
- Line 54: The current creation of compatibleRuntimesKey uses
Array.prototype.sort() with default semantics which Sonar flags; update the sort
call to use an explicit comparator (e.g., string localeCompare) when building
compatibleRuntimesKey from compatibleRuntimes so ordering is deterministic:
convert compatibleRuntimes to an array, call .sort((a, b) =>
a.localeCompare(b)), then .join('|') to form compatibleRuntimesKey (refer to the
compatibleRuntimesKey variable and compatibleRuntimes iterable).

---

Outside diff comments:
In `@packages/ui/src/providers/runtime.provider.tsx`:
- Around line 69-103: The effect that fetches props.catalogUrl inside the
useEffect (dependencies currently [schemaType, compatibleRuntimesKey]) should be
made abortable and include props.catalogUrl in its dependency array; create an
AbortController, pass its signal to fetch(props.catalogUrl), and in the cleanup
call controller.abort() to avoid stale responses overwriting state in the
component that uses setCatalogLibrary and setSelectedCatalogState. Also, when a
successful fetch returns but resolved is undefined, explicitly clear
selectedCatalogState (e.g., setSelectedCatalogState(undefined)) so a previous
selection does not persist for a different schemaType; finally, in the catch
handler treat abort errors (ignored) differently from real errors before calling
setErrorMessage and setLoadingStatus. Ensure references to getPersistedCatalog,
isCatalogCompatible, findCatalog, compatibleRuntimesKey, and schemaType remain
the same.

---

Nitpick comments:
In
`@packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.test.tsx`:
- Around line 164-200: Rename the test description string in the it(...) block
for the test that asserts mockSetCodeAndNotify is called and
mockSetSelectedCatalog is not called (currently "should warn the user when
selecting a flow type that previously changed the catalog") to a clearer name
such as "should call setCodeAndNotify without directly updating catalog when
switching to incompatible flow type"; update only the first argument of the
it(...) call that wraps the assertions (the test with expectations on
mockSetCodeAndNotify and mockSetSelectedCatalog) so the test name accurately
reflects the asserted behavior.

In
`@packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelectorToggle/IntegrationTypeSelectorToggle.tsx`:
- Around line 18-29: The useCallback for onSelect currently lists [props] which
forces recreation on any prop change; narrow it to only the actual dependencies
by replacing [props] with the specific references used inside the callback
(e.g., props.onSelect and the source schema used to compute integrationType).
Update the dependency array for onSelect to include props.onSelect and the
source schema/config reference (for example sourceSchemaConfig.config or
sourceSchemaConfig) so the callback only re-creates when those values change
while leaving out unrelated prop changes.

In
`@packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.test.tsx`:
- Around line 57-65: The test currently guards the localStorage read with "if
(raw !== null)" which lets the test pass silently if persistence didn't occur;
remove the conditional and assert that
localStorage.getItem(LocalStorageKeys.SelectedCatalog) is non-null (e.g.,
expect(raw).not.toBeNull()), then parse JSON and assert parsed is an object and
its name/version are undefined. Update the test around the
RuntimeProvider.setSelectedCatalog / setPersistedCatalog call to unconditionally
read and validate LocalStorageKeys.SelectedCatalog so regressions in persistence
fail the test.

In `@packages/ui/src/models/camel/detect-schema-type.test.ts`:
- Around line 88-90: The test case "returns Route for undefined source and no
path" calls detectSchemaType with two undefineds; remove the redundant second
argument so it calls detectSchemaType(undefined) (or simply detectSchemaType()
if preferred) to rely on the function's default for the second parameter; update
the assertion in the test file so it references detectSchemaType with a single
argument and keeps the expected SourceSchemaType.Route result.

In `@packages/ui/src/models/camel/detect-schema-type.ts`:
- Around line 66-71: Remove the redundant pathType === SourceSchemaType.Test
check inside isCitrusShape since detectSchemaType already handles the Test case
before calling isCitrusShape; update the isCitrusShape implementation to only
validate parsed (isDefined, object not array) and return Array.isArray((parsed
as { actions?: unknown }).actions); alternatively, if you intend isCitrusShape
to be a standalone defensive utility, keep the check but add a clear comment
above isCitrusShape explaining it’s defensive for direct callers outside
detectSchemaType.

In `@packages/ui/src/providers/runtime.provider.test.tsx`:
- Around line 145-148: Replace usage of the non-standard global object in the
test by changing the fetch mock assignment from (global as unknown as { fetch:
jest.Mock }).fetch = jest.fn().mockResolvedValue(...) to use globalThis instead;
update the statement that assigns the mock to the global fetch (the block that
sets .fetch = jest.fn().mockResolvedValue({ json: () =>
Promise.resolve(mockCatalogLibrary) })) so it references (globalThis as unknown
as { fetch: jest.Mock }).fetch to ensure cross-environment compatibility.

In `@packages/ui/src/utils/catalog-helper.test.ts`:
- Around line 20-22: The test passes an explicit undefined to findCatalog which
is redundant; update the test in catalog-helper.test.ts to call
findCatalog(['Main']) (remove the second argument) so it mirrors other tests and
relies on the default/omitted parameter behavior for the missing catalogLibrary.

In `@packages/ui/src/utils/catalog-storage.ts`:
- Around line 39-42: setPersistedCatalog does a non-atomic read-modify-write
using readCatalogMap and writeCatalogMap against localStorage which can cause
lost updates across concurrent tabs; change the update strategy to an
atomic/optimistic approach: add a version/timestamp field to the stored catalog
(returned by readCatalogMap), then in setPersistedCatalog loop: read current,
merge the new entry into the map, set a new version/timestamp, and attempt to
write only if the version you read is still current (retry a few times on
mismatch) or fall back to a merge strategy or notify the user; alternatively
replace the localStorage backing with IndexedDB transactions or use the storage
event to reconcile changes across tabs—reference the functions
setPersistedCatalog, readCatalogMap and writeCatalogMap when implementing the
change.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d499874b-c596-44a9-af7c-1c8e4153277b

📥 Commits

Reviewing files that changed from the base of the PR and between ac352f4 and 4fb7608.

📒 Files selected for processing (27)
  • packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/ChangeIntegrationTypeModal/ChangeIntegrationTypeModal.tsx
  • packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.test.tsx
  • packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelector.tsx
  • packages/ui/src/components/Visualization/ContextToolbar/IntegrationTypeSelector/IntegrationTypeSelectorToggle/IntegrationTypeSelectorToggle.tsx
  • packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.test.tsx
  • packages/ui/src/components/Visualization/ContextToolbar/RuntimeSelector/RuntimeSelector.tsx
  • packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.test.tsx
  • packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.tsx
  • packages/ui/src/models/camel/camel-k-resource.ts
  • packages/ui/src/models/camel/camel-route-resource.test.ts
  • packages/ui/src/models/camel/camel-route-resource.ts
  • packages/ui/src/models/camel/compatible-runtimes.test.ts
  • packages/ui/src/models/camel/compatible-runtimes.ts
  • packages/ui/src/models/camel/detect-schema-type.test.ts
  • packages/ui/src/models/camel/detect-schema-type.ts
  • packages/ui/src/models/camel/kamelet-binding-resource.test.ts
  • packages/ui/src/models/camel/kamelet-resource.test.ts
  • packages/ui/src/models/camel/pipe-resource.test.ts
  • packages/ui/src/models/citrus/citrus-test-resource.test.ts
  • packages/ui/src/models/citrus/citrus-test-resource.ts
  • packages/ui/src/models/kaoto-resource.ts
  • packages/ui/src/providers/runtime.provider.test.tsx
  • packages/ui/src/providers/runtime.provider.tsx
  • packages/ui/src/utils/catalog-helper.test.ts
  • packages/ui/src/utils/catalog-helper.ts
  • packages/ui/src/utils/catalog-storage.test.ts
  • packages/ui/src/utils/catalog-storage.ts
💤 Files with no reviewable changes (10)
  • packages/ui/src/models/camel/kamelet-resource.test.ts
  • packages/ui/src/models/camel/camel-k-resource.ts
  • packages/ui/src/models/camel/camel-route-resource.test.ts
  • packages/ui/src/models/kaoto-resource.ts
  • packages/ui/src/models/camel/pipe-resource.test.ts
  • packages/ui/src/models/camel/kamelet-binding-resource.test.ts
  • packages/ui/src/components/Visualization/EmptyState/FlowType/NewFlow.tsx
  • packages/ui/src/models/citrus/citrus-test-resource.test.ts
  • packages/ui/src/models/camel/camel-route-resource.ts
  • packages/ui/src/models/citrus/citrus-test-resource.ts

const [selectedCatalog, setSelectedCatalog] = useState<CatalogLibraryEntry | undefined>(localSelectedCatalog);
const schemaType = useMemo(() => detectSchemaType(sourceCode, path), [sourceCode, path]);
const compatibleRuntimes = COMPATIBLE_RUNTIMES_BY_SCHEMA_TYPE[schemaType];
const compatibleRuntimesKey = [...compatibleRuntimes].sort().join('|');
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "runtime.provider.tsx" | head -20

Repository: KaotoIO/kaoto

Length of output: 105


🏁 Script executed:

cat -n packages/ui/src/providers/runtime.provider.tsx | sed -n '40,70p'

Repository: KaotoIO/kaoto

Length of output: 1654


🏁 Script executed:

cat -n packages/ui/src/providers/runtime.provider.tsx | head -80

Repository: KaotoIO/kaoto

Length of output: 3853


🏁 Script executed:

find . -type f -name "*compatible-runtimes*"

Repository: KaotoIO/kaoto

Length of output: 169


🏁 Script executed:

cat packages/ui/src/models/camel/compatible-runtimes.ts

Repository: KaotoIO/kaoto

Length of output: 893


🏁 Script executed:

find . -type f \( -name ".eslintrc*" -o -name "sonar*" -o -name "tsconfig.json" \) | grep -E "(packages/ui|root)" | head -10

Repository: KaotoIO/kaoto

Length of output: 118


🏁 Script executed:

cat packages/ui/tsconfig.json 2>/dev/null | head -50

Repository: KaotoIO/kaoto

Length of output: 739


🏁 Script executed:

cat tsconfig.json 2>/dev/null | head -50

Repository: KaotoIO/kaoto

Length of output: 39


🏁 Script executed:

find . -type f -name ".eslintrc*" -o -name "eslintrc*" | head -10

Repository: KaotoIO/kaoto

Length of output: 39


🏁 Script executed:

find . -type f \( -name "sonar-project.properties" -o -name ".sonarrc" \) 2>/dev/null

Repository: KaotoIO/kaoto

Length of output: 39


🏁 Script executed:

cat -n packages/ui/src/providers/runtime.provider.tsx | sed -n '50,58p'

Repository: KaotoIO/kaoto

Length of output: 615


Add an explicit comparator for sort() to satisfy the Sonar failure.

Line 54 currently relies on default string sort semantics; Sonar flags this as a blocker. Make ordering explicit.

💡 Proposed fix
-const compatibleRuntimesKey = [...compatibleRuntimes].sort().join('|');
+const compatibleRuntimesKey = [...compatibleRuntimes].sort((a, b) => a.localeCompare(b)).join('|');
📝 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 compatibleRuntimesKey = [...compatibleRuntimes].sort().join('|');
const compatibleRuntimesKey = [...compatibleRuntimes].sort((a, b) => a.localeCompare(b)).join('|');
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 54-54: Provide a compare function to avoid sorting elements alphabetically.

See more on https://sonarcloud.io/project/issues?id=KaotoIO_kaoto&issues=AZ2WGcErDqE6GPUTSFzO&open=AZ2WGcErDqE6GPUTSFzO&pullRequest=3131

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

In `@packages/ui/src/providers/runtime.provider.tsx` at line 54, The current
creation of compatibleRuntimesKey uses Array.prototype.sort() with default
semantics which Sonar flags; update the sort call to use an explicit comparator
(e.g., string localeCompare) when building compatibleRuntimesKey from
compatibleRuntimes so ordering is deterministic: convert compatibleRuntimes to
an array, call .sort((a, b) => a.localeCompare(b)), then .join('|') to form
compatibleRuntimesKey (refer to the compatibleRuntimesKey variable and
compatibleRuntimes iterable).

Comment on lines +79 to +83
if (
isDefined(persisted) &&
isCatalogCompatible(persisted, compatibleRuntimes) &&
library.definitions.some((c) => c.name === persisted.name)
) {
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.

⚠️ Potential issue | 🟠 Major

Match persisted catalog by full identity, not only name.

At Line 82, validating only name can accept a persisted entry whose version (or runtime) no longer exists in the current library. That can leave selectedCatalog pointing to an unavailable catalog.

💡 Proposed fix
-  library.definitions.some((c) => c.name === persisted.name)
+  library.definitions.some(
+    (c) =>
+      c.name === persisted.name &&
+      c.version === persisted.version &&
+      c.runtime === persisted.runtime,
+  )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/providers/runtime.provider.tsx` around lines 79 - 83, The
code currently matches a persisted catalog entry by name only
(library.definitions.some((c) => c.name === persisted.name)), which can allow a
persisted item whose version or runtime no longer exists; update the condition
that sets selectedCatalog to verify the full identity of the persisted entry
(e.g., match name AND version AND runtime/other identity fields) against
library.definitions and compatibleRuntimes and/or use an existing identity-check
helper (or extend isCatalogCompatible) so the predicate becomes something like:
library.definitions.some(c => c.name === persisted.name && c.version ===
persisted.version && c.runtime === persisted.runtime) before accepting persisted
as the selectedCatalog.

@lordrip lordrip marked this pull request as draft April 16, 2026 11:52
@lordrip
Copy link
Copy Markdown
Member Author

lordrip commented Apr 16, 2026

Turning it into draft since I can't continue working on this at the moment

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant