Skip to content

Conversation

@imedyaosandi
Copy link

@imedyaosandi imedyaosandi commented Nov 24, 2025

$Subject

Summary by CodeRabbit

  • New Features
    • Dynamic field support in forms: fetching, inline rendering, value propagation and SQL assistance.
    • Configurable driver options: default, custom (local) and Maven-based UIs with save/clear and test-connection.
    • Server-backed driver operations: download drivers, fetch Maven coordinates, and load + test DB drivers.
    • Fetch stored procedures from DB connections.
    • Enhanced connector/connection forms: driverPath parameter, parameter management, combo value wiring, and a reusable generic radio group UI control.

✏️ Tip: You can customize this high-level summary in your review settings.

@CLAassistant
Copy link

CLAassistant commented Nov 24, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds dynamic-field and driver-management capabilities: new RPC types/endpoints and wiring, a DynamicFieldsHandler for runtime form interactions and SQL construction, driver-configuration UI components, and FormGenerator integration to surface driver parameters and dynamic fields.

Changes

Cohort / File(s) Summary
Core RPC types & exports
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts, workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts, workspaces/mi/mi-core/src/rpc-types/mi-diagram/index.ts
Added request/response interfaces and types for dynamic fields and driver operations; extended DSSFetchTablesRequest with driverPath; declared and exported five new RPC request types/endpoints (loadDriverAndTestConnection, getDynamicFields, getStoredProcedures, downloadDriverForConnector, getDriverMavenCoordinates).
RPC wiring (client, extension, manager, handler)
workspaces/mi/mi-rpc-client/src/rpc-clients/mi-diagram/rpc-client.ts, workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts, workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts, workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-handler.ts
Wired new RPC methods end-to-end: client methods, language client helpers, rpc-manager implementations, and rpc-handler registrations for the new endpoints; pass-through implementations added.
Dynamic fields runtime
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
New DynamicFieldsHandler class and related types providing APIs to fetch dynamic fields, handle value changes, manage connection changes, construct/parse queries, and update element visibility with async RPC calls and UI updates.
Driver configuration UI
workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx
New driver configuration interfaces, DRIVER_OPTIONS, styling, and three React components (DefaultDriverConfig, CustomDriverConfig, MavenDriverConfig) for default/custom/Maven driver flows including save/clear/test/download states and error/loading handling.
Form integration & radio group
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx, workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx
Integrated DynamicFieldsHandler into FormGenerator; added props/state for parameters, setComboValues, comboValuesMap, connectionName, connections; added dynamic-field rendering and driver-config workflows; introduced GenericRadioGroup component and extended Element onValueChange.
Connector side panel
workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx
Added comboValuesMap and params state, parameter normalization/generation helpers, updated form-values handling, and passed new props to FormGenerator.
Visualizer forms
workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx, workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/GenerateResourcesForm.tsx
Populate driver params from existing connections into params state and pass to FormGenerator; include driverPath when fetching tables.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as FormGenerator / Driver UI
    participant Handler as DynamicFieldsHandler
    participant LangClient as ExtendedLanguageClient
    participant RPC as MiDiagramRpcManager

    User->>UI: change connection/driver/field
    UI->>Handler: handleValueChange(field, value, element)
    Handler->>LangClient: request (getDynamicFields / getStoredProcedures / loadDriverAndTestConnection / downloadDriverForConnector / getDriverMavenCoordinates)
    LangClient->>RPC: send synapse/* RPC request
    RPC-->>LangClient: RPC response
    LangClient-->>Handler: forward response
    Handler->>UI: update combos, visibility, queries
    UI-->>User: render updated form
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus review on:
    • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx — SQL build/parse logic, async RPC/error handling, and side effects.
    • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx — lifecycle changes, hooks, new props, and driver UI integration.
    • Cross-package RPC surface — type consistency across mi-core, mi-rpc-client, mi-extension, rpc-manager, and rpc-handler.
    • workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx — UI state transitions, save/clear/download flows and error states.

Poem

🐰 I hopped through forms and RPC streams,

Drivers, fields, and query dreams,
I nudged the UI, stitched each bind,
Now combos dance and queries find,
A carrot-coded cheer for merged-up schemes!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains '$Subject' and lacks all required template sections: Purpose, Goals, Approach, UI Component Development, Icons, User stories, Release notes, Documentation, Training, Certification, Marketing, Testing, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning. Complete the PR description using the provided template. Fill in all required sections with specific details about the feature's purpose, implementation approach, test coverage, and documentation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add VSCode Extension Support for RDBMS Connector' clearly summarizes the main change: adding support for RDBMS connectors to the VSCode extension.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🧹 Nitpick comments (8)
workspaces/mi/mi-diagram/src/index.tsx (1)

23-23: Remove or clarify commented-out export

The commented export for DatabaseDriverForm looks like leftover code. To keep the public surface clear, either remove it or add a short TODO explaining when it should be re-enabled.

workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (1)

129-158: Tighten edit-flow parameter handling and avoid potential stale connectionType

The added driver-parameter handling is useful, but a couple of tweaks would make this more robust:

  • connectionFound.parameters is assumed to exist; if an older/irregular connection lacks a parameters array, parameters.filter(...) will throw. Consider defaulting to an empty array:
const parameters = connectionFound.parameters ?? [];
const driverParams = parameters.filter(/* ... */);
  • When calling reset, you use the stateful connectionType, which may still have its previous value because setConnectionType is async. Using the found value avoids any race:
- reset({
-     name: props.connectionName,
-     connectionType: connectionType
- });
+ reset({
+     name: props.connectionName,
+     connectionType: connectionFound.connectionType,
+ });

These changes preserve the current behavior while making the edit path safer.

workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (2)

70-80: Clean up duplicate setComboValues and rely on the shared helper

You now define setComboValues twice: once at component scope (used and passed to FormGenerator) and once inside fetchConnectionsForTemplateConnection (never used). The inner definition shadows the outer one and is dead code.

Recommend removing the inner definition and, if you intended to seed combo values when loading connections, explicitly call the shared helper instead, e.g.:

-        const connectionsNames = connectionsData.connections.map(connection => connection.name);
-        setConnections(connectionsNames);
+        const connectionsNames = connectionsData.connections.map(connection => connection.name);
+        setConnections(connectionsNames);
+        // Optionally expose these in comboValuesMap if needed by DynamicFieldsHandler:
+        // setComboValues('configKey', connectionsNames);

Also applies to: 88-93, 383-391


127-162: Side-panel parameter rehydration is good; consider avoiding mutation and hardening value handling

The new effect that pulls sidePanelContext.formValues.parameters back into the form and into params is a nice improvement. A few small adjustments would make it safer:

  • You mutate each param in-place (param.name = ..., param.namespaces = []). If sidePanelContext reuses these objects elsewhere, this can have surprising side effects. Consider cloning instead:
sidePanelContext.formValues.parameters.forEach((param: any) => {
    const normalizedName = getNameForController(param.name);
    if (param.isExpression) {
        // ...
        setValue(normalizedName, { isExpression: true, value, namespaces });
    } else {
        setValue(normalizedName, { ...param, namespaces: [] });
    }
});
  • For expression parameters you assume param.value is a string and call .replace(/[{}]/g, ''). If older data stores expressions under a different property (e.g. expression) or value is missing, this will throw. You already handle both in generateParams via param.value ?? param.expression; consider doing the same when calling setValue:
const raw = param.value ?? param.expression ?? '';
const cleaned = String(raw).replace(/[{}]/g, '');
setValue(normalizedName, { isExpression: true, value: cleaned, namespaces: namespacesArray });
  • generateParams(sidePanelContext.formValues.parameters) is unguarded but you already check for sidePanelContext.formValues?.parameters in the outer if, so that part is fine.

These tweaks keep the behavior but make the code more future-proof to changes in the shape of formValues.parameters.

Also applies to: 269-286

workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (1)

53-59: Tidy minor issues: reuse shared cardStyle and avoid unused props

Two small cleanups:

  1. cardStyle is already exported from FormGenerator.tsx with the same definition. To avoid duplication (and keep layout changes in one place), you can import and reuse it here instead of redefining it.

  2. In CustomDriverConfig, onConfigChange is destructured but never used, which may trip no-unused-vars:

-export const CustomDriverConfig: React.FC<DriverConfigProps> = ({
-  config,
-  onConfigChange,
+export const CustomDriverConfig: React.FC<DriverConfigProps> = ({
+  config,
   onSave,
   onClear,
   onSelectLocation,
   isLoading,
   error
 }) => {

Keeping the prop in the interface but not destructuring it is fine if this component doesn’t need to modify the config.

Also applies to: 111-119

workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

418-438: Double-check dynamic-field initialisation and error-precedence behavior.

The dynamic-field plumbing looks coherent overall, but a couple of subtleties are worth confirming:

  • DynamicFieldsHandler is initialized only once (Lines 418–438) with the then-current parameters and dynamicFields. Subsequent prop updates to parameters or connectionName won’t be reflected unless a new handler is constructed. If those props can change during the form’s lifetime (e.g., when editing an existing connector vs. creating a new one), consider either:

    • Re-instantiating the handler when those specific props change, or
    • Passing them in via methods/refs rather than capturing them in the constructor.
  • handleValueChange (Lines 469–475) is only invoked if element.onValueChange?.function is present, which is good. However, some initialisation paths rely on the useEffect calls that are currently flagged by Biome (e.g., Lines 980–986 for expression fields, and the combo/connection blocks), and those are likely to be refactored. When you move those hooks, ensure you preserve the “call once on mount with existing value” semantics so dynamic fields still hydrate correctly in edit flows.

  • In renderFormElement, custom errors take precedence (customErrors[name] ?? standardErrorMsg) for both FormExpressionFieldComponent and standard fields (Lines ~980–986, 1048–1050, 1519–1520, 1566–1569). That matches the intended behavior with setCustomError, but it also means any existing RHF validation messages will be fully hidden while a custom error is set. Confirm this is acceptable UX; otherwise, you may want to concatenate or otherwise surface both sources.

No code changes strictly required here, but please revalidate the initialization flows once you address the hook-rule fixes.

Also applies to: 469-475, 980-986, 1048-1050, 1212-1264, 1519-1525, 1566-1569

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)

190-221: Address noSwitchDeclarations lint errors by scoping const declarations per case.

Biome reports multiple noSwitchDeclarations errors where const declarations live directly in switch cases (e.g., in handleValueChange and the query-building/parsing switches). While the current code is functionally OK, the lint rule is there to prevent future accidental cross-case scope issues.

Pattern to fix (example in _buildQueryFromDynamicFields):

-        switch (operationType) {
-            case QUERY_TYPES.INSERT:
-                const insertColumns = Object.values(activeFields);
-                if (insertColumns.length > 0) {
-                    // ...
-                }
-                break;
-            case QUERY_TYPES.DELETE:
-                query = `DELETE FROM ${encodedTableName}`;
-                // ...
-                break;
-            // ...
-        }
+        switch (operationType) {
+            case QUERY_TYPES.INSERT: {
+                const insertColumns = Object.values(activeFields);
+                if (insertColumns.length > 0) {
+                    // ...
+                }
+                break;
+            }
+            case QUERY_TYPES.DELETE: {
+                query = `DELETE FROM ${encodedTableName}`;
+                // ...
+                break;
+            }
+            // ...
+        }

Apply the same { ... } wrapping pattern to:

  • switch (config.function) in handleValueChange.
  • The switch (operationType) blocks in _buildQueryFromDynamicFields and _handleManualQueryChange.
  • Any other switch where you declare const/let at case level.

This will clear the Biome errors and make future modifications safer.

Also applies to: 1026-1154, 1358-1373, 1581-1603


10-11: Minor cleanup: remove unused import and consider reducing console noise.

A few small polish items:

  • import { config } from 'process'; (Line 10) is unused in this file and can be safely removed.
  • There are multiple console.log calls throughout (_getConnectorName, _getValidConnectionDetails, _buildQueryFromDynamicFields, _handleManualQueryChange, etc.). These are helpful during development but may clutter the extension’s output channel in production.

Suggested cleanup:

-import { config } from 'process';
-import { Element, getNameForController, DynamicField, DynamicFieldGroup } from '../FormGenerator';
+import { Element, getNameForController, DynamicField, DynamicFieldGroup } from '../FormGenerator';
@@
-        console.log("Validating connection:", connection.name);
+        // Optional: use a structured logger or remove in production
@@
-        console.log("Query and fields populated from existing parameters.");
-        console.log("Query:", query);
-        console.log("queryBuilt. ----",queryBuilt);
-        console.log("Element:", element);
+        // Consider removing or gating debug logs in production builds.

Not functionally blocking, but cleaning this up will make diagnostics easier to read.

Also applies to: 552-555, 595-605, 782-785, 1184-1192, 1353-1381

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 816940a and b9187b9.

📒 Files selected for processing (15)
  • workspaces/mi/mi-core/src/rpc-types/mi-diagram/index.ts (2 hunks)
  • workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts (2 hunks)
  • workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (2 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (5 hunks)
  • workspaces/mi/mi-diagram/src/index.tsx (1 hunks)
  • workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (2 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-handler.ts (2 hunks)
  • workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (2 hunks)
  • workspaces/mi/mi-rpc-client/src/rpc-clients/mi-diagram/rpc-client.ts (2 hunks)
  • workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (3 hunks)
  • workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/GenerateResourcesForm.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-24T14:51:49.207Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.207Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-20T11:04:33.712Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 898
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:38-38
Timestamp: 2025-11-20T11:04:33.712Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the use of `useDMQueryClausesPanelStore.getState()` to access `clauseToAdd` and `setClauseToAdd` (instead of the hook subscription pattern) is intentional to prevent re-renders when these state values change.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx
🧬 Code graph analysis (9)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/index.ts (1)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (10)
  • LoadDriverAndTestConnectionRequest (2306-2316)
  • TestDbConnectionResponse (1875-1877)
  • GetDynamicFieldsRequest (2268-2274)
  • GetDynamicFieldsResponse (2276-2278)
  • DSSFetchTablesRequest (1923-1929)
  • GetStoredProceduresResponse (2280-2282)
  • DriverDownloadRequest (2284-2288)
  • DriverDownloadResponse (2290-2292)
  • DriverMavenCoordinatesRequest (2293-2297)
  • DriverMavenCoordinatesResponse (2299-2304)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)
  • DynamicField (160-172)
workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (3)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)
  • cardStyle (87-93)
workspaces/common-libs/ui-toolkit/src/components/Codicon/Codicon.tsx (1)
  • Codicon (44-56)
workspaces/common-libs/ui-toolkit/src/components/Button/Button.tsx (1)
  • Button (54-65)
workspaces/mi/mi-rpc-client/src/rpc-clients/mi-diagram/rpc-client.ts (3)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (10)
  • GetDynamicFieldsRequest (2268-2274)
  • GetDynamicFieldsResponse (2276-2278)
  • DSSFetchTablesRequest (1923-1929)
  • GetStoredProceduresResponse (2280-2282)
  • DriverDownloadRequest (2284-2288)
  • DriverDownloadResponse (2290-2292)
  • DriverMavenCoordinatesRequest (2293-2297)
  • DriverMavenCoordinatesResponse (2299-2304)
  • LoadDriverAndTestConnectionRequest (2306-2316)
  • TestDbConnectionResponse (1875-1877)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts (5)
  • getDynamicFields (465-465)
  • getStoredProcedures (466-466)
  • downloadDriverForConnector (467-467)
  • getDriverMavenCoordinates (468-468)
  • loadDriverAndTestConnection (464-464)
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (5)
  • getDynamicFields (487-489)
  • getStoredProcedures (491-493)
  • downloadDriverForConnector (495-497)
  • getDriverMavenCoordinates (499-501)
  • loadDriverAndTestConnection (483-485)
workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (2)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (10)
  • GetDynamicFieldsRequest (2268-2274)
  • GetDynamicFieldsResponse (2276-2278)
  • DSSFetchTablesRequest (1923-1929)
  • GetStoredProceduresResponse (2280-2282)
  • DriverDownloadRequest (2284-2288)
  • DriverDownloadResponse (2290-2292)
  • LoadDriverAndTestConnectionRequest (2306-2316)
  • TestDbConnectionResponse (1875-1877)
  • DriverMavenCoordinatesRequest (2293-2297)
  • DriverMavenCoordinatesResponse (2299-2304)
workspaces/mi/mi-extension/src/stateMachine.ts (1)
  • getStateMachine (681-714)
workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (3)
workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx (1)
  • ParamConfig (46-50)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)
  • getNameForController (179-184)
workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts (1)
  • setValue (103-145)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts (1)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (10)
  • LoadDriverAndTestConnectionRequest (2306-2316)
  • TestDbConnectionResponse (1875-1877)
  • GetDynamicFieldsRequest (2268-2274)
  • GetDynamicFieldsResponse (2276-2278)
  • DSSFetchTablesRequest (1923-1929)
  • GetStoredProceduresResponse (2280-2282)
  • DriverDownloadRequest (2284-2288)
  • DriverDownloadResponse (2290-2292)
  • DriverMavenCoordinatesRequest (2293-2297)
  • DriverMavenCoordinatesResponse (2299-2304)
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (1)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (7)
  • LoadDriverAndTestConnectionRequest (2306-2316)
  • TestDbConnectionResponse (1875-1877)
  • GetDynamicFieldsRequest (2268-2274)
  • GetDynamicFieldsResponse (2276-2278)
  • DriverDownloadRequest (2284-2288)
  • DriverDownloadResponse (2290-2292)
  • DriverMavenCoordinatesRequest (2293-2297)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 895-896: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 928-929: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 983-984: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1212-1213: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1213-1214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1214-1215: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1469-1470: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts

[error] 6086-6107: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 6111-6117: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 6121-6125: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 6130-6134: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 6138-6144: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 206-206: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1028-1028: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1095-1095: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1096-1096: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1097-1097: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1119-1119: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1138-1142: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1144-1144: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1259-1259: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1264-1264: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1269-1269: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1274-1274: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (12)
workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/GenerateResourcesForm.tsx (1)

174-180: Confirm that sending an empty driverPath is the intended contract

DSSFetchTablesRequest now requires driverPath, and this call always sends an empty string. If the backend treats empty string as “no explicit driver path, use default”, this is fine; if it expects a valid path or null/omitted value, this may cause confusing failures.

Please confirm the server-side expectation and, if needed, either:

  • derive driverPath from available config, or
  • make driverPath optional in the contract and omit it when not applicable.
workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (2)

212-214: Good change: preserves falsy-but-valid values in XML generation

Switching the guard to values[key] != null && values[key] !== undefined means false, 0, and empty strings will now be serialized instead of being dropped. This is a correctness improvement for boolean/numeric fields.


445-455: Passing parameters={params} into FormGenerator matches the new dynamic-field API

Wiring the local params state into FormGenerator via the parameters prop is consistent with the extended form API and the edit-flow parameter population above. As long as params follows the expected shape for dynamic fields, this looks correct.

workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1)

24-59: Generic radio group wrapper looks solid

The component cleanly wraps RadioButtonGroup with label, required marker, and optional help tooltip. Props are minimal and focused, and orientation has a sensible default. No issues from my side.

workspaces/mi/mi-core/src/rpc-types/mi-diagram/rpc-type.ts (1)

269-276: New RPC endpoints are consistent with the shared type contracts

The added RequestTypes for:

  • loadDriverAndTestConnection
  • getDynamicFields
  • getStoredProcedures
  • downloadDriverForConnector
  • getDriverMavenCoordinates

all correctly use the corresponding request/response interfaces from types.ts, and the method names follow the existing ${_preFix}/... convention. Looks good.

Also applies to: 464-468

workspaces/mi/mi-core/src/rpc-types/mi-diagram/index.ts (1)

267-275: API surface for new driver/dynamic-field methods looks correct; ensure implementations are updated

The MiDiagramAPI methods:

  • loadDriverAndTestConnection(LoadDriverAndTestConnectionRequest): Promise<TestDbConnectionResponse>
  • getDynamicFields(GetDynamicFieldsRequest): Promise<GetDynamicFieldsResponse>
  • getStoredProcedures(DSSFetchTablesRequest): Promise<GetStoredProceduresResponse>
  • downloadDriverForConnector(DriverDownloadRequest): Promise<DriverDownloadResponse>
  • getDriverMavenCoordinates(DriverMavenCoordinatesRequest): Promise<DriverMavenCoordinatesResponse>

are consistent with the request/response types and the RequestTypes declared in rpc-type.ts.

Please double-check that ExtendedLanguageClient, the RPC manager, and RPC handler layers all implement and register these methods; otherwise consumers of MiDiagramAPI will see runtime errors despite the typings compiling.

Also applies to: 455-459

workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (1)

213-243: DB connector value filtering and dynamic-form wiring look correct

The new valuesForSynapseConfig helper and its use in onClick for the "db" connector nicely ensure that transient dynamic fields (dyn_param* and preparedStmt) are not serialized back into the Synapse config, while still passing the full values object to updateMediator for other connectors.

Passing:

  • parameters={params},
  • setComboValues,
  • comboValuesMap,
  • connections,
  • connectionName,
  • and ignoreFields / disableFields

into FormGenerator lines up with the dynamic-field and driver configuration work in the rest of the PR. I don’t see functional issues here.

Also applies to: 259-267, 383-400

workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (2)

1923-1929: LGTM! Driver path addition supports new functionality.

The addition of the driverPath field to DSSFetchTablesRequest properly extends the interface to support driver-specific table fetching operations.


2268-2316: LGTM! New interface definitions are well-structured.

The new interfaces for dynamic fields, stored procedures, and driver operations are properly defined with appropriate types and follow established naming conventions. The request/response pairs are consistent and the field selections make sense for their intended operations.

workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-handler.ts (1)

321-333: LGTM! RPC handler registrations follow established patterns.

The new RPC handlers are properly registered using the messenger pattern consistent with existing handlers. The imports match the newly defined types and the handler wiring correctly delegates to the RpcManager methods.

Also applies to: 521-525

workspaces/mi/mi-rpc-client/src/rpc-clients/mi-diagram/rpc-client.ts (1)

452-465: LGTM! RPC client methods properly implement the messenger pattern.

The new client methods are correctly typed with appropriate request/response pairs and consistently use the sendRequest pattern with HOST_EXTENSION. The implementations align with the established codebase patterns.

Also applies to: 1201-1218

workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts (1)

82-90: LGTM! Language client methods properly delegate to synapse endpoints.

The new language client methods are correctly typed and follow the established pattern of delegating to synapse/* endpoints via sendRequest. The implementations are consistent with existing methods in the class.

Also applies to: 483-501

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

♻️ Duplicate comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

662-676: Unbounded retry loop persists - previously flagged.

This unbounded while loop was flagged in the previous review. If downloadDriverForConnector continues returning a falsy value, this will hang the UI indefinitely.

The suggested fix from the previous review remains valid:

-        let isDriverDownloaded = false;
-        if (!driverPath) {
-            while (!isDriverDownloaded) {
-                const args = {
-                    groupId: groupId,
-                    artifactId: artifactId,
-                    version: version,
-                };
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Checking DB Driver...");
-                driverPath = await this.rpcClient.getMiDiagramRpcClient().downloadDriverForConnector(args);
-                if (driverPath) {
-                    isDriverDownloaded = true;
-                }
-            }
-        }
+        if (!driverPath) {
+            const args = { groupId, artifactId, version };
+            this.setCustomError(
+                getNameForController(FIELD_NAMES.CONFIG_KEY),
+                "Checking DB Driver..."
+            );
+            driverPath = await this.rpcClient
+                .getMiDiagramRpcClient()
+                .downloadDriverForConnector(args);
+
+            if (!driverPath) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download DB driver. Please check Maven coordinates or network connectivity."
+                );
+                return undefined;
+            }
+        }
🧹 Nitpick comments (4)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (4)

19-19: Remove unused import.

The config import from 'process' is not used anywhere in this file.

Apply this diff:

-import { config } from 'process';
 import { Element, getNameForController, DynamicField, DynamicFieldGroup } from '../FormGenerator';

456-481: Replace forEach with for...of to properly await async operations.

Using forEach with an async callback doesn't wait for the promises to complete, which can lead to race conditions and unhandled promise rejections.

Apply this diff:

-            targetCombos.forEach(async (comboItem: string) => {
+            for (const comboItem of targetCombos) {
                 this.setComboValues(comboItem, comboOptions);
                 // if combooptions is empty trigger the onDynamicFieldChange
                 if (comboOptions.length === 0) {
 
                     // dummy empty element to trigger the onDynamicFieldChange
                     const dummyElement = {
                         name: comboItem,
                         value: {
                             name: comboItem,
                             displayName: comboItem,
                             value: "",
                             inputType: 'stringOrExpression',
                             hidden: false,
                             onValueChange: element.onValueChange?.params?.[0]?.onDynamicFieldChange,
                             parentField: fieldName,
                         },
                     };
 
                     const tempElem = this.findElementByName(this.formData.elements, FIELD_NAMES.COLUMNS);
                     if (tempElem) {
                         await this.onDynamicFieldChange(dummyElement.value, dummyElement, fieldName);
                     }
                 }
-            });
+            }

625-634: Remove commented-out code.

Lines 625-634 contain commented-out code that should be removed if no longer needed. Keeping commented code reduces readability and can cause confusion.

Apply this diff:

             this.setValue(FIELD_NAMES.ASSISTANCE_MODE, true);
         }
-        // if (this.getValues(FIELD_NAMES.ASSISTANCE_MODE) === false) {
-        //     console.log("Assistance mode disabled, skipping connection validation.");
-        //     this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), null);
-        //     this.setValue(FIELD_NAMES.ASSISTANCE_MODE, true);
-        //     //return undefined;
-        // }
-        // if (this.getValues(FIELD_NAMES.QUERY_TYPE) && this.getValues(FIELD_NAMES.QUERY_TYPE) === UI_MODES.OFFLINE) {
-        //     this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), null);
-        //     return undefined;
-        // }
         // 2. Check Required Parameters exist

1016-1019: Remove or reduce debug console.log statements.

Multiple console.log statements appear throughout the code (e.g., lines 534, 604, 613, 818, 1016-1019, 1649). Consider removing them or replacing with a proper logging framework for production code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9187b9 and 1e3b4cc.

📒 Files selected for processing (2)
  • workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 215-215: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1041-1041: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1108-1108: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1109-1109: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1110-1110: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1132-1132: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1151-1155: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1157-1157: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1272-1272: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1277-1277: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1282-1282: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1287-1287: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

777-792: Good fix for the responseColumns guard!

The guard condition now properly checks that operationType === QUERY_TYPES.SELECT and that formValues[FIELD_NAMES.RESPONSE_COLUMNS] is a non-empty string before calling .split(). This addresses the issue flagged in the previous review.

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

♻️ Duplicate comments (5)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (5)

199-229: Wrap switch case declarations in blocks.

Variable declarations in switch cases should be wrapped in blocks to prevent them from being accessible in other cases. This issue was previously identified.

Based on learnings, static analysis hints from Biome should be addressed.


648-662: Avoid unbounded retry loop when downloading DB driver.

This unbounded while (!isDriverDownloaded) loop was previously identified as a critical issue. If downloadDriverForConnector keeps returning a falsy value, the loop will never terminate and will hang the UI.


1019-1147: Wrap switch case declarations in blocks.

Variable declarations in switch cases (INSERT, DELETE, SELECT, CALL) should be wrapped in blocks to prevent them from being accessible in other cases. This issue was previously identified.

Based on learnings, static analysis hints from Biome should be addressed.


1243-1271: Wrap switch case declarations in blocks.

Variable declarations in switch cases (SELECT, DELETE, INSERT, CALL parsing) should be wrapped in blocks. This issue was previously identified.

Based on learnings, static analysis hints from Biome should be addressed.


1625-1625: Use strict equality operator.

Line 1625 uses loose equality (==) which can lead to unexpected type coercion. This was previously identified.

Apply this diff:

-        if (value == true) {
+        if (value === true) {
🧹 Nitpick comments (3)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (3)

19-19: Remove unused import.

The config import from 'process' is not used anywhere in the file.

Apply this diff:

-import { config } from 'process';
 import { Element, getNameForController, DynamicField, DynamicFieldGroup } from '../FormGenerator';

243-246: Remove commented-out code.

Commented-out code should be removed to improve maintainability. If this logic might be needed in the future, consider documenting the reasoning in a comment or tracking it in an issue.

Apply this diff:

         try {
-            // //if offline mode do not fetch dynamic fields
-            // if(element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-            //     return undefined;
-            // }
             const connectionInfo = await this._getValidConnectionDetails();

1540-1543: Remove commented-out code.

The commented code for SELECT * handling should either be implemented or removed to improve code clarity.

Apply this diff:

         if (!columnsStr) return { success: false, fields: {}, errorMessage: "No columns specified for SELECT." };
-        // Handle SELECT * case
-        // if (columnsStr.trim() === '*') {
-        //     return { success: true, fields: availableFields };
-        // }
         const columns = columnsStr.split(',').map(col => col.trim());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e3b4cc and 6cdc36d.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 215-215: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1021-1021: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1088-1088: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1089-1089: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1090-1090: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1112-1112: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1131-1135: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1137-1137: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1252-1252: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1257-1257: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1262-1262: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1267-1267: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds comprehensive VSCode extension support for RDBMS connectors, enabling form-driven SQL query building with both online (database-connected) and offline modes. The implementation includes dynamic field management, driver configuration UI with three options (Default, Custom, Maven), and several new RPC endpoints for database operations.

Key Changes:

  • Introduction of dynamic field handling system that fetches table schemas and generates form fields based on database connections
  • Driver configuration management with support for default drivers, custom local drivers, and Maven-based dependency resolution
  • New RPC endpoints for fetching dynamic fields, stored procedures, downloading drivers, and validating database connections
  • Enhanced form generator with support for radio button groups, combo value propagation, and custom error handling

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 28 comments.

Show a summary per file
File Description
GenerateResourcesForm.tsx Added driverPath parameter initialization for database connections
ConnectionFormGenerator.tsx Enhanced to populate driver parameters and handle parameter generation for connections
rpc-client.ts Added client methods for dynamic fields, stored procedures, and driver operations
rpc-manager.ts Implemented RPC manager methods for new database and driver-related operations
rpc-handler.ts Registered new RPC handlers for driver and dynamic field operations
ExtendedLanguageClient.ts Extended language client with new request methods for database operations
index.tsx Minor export comment (potentially incomplete)
AddConnector.tsx Enhanced connector form with combo values management and parameter generation
GenericRadioGroup.tsx New reusable radio button group component with tooltip support
FormGenerator.tsx Major updates including driver configuration UI, dynamic field handling, and value change propagation
DynamicFieldsHandler.tsx New comprehensive handler for dynamic fields, query building/parsing, and connection management (1634 lines)
DriverConfiguration.tsx New component providing three driver configuration options with validation
types.ts Added type definitions for dynamic fields, driver operations, and stored procedures
rpc-type.ts Defined RPC request types for new operations
index.ts (mi-core) Extended API interface with new method signatures
Comments suppressed due to low confidence (2)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx:19

  • Unused import config.
import { config } from 'process';

workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx:88

  • Unused variable setComboValues.
            const setComboValues = (elementName: string, newValues: string[]) => {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

config: OnValueChangeParams,
element: Element
): Promise<void> {
let parseSuccess = true;
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The initial value of parseSuccess is unused, since it is always overwritten.

Copilot uses AI. Check for mistakes.
const match = currentRegex?.exec(userQuery);

if (!match?.groups) {
parseSuccess = false;
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The value assigned to parseSuccess here is unused.

Copilot uses AI. Check for mistakes.
@ChinthakaJ98 ChinthakaJ98 removed the Checks/Run MI UI Tests Force run MI UI tests label Nov 26, 2025
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: 1

🧹 Nitpick comments (2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)

242-245: Remove commented-out code.

Lines 242-245 contain commented-out logic that appears to be old implementation. Dead code should be removed to improve maintainability.

Apply this diff:

     ): Promise<DynamicField[] | null> => {
         try {
-            // //if offline mode do not fetch dynamic fields
-            // if(element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-            //     return undefined;
-            // }
             const connectionInfo = await this._getValidConnectionDetails();

1389-1390: Fix typo in comment.

Line 1389 contains "complexicity" which should be "complexity".

Apply this diff:

-        // conditions within parenthesis are considered out of scope since it introduces unwanted complexicity
+        // conditions within parenthesis are considered out of scope since it introduces unwanted complexity
         const conditions = whereClause.trim().split(/\s+AND\s+/i);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdc36d and f7a43e9.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1020-1020: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1087-1087: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1088-1088: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1089-1089: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1111-1111: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1130-1134: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1136-1136: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1251-1251: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

19-19: Unable to verify the import statement due to repository access failure.

The verification process requires access to the repository files to confirm:

  • Whether DynamicFieldGroup is exported from FormGenerator
  • Whether DynamicFieldGroup is defined locally in DynamicFieldsHandler at lines 173-176
  • The exact nature of the import on line 19

Without access to the codebase, the claim about a potential circular dependency or incorrect import path cannot be definitively validated or refuted.

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

🧹 Nitpick comments (5)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (5)

242-245: Remove commented-out code.

Lines 242-245 contain commented-out code that should be removed to improve readability. If this logic might be needed in the future, document the decision in a comment or track it in an issue.

         try {
-            // //if offline mode do not fetch dynamic fields
-            // if(element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-            //     return undefined;
-            // }
             const connectionInfo = await this._getValidConnectionDetails();

330-335: Consider more robust error identification.

The error check on line 332 uses string matching (error.message.startsWith('Query parsing error:')) to avoid double banners. This is brittle and error-prone if the error message format changes. Consider using custom error types or error codes for better reliability.

// Define a custom error class
class QueryParsingError extends Error {
    constructor(message: string) {
        super(message);
        this.name = 'QueryParsingError';
    }
}

// Then in catch block:
if (!(error instanceof QueryParsingError)) {
    this.setCustomError(getNameForController(element.name), "Error processing field change.");
}

609-618: Simplify complex nested conditionals.

The nested conditionals in lines 609-618 are difficult to follow. The logic checks findElementByName, accesses currentValue, compares with OFFLINE, and sets ASSISTANCE_MODE. Consider extracting this logic into a helper method with clear naming to improve readability.

private _shouldEnableAssistanceMode(): boolean {
    const queryTypeElement = this.findElementByName(this.formData.elements, FIELD_NAMES.QUERY_TYPE);
    if (!queryTypeElement?.currentValue) {
        return true;
    }
    return !(queryTypeElement.currentValue === UI_MODES.OFFLINE && 
             this.getValues(FIELD_NAMES.QUERY_TYPE) === UI_MODES.OFFLINE);
}

// Then in _getValidConnectionDetails:
const enableAssistance = this._shouldEnableAssistanceMode();
this.setValue(FIELD_NAMES.ASSISTANCE_MODE, enableAssistance);
if (!enableAssistance) {
    return undefined;
}

744-758: Direct mutation of column object may cause side effects.

Line 746 directly mutates the column object (column.columnName = ...) which is from paramManagerValues. This could cause unintended side effects if the original data is used elsewhere. Consider creating a new object or documenting that this mutation is intentional.

             if (Array.isArray(paramManagerValues)) {
                 let count = 0;
                 for (const column of paramManagerValues) {
-                    if (operationType === QUERY_TYPES.CALL) { column.columnName = `param_${++count}`; }
+                    const columnName = operationType === QUERY_TYPES.CALL 
+                        ? `param_${++count}` 
+                        : column.columnName;
-                    if (!column.columnName) continue; // Skip if columnName is missing
+                    if (!columnName) continue; // Skip if columnName is missing
 
-                    const normalizedName = column.columnName.replace(/[^a-zA-Z0-9_]/g, '_');
+                    const normalizedName = columnName.replace(/[^a-zA-Z0-9_]/g, '_');
                     collectedValues[normalizedName] = {
                         value: column.columnValue?.value,
                         isExpression: column.columnValue?.isExpression,
                         columnType: column.propertyType?.value,
                         name: normalizedName,
-                        displayName: column.columnName,
+                        displayName: columnName,
                         helpTip: '',
                     };
                 }
             }

1623-1632: Add space after comma for consistent formatting.

Line 1626 is missing a space after the comma in this.onConnectionChange(fieldName,rpc). While minor, consistent formatting improves code readability.

         if (value === true) {
             this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
-            this.onConnectionChange(fieldName,rpc)
+            this.onConnectionChange(fieldName, rpc)
         } else {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7a43e9 and d6c5cc5.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (3)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
workspaces/mi/mi-data-mapper-utils/src/dm-utils.ts (1)
  • match (231-233)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1020-1020: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1087-1087: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1088-1088: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1089-1089: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1111-1111: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1130-1134: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1136-1136: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1251-1251: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

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

Caution

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

⚠️ Outside diff range comments (1)
workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (1)

110-159: Use connectionFound.connectionType directly when resetting form on edit.

Inside the edit flow, you call setConnectionType(connectionFound.connectionType) and then immediately reset({ ..., connectionType: connectionType }). Because setConnectionType is async, connectionType here may still hold the old value, so the form can be reset with stale connection type.

Prefer the concrete value from connectionFound:

                setConnectionType(connectionFound.connectionType);
                setConnectionName(props.connectionName);
                setFormData(connectionSchema);
-                const parameters = connectionFound.parameters
+                const parameters = connectionFound.parameters;
@@
-                reset({
-                    name: props.connectionName,
-                    connectionType: connectionType
-                });
+                reset({
+                    name: props.connectionName,
+                    connectionType: connectionFound.connectionType
+                });
♻️ Duplicate comments (10)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (6)

355-399: Fix unreachable updatedOffline && updatedOnline condition in setElementVisibility.

updatedOffline and updatedOnline are set in mutually exclusive branches based on enableCondition[0].queryType, so if (updatedOffline && updatedOnline) can never be true. Use || so the method returns true once either offline or online element is updated.

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

This also aligns the return value with the “found and updated” semantics used elsewhere in the method.


200-207: Guard queryBuildConfig before using it in handleDynamicContent.

queryBuildConfig is derived via a long optional-chaining chain, but then queryBuildConfig.queryType is accessed unconditionally. If any link in the chain is missing, this will throw at runtime when a user selects a table.

-                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig.queryType , queryBuildConfig, element);
-                    } else {
+                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
+                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element
+                            );
+                        }
+                    } else {
                         await this._handleDynamicContentChange(value, fieldName, element);
                     }

632-695: Avoid unbounded retry loop when downloading DB driver.

The while (!isDriverDownloaded) loop around downloadDriverForConnector can spin forever if the RPC continues to fail (bad coordinates, network, permission), hanging the UI and hammering the backend.

Replace it with a single attempt (or a bounded retry with backoff) and surface a clear error:

-            let isDriverDownloaded = false;
-            if (!driverPath) {
-                while (!isDriverDownloaded) {
-                    const args = {
-                        groupId: groupId,
-                        artifactId: artifactId,
-                        version: version,
-                    };
-                    this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Checking DB Driver...");
-                    driverPath = await this.rpcClient.getMiDiagramRpcClient().downloadDriverForConnector(args);
-                    if (driverPath) {
-                        isDriverDownloaded = true;
-                    }
-                }
-            }
+            if (!driverPath) {
+                const args = { groupId, artifactId, version };
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Checking DB Driver..."
+                );
+                driverPath = await this.rpcClient
+                    .getMiDiagramRpcClient()
+                    .downloadDriverForConnector(args);
+
+                if (!driverPath) {
+                    this.setCustomError(
+                        getNameForController(FIELD_NAMES.CONFIG_KEY),
+                        "Failed to download DB driver. Please check Maven coordinates or network connectivity."
+                    );
+                    return undefined;
+                }
+            }

873-890: Remove hard‑coded 'table' key in _setFieldValue dynamic field lookup.

this.dynamicFields['table'] assumes all dynamic fields live under the 'table' parent, which will break for other parents (e.g., different field names or multiple dynamic sections). This also couples the helper to a single parent key.

Consider either:

  • Passing the parent field key into _setFieldValue, or
  • Searching across all dynamic field groups:
-        const dynamicField = this.dynamicFields['table']?.fields.find((f: DynamicField) => f.value.name === fieldName);
+        const dynamicField = Object.values(this.dynamicFields)
+            .flatMap(group => group.fields)
+            .find((f: DynamicField) => f.value.name === fieldName);

This keeps expectsObject detection working regardless of which parent field owns the dynamic field.


214-221: Wrap case 'buildQuery' (and other case declarations) in blocks to satisfy noSwitchDeclarations.

const parentField = ... is declared directly under the case 'buildQuery': label. Biome flags this as noSwitchDeclarations; the variable is technically visible to other cases and can cause subtle bugs.

Wrap the case body in a block and keep the break inside it:

-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
+                    }
+                    break;
+                }

Apply the same pattern to:

  • case QUERY_TYPES.INSERT (around line 1019),
  • case QUERY_TYPES.SELECT (orderBy/limit/offset locals),
  • case QUERY_TYPES.CALL (callTemplate, callParams, prepParams), and
  • The switch in _handleManualQueryChange (SELECT/INSERT/DELETE/CALL cases).

1480-1535: Guard against undefined valuesStr in _parseCallParams.

valuesStr is typed as string | undefined, but valuesStr.split(',') is called unconditionally. If the regex ever matches without a values group (or _parseCallParams is reused elsewhere), this will throw.

     private _parseCallParams(valuesStr: string | undefined, availableFields: Record<string, DynamicFieldValue>, connectionInfo: boolean): { success: boolean, fields: Record<string, DynamicFieldValue>, errorMessage?: string } {
         const matchedFields: Record<string, DynamicFieldValue> = {};
-        const values = valuesStr.split(',').map(val => val.trim().replace(/^['"]|['"]$/g, ''));
+        if (!valuesStr) {
+            return {
+                success: false,
+                fields: {},
+                errorMessage: "No parameters provided for CALL statement."
+            };
+        }
+        const values = valuesStr.split(',').map(val => val.trim().replace(/^['"]|['"]$/g, ''));
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)

894-923: Hooks used inside ParamManagerComponent invoked as a plain function violate Rules of Hooks.

ParamManagerComponent calls useEffect but is invoked as ParamManagerComponent(...) inside the 'ParamManager' case, not as <ParamManagerComponent />. This means React can’t track its hooks correctly, and Biome correctly flags useHookAtTopLevel.

Refactor ParamManagerComponent into a proper child component and render it via JSX:

-    function ParamManagerComponent(element: Element, isRequired: boolean, helpTipElement: JSX.Element, field: any) {
-        useEffect(() => {
-            try {
-                handleValueChange(field.value, element.name.toString(), element);
-            } catch {
-                console.error("Error initializing ParamManagerComponent");
-            }
-        }, []); // run only on mount
-        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
+    const ParamManagerComponent: React.FC<{
+        element: Element;
+        isRequired: boolean;
+        helpTipElement: JSX.Element;
+        field: any;
+    }> = ({ element, isRequired, helpTipElement, field }) => {
+        useEffect(() => {
+            try {
+                handleValueChange(field.value, element.name.toString(), element);
+            } catch {
+                console.error("Error initializing ParamManagerComponent");
+            }
+        }, []);
+        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
             ...
-        </ComponentCard>;
-    }
+        </ComponentCard>;
+    };
@@
-            case 'ParamManager': {
-                return (
-                    ParamManagerComponent(element, isRequired, helpTipElement, field)
-                );
-            }
+            case 'ParamManager': {
+                return (
+                    <ParamManagerComponent
+                        element={element}
+                        isRequired={isRequired}
+                        helpTipElement={helpTipElement}
+                        field={field}
+                    />
+                );
+            }

Apply the same pattern to ExpressionFieldComponent and FormExpressionFieldComponent (render them as <ExpressionFieldComponent ... /> / <FormExpressionFieldComponent ... /> instead of calling them directly).


1191-1265: Move useEffect and declarations out of the 'combo*' switch case to satisfy Rules of Hooks and noSwitchDeclarations.

Within the 'booleanOrExpression'/'comboOrExpression'/'combo' case you:

  • Declare const items and const allowItemCreate, and
  • Call useEffect(...) inside the case body.

This triggers both useHookAtTopLevel and noSwitchDeclarations errors: hooks must be called unconditionally from component/custom-hook top level, and case-local declarations should be wrapped.

Extract this branch into a small child component that owns the effect and items calculation:

-            case 'booleanOrExpression':
-            case 'comboOrExpression':
-            case 'combo':
-                const items = element.inputType === 'booleanOrExpression' ? ["true", "false"] : (props.comboValuesMap?.[name] || element.comboValues);
-                const allowItemCreate = element.inputType === 'comboOrExpression';
-                useEffect(() => {
-                    handleValueChange(field.value, name, element);
-                }, [props.comboValuesMap?.[name]]);
-                return (
-                    <>
-                        <AutoComplete ... />
-                        {dynamicFields[name]?.fields?.length > 0 && (/* dynamic field rendering */)}
-                    </>
-                );
+            case 'booleanOrExpression':
+            case 'comboOrExpression':
+            case 'combo': {
+                return (
+                    <ComboWithDynamicFields
+                        element={element}
+                        name={name}
+                        field={field}
+                        helpTipElement={helpTipElement}
+                        isRequired={isRequired}
+                        errorMsg={errorMsg}
+                        comboValuesMap={props.comboValuesMap}
+                        dynamicFields={dynamicFields}
+                        onValueChange={handleValueChange}
+                    />
+                );
+            }

Where ComboWithDynamicFields is a new component declared at top level that:

  • Computes items and allowItemCreate,
  • Calls its own useEffect to notify onValueChange on mount/when comboValuesMap[name] changes, and
  • Renders the AutoComplete + dynamic field group.

This removes hooks from the switch, fixes the declaration-scope warning, and keeps behavior localized.


1469-1525: Also move the useEffect in the 'connection' case into a child component.

The 'connection' switch case calls useEffect directly, which again violates Rules of Hooks and is flagged by Biome.

As with the combo case, extract this into a dedicated ConnectionField component:

-            case 'connection':
-                useEffect(() => {
-                    handleValueChange(field.value, name, element);
-                }, []);
-                ...
-                return (
-                    <>
-                        {/* label + Add new connection */}
-                        <AutoComplete ... />
-                        {generatedFormDetails && ...}
-                    </>
-                );
+            case 'connection': {
+                return (
+                    <ConnectionField
+                        element={element}
+                        name={name}
+                        field={field}
+                        helpTipElement={helpTipElement}
+                        isDisabled={isDisabled}
+                        errorMsg={errorMsg}
+                        connectionNames={connectionNames}
+                        getValues={getValues}
+                        setValue={setValue}
+                        handleValueChange={handleValueChange}
+                        getConnectionNames={getConnectionNames}
+                        rpcClient={rpcClient}
+                        sidePanelContext={sidePanelContext}
+                        generatedFormDetails={generatedFormDetails}
+                        visibleDetails={visibleDetails}
+                        numberOfDifferent={numberOfDifferent}
+                        setVisibleDetails={setVisibleDetails}
+                        setNumberOfDifferent={setNumberOfDifferent}
+                        setIsClickedDropDown={setIsClickedDropDown}
+                        setIsGenerating={setIsGenerating}
+                    />
+                );
+            }

ConnectionField can then own its useEffect and any local logic, preserving hook ordering in FormGenerator.


272-325: Driver config save still reads stale state; pass the next config explicitly into saveDriverConfig.

handleDriverTypeChange calls setDriverConfig(...) and then saveDriverConfig(), but saveDriverConfig reads driverConfig from state. Because setDriverConfig is async, driverConfig inside saveDriverConfig is still the previous value, so validation and persisted coordinates can be wrong (especially when reusing existing params).

Refactor to compute a nextConfig object synchronously and pass it through:

-    const handleDriverTypeChange = async (driverType: string, element: Element) => {
+    const handleDriverTypeChange = async (driverType: string, element: Element) => {
         const option = DRIVER_OPTIONS.find(opt => opt.label === driverType);
         if (!option) return;
 
-        setDriverConfig(prev => ({ ...prev, type: option.configType }));
+        let nextConfig: DriverConfig = { ...driverConfig, type: option.configType };
+        setDriverConfig(nextConfig);
         setDriverError(null);
@@
-            if (option.configType === 'custom') {
+            if (option.configType === 'custom') {
                 if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: existingDriverPath
-                    }));
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: existingDriverPath,
+                    };
                 }
             } else {
                 if (existingGroupId && existingArtifactId && existingVersion) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: null
-                    }));
-                } else if (existingDriverPath) {
-                    setValue("driverPath", null);
-                }
-            }
-            saveDriverConfig();
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: null,
+                    };
+                } else if (existingDriverPath) {
+                    setValue("driverPath", null);
+                }
+            }
+            setDriverConfig(nextConfig);
+            await saveDriverConfig(nextConfig);
         }
     };
@@
-    const saveDriverConfig = async () => {
+    const saveDriverConfig = async (configOverride?: DriverConfig) => {
+        const cfg = configOverride ?? driverConfig;
         try {
-            if (driverConfig.type === 'maven' &&
-                (!driverConfig.groupId || !driverConfig.artifactId || !driverConfig.version)) {
+            if (cfg.type === 'maven' &&
+                (!cfg.groupId || !cfg.artifactId || !cfg.version)) {
@@
-            if (driverConfig.type === 'maven') {
+            if (cfg.type === 'maven') {
                 const validDriver = await rpcClient.getMiDiagramRpcClient().downloadDriverForConnector({
-                    groupId: driverConfig.groupId,
-                    artifactId: driverConfig.artifactId,
-                    version: driverConfig.version
+                    groupId: cfg.groupId,
+                    artifactId: cfg.artifactId,
+                    version: cfg.version
                 });
@@
-            setValue("groupId", driverConfig.groupId);
-            setValue("artifactId", driverConfig.artifactId);
-            setValue("version", driverConfig.version);
-            if (driverConfig.type === 'custom') {
-                setValue("driverPath", driverConfig.driverPath);
+            setValue("groupId", cfg.groupId);
+            setValue("artifactId", cfg.artifactId);
+            setValue("version", cfg.version);
+            if (cfg.type === 'custom') {
+                setValue("driverPath", cfg.driverPath);
             }

Update any onSave={saveDriverConfig} props to call the new signature, e.g. onSave={() => saveDriverConfig(driverConfig)}.

🧹 Nitpick comments (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (2)

418-438: DynamicFieldsHandler initialization is fine but should ideally react to parameters/dynamicFields changes.

The handler is created once in a useEffect with an empty dependency array. That’s acceptable if rpcClient, formData, parameters, etc. are effectively stable for the lifetime of the component, but if they can change between renders the handler will retain stale references.

If formData/parameters/connectionName are expected to change for the same FormGenerator instance, consider including them in the dependency array and reconstructing the handler when they change.


1191-1217: Ensure useEffect dependencies reflect name and field.value when reusing the pattern.

When you refactor the combo/connection cases into components, make sure any effects that depend on name or field.value include them in the dependency array so dynamic-field updates re-fire when the bound field changes:

useEffect(() => {
    onValueChange(field.value, name, element);
}, [field.value, name, element, comboValuesMap?.[name]]);

This avoids stale closures while keeping hook usage valid.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6c5cc5 and cd60534.

📒 Files selected for processing (4)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
  • workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (5 hunks)
  • workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx
  • workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/utils/diagram.ts : Filter out autogenerated connections (symbol starting with '_') and connections with enableFlowModel=false before rendering

Applied to files:

  • workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx
  • workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T22:16:28.380Z
Learnt from: dan-niles
Repo: wso2/vscode-extensions PR: 980
File: workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx:384-412
Timestamp: 2025-11-24T22:16:28.380Z
Learning: In the RichTextTemplateEditor component (workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx), token fetching on external `value` prop changes is intentionally disabled. Users cannot edit both the minimized and expanded editors simultaneously, so tokens only need to be generated based on user typing in the expanded editor view, not on external prop updates.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-27T07:59:33.522Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.522Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (2)
workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (3)
workspaces/ballerina/ballerina-side-panel/src/components/ParamManager/ParamManager.tsx (1)
  • ParamConfig (46-50)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)
  • getNameForController (179-184)
workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts (1)
  • setValue (103-145)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
  • DynamicFieldsHandler (162-1633)
workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (5)
  • DriverConfig (24-30)
  • DRIVER_OPTIONS (50-69)
  • DefaultDriverConfig (83-127)
  • CustomDriverConfig (129-212)
  • MavenDriverConfig (214-276)
workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts (1)
  • setValue (103-145)
workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1)
  • GenericRadioGroup (24-60)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 895-896: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 928-929: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 983-984: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1212-1213: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1213-1214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1214-1215: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1469-1470: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1020-1020: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1087-1087: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1088-1088: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1089-1089: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1111-1111: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1130-1134: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1136-1136: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1251-1251: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (5)
workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx (2)

212-263: Value filtering in onAddConnection looks correct and handles non-string values.

The updated guard values[key] != null ensures 0/false are still serialized while skipping only null/undefined, and the exclusion of configRef, connectionType, and connectionName matches the expected XML payload.

No changes needed here.


441-455: Passing parameters={params} into FormGenerator integrates driver params cleanly.

Forwarding the derived params state into FormGenerator via the new parameters prop aligns with the dynamic driver configuration logic introduced there and keeps the connection form’s driver-related params in sync.

workspaces/mi/mi-diagram/src/components/sidePanel/connectors/AddConnector.tsx (2)

222-261: Filtering out dynamic param and prepared-statement fields before updateMediator for DB connectors makes sense.

valuesForSynapseConfig’s exclusion of keys starting with dyn_param and preparedStmt when connectorName === "db" prevents internal dynamic-field and prepared-statement wiring from leaking into the Synapse config, while preserving all other values.

This is a reasonable specialization for the DB connector path, and the helper keeps the core onClick logic tidy.


375-395: The additional FormGenerator props (parameters, combo values, connection info) are wired consistently.

Passing parameters={params}, setComboValues, comboValuesMap, connections, connectionName, and ignoreFields into FormGenerator matches the extended FormGenerator API and enables dynamic fields and connection-aware behavior in the side panel.

workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

1618-1660: radio handling for driver selection is structurally sound.

The special case for driverSelectOption correctly:

  • Uses GenericRadioGroup to render options from comboValues,
  • Updates the form value via field.onChange, and
  • Delegates driver-type behavior to handleDriverTypeChange,
  • Then renders the appropriate driver configuration via renderDriverConfiguration.

Generic radio fields fall back to a simpler GenericRadioGroup with optional onValueChange integration. This split is clear and keeps driver-specific logic isolated.

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

♻️ Duplicate comments (9)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)

723-725: Correct typo in expressionTypes array entry.

'stringOrExpresion' is misspelled and duplicates the correctly spelled 'stringOrExpression' above. This can cause subtle mismatches in inputType checks.

If no backward‑compat dependency on the typo, simplify the array:

-        const expressionTypes = ['stringOrExpression', 'integerOrExpression', 'expression', 'keyOrExpression', 'resourceOrExpression',
-            'textOrExpression', 'textAreaOrExpression', 'stringOrExpresion'
-        ];
+        const expressionTypes = [
+            'stringOrExpression',
+            'integerOrExpression',
+            'expression',
+            'keyOrExpression',
+            'resourceOrExpression',
+            'textOrExpression',
+            'textAreaOrExpression',
+        ];

895-919: Fix React hook rule violations in nested helper components.

ParamManagerComponent, ExpressionFieldComponent, and FormExpressionFieldComponent all call useEffect but are invoked as plain functions from renderFormElement (not rendered as <Component />), which breaks the Rules of Hooks and is flagged by Biome.

Refactor these to real React components and render them via JSX, e.g.:

-    function ParamManagerComponent(element: Element, isRequired: boolean, helpTipElement: JSX.Element, field: any) {
-        useEffect(() => {
-            handleValueChange(field.value, element.name.toString(), element);
-        }, []); // run only on mount
-        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
+    const ParamManagerComponent: React.FC<{
+        element: Element;
+        isRequired: boolean;
+        helpTipElement: JSX.Element;
+        field: any;
+    }> = ({ element, isRequired, helpTipElement, field }) => {
+        useEffect(() => {
+            handleValueChange(field.value, element.name.toString(), element);
+        }, []); // run only on mount
+        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
             ...
-        </ComponentCard>;
-    }
+        </ComponentCard>;
+    };
@@
-            case 'ParamManager': {
-                return (
-                    ParamManagerComponent(element, isRequired, helpTipElement, field)
-                );
-            }
+            case 'ParamManager': {
+                return (
+                    <ParamManagerComponent
+                        element={element}
+                        isRequired={isRequired}
+                        helpTipElement={helpTipElement}
+                        field={field}
+                    />
+                );
+            }

Apply the same pattern to ExpressionFieldComponent and FormExpressionFieldComponent (define as React.FC and use <ExpressionFieldComponent ... /> / <FormExpressionFieldComponent ... />), so all hooks are called from proper component top‑level.

Also applies to: 921-977, 971-977


484-528: Fix stale driverConfig usage when saving driver settings.

handleDriverTypeChange mutates driverConfig via setDriverConfig and then immediately calls saveDriverConfig, but saveDriverConfig reads from the previous state. This can cause Maven validation and persisted values to be out of sync with what the UI shows (especially when loading existing coordinates from parameters).

Refactor to compute and use a single “nextConfig” object:

-    const handleDriverTypeChange = async (driverType: string, element: Element) => {
+    const handleDriverTypeChange = async (driverType: string, element: Element) => {
         const option = DRIVER_OPTIONS.find(opt => opt.label === driverType);
         if (!option) return;
-
-        setDriverConfig(prev => ({ ...prev, type: option.configType }));
-        setDriverError(null);
+        setDriverError(null);
+
+        let nextConfig: DriverConfig = {
+            ...driverConfig,
+            type: option.configType,
+        };
@@
-            if (option.configType === 'custom') {
+            if (option.configType === 'custom') {
                 if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: existingDriverPath
-                    }));
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: existingDriverPath,
+                    };
                 }
             } else {
                 if (existingGroupId && existingArtifactId && existingVersion) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: null
-                    }));
-                } else if (existingDriverPath) {
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: null,
+                    };
+                } else if (existingDriverPath) {
                     setValue("driverPath", null);
                 }
             }
-            saveDriverConfig();
+            setDriverConfig(nextConfig);
+            await saveDriverConfig(nextConfig);
         }
     };
@@
-    const saveDriverConfig = async () => {
+    const saveDriverConfig = async (configOverride?: DriverConfig) => {
+        const cfg = configOverride ?? driverConfig;
         try {
             // Validate required fields based on driver type
-            if (driverConfig.type === 'maven' &&
-                (!driverConfig.groupId || !driverConfig.artifactId || !driverConfig.version)) {
+            if (cfg.type === 'maven' &&
+                (!cfg.groupId || !cfg.artifactId || !cfg.version)) {
@@
-            if (driverConfig.type === 'maven') {
+            if (cfg.type === 'maven') {
                 const validDriver = await rpcClient.getMiDiagramRpcClient().downloadDriverForConnector({
-                    groupId: driverConfig.groupId,
-                    artifactId: driverConfig.artifactId,
-                    version: driverConfig.version
+                    groupId: cfg.groupId,
+                    artifactId: cfg.artifactId,
+                    version: cfg.version,
                 });
@@
-            setValue("groupId", driverConfig.groupId);
-            setValue("artifactId", driverConfig.artifactId);
-            setValue("version", driverConfig.version);
-            if (driverConfig.type === 'custom') {
-                setValue("driverPath", driverConfig.driverPath);
+            setValue("groupId", cfg.groupId);
+            setValue("artifactId", cfg.artifactId);
+            setValue("version", cfg.version);
+            if (cfg.type === 'custom') {
+                setValue("driverPath", cfg.driverPath);
             }

Also ensure any onSave={saveDriverConfig} props are updated to pass the current config (e.g. onSave={() => saveDriverConfig(driverConfig)}).

Also applies to: 549-587, 646-685


1203-1208: Remove useEffect calls from inside switch cases.

The combo* and connection switch branches call useEffect directly inside renderFormElement, which is conditional and depends on inputType. This violates the Rules of Hooks and matches Biome’s useHookAtTopLevel errors.

Move these effects into dedicated child components or a top‑level custom hook, e.g.:

  • For combo*: create a small ComboField component that owns the useEffect and wraps the AutoComplete and dynamic field rendering.
  • For connection: create a ConnectionField component that runs the effect once on mount and renders the connection AutoComplete.

Both components can receive element, field, name, handleValueChange, and any other needed props, and be invoked as <ComboField ... /> / <ConnectionField ... /> from the switch, keeping hook order stable.

Also applies to: 1459-1463

workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)

2254-2316: Align RPC DynamicField type with UI usage by adding columnName.

The RPC DynamicField.value object still lacks columnName, but the FormGenerator/DynamicFieldsHandler side expects it (see DynamicField in FormGenerator.tsx and _parseSelectColumns / SELECT building logic), and prior review already called this out.

Please update the interface to include columnName as first field to keep contracts consistent:

 export interface DynamicField {
     type: string;
     value: {
+        columnName: string;
         name: string;
         displayName: string;
         inputType: string;
         required: string;
         helpTip: string;
         placeholder: string;
         defaultValue: string;
     };
 }

This ensures the MI core RPC types match what the UI actually receives and what DynamicFieldsHandler/FormGenerator expect.

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (4)

197-207: Guard against undefined queryBuildConfig before building query.

You now use optional chaining for queryBuildConfig?.queryType, but still call _buildQueryFromDynamicFields even when queryBuildConfig is undefined, which will then access config.resultField! and throw.

Wrap the call instead of passing ?.queryType:

-                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig?.queryType , queryBuildConfig, element);
-                    } else {
+                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
+                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element,
+                            );
+                        }
+                    } else {
                         await this._handleDynamicContentChange(value, fieldName, element);
                     }

355-387: Fix unreachable updatedOffline && updatedOnline condition in visibility logic.

updatedOffline and updatedOnline are set in mutually exclusive branches based on enableCondition[0].queryType, so updatedOffline && updatedOnline can never be true; the function will never return early even when it did update visibility.

Change the condition to || so you return when either mode was updated:

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

879-896: Avoid hard‑coding parentField default in _setFieldValue for dynamic fields.

Defaulting parentField to 'table' still bakes in a specific section key. For dynamic fields under other parents (e.g., stored procedures), this will fail to find the correct DynamicField metadata.

At minimum, pass the real parent field where you know it, e.g. in _handleManualQueryChange:

-                    if (matched) {
-                        this._setFieldValue(fieldDef.name, matched.value, matched.isExpression);
-                    } else {
-                        this._clearFieldValue(fieldDef.name);
-                    }
-                    if (selectColMatched) {
-                        this._setFieldValue(fieldDef.name, true, selectColMatched.isExpression);
-                    }
+                    if (matched) {
+                        this._setFieldValue(fieldDef.name, matched.value, matched.isExpression, parentField);
+                    } else {
+                        this._clearFieldValue(fieldDef.name);
+                    }
+                    if (selectColMatched) {
+                        this._setFieldValue(fieldDef.name, true, selectColMatched.isExpression, parentField);
+                    }

and keep the 'table' default only as a backward‑compat fallback.


214-221: Wrap buildQuery case body in a block to satisfy noSwitchDeclarations.

The const parentField declaration inside the switch case is visible to other cases and triggers Biome’s noSwitchDeclarations error. Wrapping this case body in a block scopes the variable correctly and silences the lint error.

-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
+                    }
+                    break;
+                }

Apply the same block‑wrapping pattern to other switch cases with local const declarations (INSERT/DELETE/SELECT/CALL in _buildQueryFromDynamicFields and the parse switch in _handleManualQueryChange) as indicated by Biome.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd60534 and fdb1655.

📒 Files selected for processing (4)
  • workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (2 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T22:16:28.380Z
Learnt from: dan-niles
Repo: wso2/vscode-extensions PR: 980
File: workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx:384-412
Timestamp: 2025-11-24T22:16:28.380Z
Learning: In the RichTextTemplateEditor component (workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx), token fetching on external `value` prop changes is intentionally disabled. Users cannot edit both the minimized and expanded editors simultaneously, so tokens only need to be generated based on user typing in the expanded editor view, not on external prop updates.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
  • DynamicFieldsHandler (162-1642)
workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (5)
  • DriverConfig (24-30)
  • DRIVER_OPTIONS (50-69)
  • DefaultDriverConfig (83-127)
  • CustomDriverConfig (129-212)
  • MavenDriverConfig (214-276)
workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1)
  • GenericRadioGroup (23-59)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)
  • DynamicField (160-172)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 895-896: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 923-924: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 974-975: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1203-1204: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1204-1205: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1205-1206: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1460-1461: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1026-1026: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1095-1095: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1117-1117: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1136-1140: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1142-1142: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1257-1257: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1262-1262: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1267-1267: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1272-1272: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (3)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (2)

272-288: DynamicFieldsHandler wiring and custom error plumbing look good.

The handler is instantiated once and receives rpcClient, formData, getValues/setValue, setComboValues, parameters, dynamicFields, setDynamicFields, connectionName, and setCustomError. The customErrors map is correctly integrated so form rendering prefers backend‑driven errors over schema validation.

Also applies to: 418-438


160-177: DynamicField/DynamicFieldGroup definitions are consistent with DynamicFieldsHandler usage.

The local DynamicField/DynamicFieldGroup exports match how DynamicFieldsHandler and the combo/dynamic‑fields branch use value.columnName, displayName, and inputType, and they correctly expose a fields collection under a header. This is aligned with the RPC side once columnName is added there.

workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)

1923-1929: Repository access issue prevents verification of DSSFetchTablesRequest call sites.

Unable to verify the concern about all DSSFetchTablesRequest call sites passing the new driverPath field—the repository clone operation failed repeatedly. Manual verification is needed to confirm that all usages of DSSFetchTablesRequest in the codebase (particularly in _fetchTablesForConnection and related functions) properly supply the driverPath parameter, and that no existing call sites still omit this field.

@ChinthakaJ98 ChinthakaJ98 added the Checks/Run MI UI Tests Force run MI UI tests label Nov 28, 2025
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: 0

♻️ Duplicate comments (6)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (6)

735-789: Offline _getDynamicFieldValues guard for responseColumns looks good.

The added check:

if (
    operationType === QUERY_TYPES.SELECT &&
    typeof formValues[FIELD_NAMES.RESPONSE_COLUMNS] === 'string' &&
    formValues[FIELD_NAMES.RESPONSE_COLUMNS].trim() !== ''
) {
    const selectColumns = formValues[FIELD_NAMES.RESPONSE_COLUMNS]
        .split(',')
        .map(...)

eliminates the previous .split on undefined and properly scopes this to SELECT only. This addresses the earlier runtime risk.


1165-1174: Remove unused parseSuccess plumbing to simplify _handleManualQueryChange.

parseSuccess is set but never read:

let parseSuccess = true;
...
if (!match?.groups) {
    parseSuccess = false;
    ...
}
...
} catch (error: any) {
    parseSuccess = false;
    parseErrorMessage = error.message || ERROR_MESSAGES.COMPLEX_QUERY;

All error handling is already driven by early returns and the catch block; parseSuccess itself is unused.

You can safely drop parseSuccess (and related assignments) to reduce noise:

-        let parseSuccess = true;
         let parseErrorMessage = '';
@@
-            parseSuccess = false;
@@
-            parseSuccess = false;
             parseErrorMessage = error.message || ERROR_MESSAGES.COMPLEX_QUERY;

[Suggest similarly cleaning any other now-dead variables flagged by your linter here.]

Also applies to: 1373-1376


735-747: Clean up unused locals (paramManagerElement, ctrlName) to appease linters.

A few variables are declared but never used:

  • _getDynamicFieldValues offline branch:
const paramManagerElement = this.findElementByName(this.formData.elements, FIELD_NAMES.COLUMNS);
  • _handleManualQueryChange offline branch:
const paramManagerElement = this.findElementByName(this.formData.elements, FIELD_NAMES.COLUMNS);
  • _handleOptionalClause:
const ctrlName = getNameForController(fieldName);

These don’t affect behavior but will keep showing up in lint/static-analysis output. You can delete them (and any associated unused imports) without changing semantics.

Also applies to: 1305-1307, 1565-1567


193-207: Guard queryBuildConfig before calling _buildQueryFromDynamicFields for TABLE_NAME.

In the handleDynamicContent case:

const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
await this._buildQueryFromDynamicFields(
    FIELD_NAMES.TABLE_NAME,
    queryBuildConfig?.queryType,
    queryBuildConfig,
    element
);

If queryBuildConfig is undefined (or queryType is missing), you end up calling _buildQueryFromDynamicFields with an undefined operationType/config, which then falls through its switch and can write empty values back into the form.

Wrap the call in a guard so it only runs when the config is complete:

-                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig?.queryType , queryBuildConfig, element);
-                    } else {
+                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
+                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element,
+                            );
+                        } else {
+                            console.warn(
+                                `'handleDynamicContent' called for TABLE_NAME without a valid queryBuildConfig`,
+                            );
+                        }
+                    } else {
                         await this._handleDynamicContentChange(value, fieldName, element);
                     }

355-399: Fix impossible condition in setElementVisibility (updatedOffline && updatedOnline).

In the TABLE/ORDER_BY special-case:

if (isHidden) {
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.OFFLINE) { ... updatedOffline = true; }
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.ONLINE)  { ... updatedOnline = true; }
} else {
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.ONLINE)  { ... updatedOnline = true; }
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.OFFLINE) { ... updatedOffline = true; }
}
if (updatedOffline && updatedOnline) {
    return true;
}

enableCondition[0].queryType is a single value, so updatedOffline and updatedOnline are mutually exclusive; the final if (updatedOffline && updatedOnline) is never true.

If the intent is to return when either branch updated, switch to ||:

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

This makes the function correctly signal that the element was found and updated in either offline or online configuration.


595-700: Driver download logic mislabels success/failure and still tests connection with missing driver.

In _getValidConnectionDetails:

  • isDriverDownloaded starts as false, and is only set true inside the retry loop.
  • If driverPath is already present from the connection, the loop is skipped, isDriverDownloaded stays false, and you still hit:
if (!isDriverDownloaded) {
    this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY),
        "Failed to download the DB driver after 5 attempts.");
}
connection.parameters.push({ name: FIELD_NAMES.DRIVER_PATH, value: driverPath });
const testResult = await ...loadDriverAndTestConnection({ driverPath });

So for preconfigured drivers you incorrectly show a “Failed to download…” error, and when download does fail you still push driverPath (possibly undefined) and attempt loadDriverAndTestConnection, causing an extra failing RPC and confusing UX.

Consider:

-            let isDriverDownloaded = false;
+            let isDriverDownloaded = !!driverPath;
             let retryCount = 0;
             const maxRetries = 5;   
             if (!driverPath) {
                 while (!isDriverDownloaded && retryCount < maxRetries) {
@@
                     if (driverPath) {
                         isDriverDownloaded = true;
                     }
                     retryCount++;
                 }
             }
-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
+            if (!isDriverDownloaded || !driverPath) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download the DB driver after 5 attempts."
+                );
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                return undefined;
+            }
             connection.parameters.push({
                 name: FIELD_NAMES.DRIVER_PATH,
                 value: driverPath,
             });

This avoids false negatives when a driver is already configured and cleanly stops validation when download ultimately fails (also addressing the earlier suggestion about bailing out).

🧹 Nitpick comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

878-895: Consider passing the actual parentField when setting dynamic-field values.

_setFieldValue now accepts a parentField argument and uses it to locate the dynamic field:

private _setFieldValue(
    fieldName: string,
    value: string | number | boolean | null | undefined,
    isExpression: boolean = false,
    parentField: string = 'table'
): void {
    const targetElement = this.findElementByName(this.formData.elements, getNameForController(fieldName));
    const dynamicField = this.dynamicFields[parentField]?.fields.find(
        (f: DynamicField) => f.value.name === fieldName
    );

Most call sites still rely on the default 'table', including where you update dynamic fields in _handleManualQueryChange. If in future you introduce dynamic fields under other parent keys (e.g. different groups per operation), those updates will silently miss.

Not urgent for current usage, but you may want to:

  • Thread the actual parentField through call sites that are updating dynamic fields (e.g. _handleManualQueryChange when applying matchedFields and selectMatchedFields), and
  • Reserve the default 'table' only for the specific DB TABLE_NAME group.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdb1655 and 2da5aa2.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (3)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (3)

1631-1640: Await onConnectionChange inside _handleAssistanceModeChange to avoid unhandled rejections.

_handleAssistanceModeChange is async, but it calls the async onConnectionChange without await:

if (value === true) {
    this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
    this.onConnectionChange(fieldName, rpc)
} else {
    ...
}

Because handleValueChange awaits _handleAssistanceModeChange and wraps it in a try/catch, any rejection from onConnectionChange will currently bypass that error handling and surface as an unhandled promise rejection.

Change to:

-        if (value === true) {
-            this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
-            this.onConnectionChange(fieldName, rpc)
-        } else {
+        if (value === true) {
+            this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
+            await this.onConnectionChange(fieldName, rpc);
+        } else {

so connection-change failures are caught by the surrounding try/catch in handleValueChange.


1555-1607: Prepared-statement builders may ignore DB type when encoding column names.

In _buildSelectPreparedStatement, _buildInsertPreparedStatement, and _buildDeletePreparedStatement, verify whether _encodeColumnName is called with the dbType parameter. If these methods do not pass dbType, they fall back to default double-quote behavior, causing inconsistent identifier quoting across database types (e.g., MySQL backticks vs. SQL Server brackets).

Since _handleManualQueryChange resolves dbType and passes it when encoding the table name, the reconstructed prepared statements should also receive dbType to maintain consistency. Consider threading dbType as a parameter through the prepared statement builder methods and passing it from _handleManualQueryChange.


213-221: Wrap switch case bodies that declare variables in blocks (Biome noSwitchDeclarations).

Several switch cases declare const/let directly in the case clause, triggering Biome's noSwitchDeclarations rule:

  • handleValueChangecase 'buildQuery' (Line 214).
  • _buildQueryFromDynamicFieldscase QUERY_TYPES.INSERT, DELETE, SELECT, CALL (Lines 1024–1151).
  • _handleManualQueryChangecase QUERY_TYPES.SELECT | DELETE | INSERT | CALL (Lines 1247–1274).

Lexical declarations in case clauses are hoisted to the entire switch statement and can be used before initialization across multiple cases. Wrap each case body in its own block to isolate scope:

-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
+                    }
+                    break;
+                }

Apply this pattern to all INSERT/DELETE/SELECT/CALL branches in _buildQueryFromDynamicFields and _handleManualQueryChange.

Also applies to: 1023–1151, 1247–1275

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

♻️ Duplicate comments (4)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (4)

200-207: Guard _buildQueryFromDynamicFields when TABLE_NAME build config is missing.

queryBuildConfig can be undefined, but _buildQueryFromDynamicFields assumes a non‑null config and will dereference config.* internally. With the current call, a missing onDynamicFieldChange wiring will surface as a runtime crash during query build rather than a clean no‑op/log.

Consider guarding before calling:

-                case 'handleDynamicContent':
-                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig?.queryType , queryBuildConfig, element);
-                    } else {
-                        await this._handleDynamicContentChange(value, fieldName, element);
-                    }
+                case 'handleDynamicContent':
+                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
+                        const parentElement = this.findElementByName(
+                            this.formData.elements,
+                            FIELD_NAMES.TABLE_NAME,
+                        );
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element,
+                            );
+                        } else {
+                            console.warn(
+                                "'handleDynamicContent' called for TABLE_NAME without a valid buildQuery configuration",
+                            );
+                        }
+                    } else {
+                        await this._handleDynamicContentChange(value, fieldName, element);
+                    }

This keeps the TABLE_NAME fast‑path safe even if the form schema changes.


214-221: Wrap switch‑case variable declarations in blocks to satisfy Biome and avoid bleed‑through.

Biome is flagging noSwitchDeclarations for declarations like const parentField = … (Line 214), const insertColumns = … (Line 1025), and the various const/let declarations inside the operationType and QUERY_TYPES.* switches. While you currently break in each case, TS/JS still treats these declarations as shared across cases, which is what Biome is complaining about.

You can fix this by wrapping case bodies that declare variables in their own blocks. For example:

             case 'buildQuery':
-                const parentField = (element as any).parentField;
-                if (parentField) {
-                    await this.onDynamicFieldChange(value, element, parentField);
-                } else {
-                    console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                }
+                {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(
+                            `'buildQuery' called without parentField for element:`,
+                            element.name,
+                        );
+                    }
+                }
                 break;

And in _buildQueryFromDynamicFields:

-        switch (operationType) {
-            case QUERY_TYPES.INSERT:
-                const insertColumns = Object.values(activeFields);
-                if (insertColumns.length > 0) {
+        switch (operationType) {
+            case QUERY_TYPES.INSERT: {
+                const insertColumns = Object.values(activeFields);
+                if (insertColumns.length > 0) {
                     // ...
-                } else {
+                } else {
                     // ...
-                }
-
-                break;
+                }
+                break;
+            }

Apply the same { … } wrapping pattern to the other case blocks that declare const/let (INSERT/DELETE/SELECT/CALL here and in _handleManualQueryChange) so Biome’s noSwitchDeclarations errors are resolved.

Also applies to: 1023-1151, 1248-1275


361-385: updatedOffline && updatedOnline condition is unreachable; use OR or simplify.

Within the TABLE_NAME / ORDER_BY special case, updatedOffline and updatedOnline are set in mutually exclusive branches depending on enableCondition[0].queryType, so the condition:

if (updatedOffline && updatedOnline) {
    return true;
}

can never be true for a single element. The body of setElementVisibility still works due to side‑effects, but this return condition is effectively dead code and misleading.

If the intent is to signal success when either flag was updated, switch to ||:

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

Alternatively, if the return value is not used for these fields, consider removing the flags/condition entirely for clarity.


631-687: Fix driver download flow: treat existing driverPath as success and bail out cleanly on repeated failures.

The current logic around isDriverDownloaded has two problems:

  1. If driverPath is already present on the connection, isDriverDownloaded stays false, so you still set the “Failed to download…” error even though no download was attempted.
  2. When download retries all fail, you set the error but still push an undefined driverPath into connection.parameters and proceed to loadDriverAndTestConnection, causing a guaranteed failing RPC.

You can simplify by keying solely off driverPath and returning early on failure:

-            let isDriverDownloaded = false;
-            let retryCount = 0;
-            const maxRetries = 5;   
-            if (!driverPath) {
-                while (!isDriverDownloaded && retryCount < maxRetries) {
-                    const args = {
-                        groupId: groupId,
-                        artifactId: artifactId,
-                        version: version,
-                    };
-                    this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Checking DB Driver...");
-                    driverPath = await this.rpcClient.getMiDiagramRpcClient().downloadDriverForConnector(args);
-                    if (driverPath) {
-                        isDriverDownloaded = true;
-                    }
-                    retryCount++;
-                }
-            }
-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
+            if (!driverPath) {
+                let retryCount = 0;
+                const maxRetries = 5;
+                while (!driverPath && retryCount < maxRetries) {
+                    const args = { groupId, artifactId, version };
+                    this.setCustomError(
+                        getNameForController(FIELD_NAMES.CONFIG_KEY),
+                        "Checking DB Driver...",
+                    );
+                    driverPath = await this.rpcClient
+                        .getMiDiagramRpcClient()
+                        .downloadDriverForConnector(args);
+                    retryCount++;
+                }
+
+                if (!driverPath) {
+                    this.setCustomError(
+                        getNameForController(FIELD_NAMES.CONFIG_KEY),
+                        "Failed to download the DB driver after 5 attempts.",
+                    );
+                    this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                    this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                    return undefined;
+                }
+            }

After this block, driverPath is guaranteed to be truthy (either pre‑configured or downloaded), so pushing it into connection.parameters and testing the connection is safe and avoids an extra failing RPC and misleading error state.

🧹 Nitpick comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

1630-1638: Await onConnectionChange inside _handleAssistanceModeChange for predictable async flow.

_handleAssistanceModeChange is declared async and is awaited from handleValueChange, but the internal call to onConnectionChange is not awaited:

if (value === true) {
    this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
    this.onConnectionChange(fieldName, rpc)
}

This means the caller may proceed assuming the online UI/combos are updated while the connection test and table fetch are still in progress.

Consider awaiting the call so the assistance‑mode toggle behaves transactionally:

     private async _handleAssistanceModeChange(value: any, fieldName: string, rpc?: string): Promise<void> {
         if (value === true) {
             this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
-            this.onConnectionChange(fieldName, rpc)
+            await this.onConnectionChange(fieldName, rpc);
         } else {

This way, any code awaiting _handleAssistanceModeChange has a clear point at which the connection change is fully processed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2da5aa2 and d30aef0.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Comment on lines +693 to +699
} catch (error) {
console.error("Error testing DB connection:", error);
this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");
this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
return undefined;
}
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

Use getNameForController consistently when setting CONFIG_KEY error.

Everywhere else in this file you call setCustomError with getNameForController(FIELD_NAMES.CONFIG_KEY), but the RPC error path uses the raw field name:

this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");

If setCustomError expects the controller name (as in the other call sites), this path may fail to show the error on the correct field.

Align it with the rest:

-            this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");
+            this.setCustomError(
+                getNameForController(FIELD_NAMES.CONFIG_KEY),
+                ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)",
+            );

This keeps error targeting consistent across all connection‑validation failures.

🤖 Prompt for AI Agents
In
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
around lines 693 to 699, the RPC error path calls setCustomError with the raw
FIELD_NAMES.CONFIG_KEY; change this to use
getNameForController(FIELD_NAMES.CONFIG_KEY) like the other call sites so the
error targets the correct form controller. Update the setCustomError call to
pass getNameForController(FIELD_NAMES.CONFIG_KEY) and ensure
getNameForController is in scope (it already is used elsewhere in the file).

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

♻️ Duplicate comments (5)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (5)

744-789: Remove unused paramManagerElement (and keep offline path focused).

In _getDynamicFieldValues’ offline branch:

const paramManagerElement = this.findElementByName(this.formData.elements, FIELD_NAMES.COLUMNS);
if (currentQueryType === UI_MODES.OFFLINE) {
    const paramManagerValues = formValues[FIELD_NAMES.COLUMNS] || [];
    // ...
}

paramManagerElement isn’t used anywhere in this method. It looks like a leftover from an earlier iteration.

Safe to drop:

-        // Handling ParamManager (Offline Mode)
-        const paramManagerElement = this.findElementByName(this.formData.elements, FIELD_NAMES.COLUMNS);
-        if (currentQueryType === UI_MODES.OFFLINE) {
+        // Handling ParamManager (Offline Mode)
+        if (currentQueryType === UI_MODES.OFFLINE) {

Same pattern appears later in _handleManualQueryChange around the offline param‑manager update; that local is also unused and can be removed in the same way.


197-221: Guard queryBuildConfig before calling _buildQueryFromDynamicFields and address switch‑case scoping.

In the 'handleDynamicContent' branch you now pass queryBuildConfig?.queryType:

const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
await this._buildQueryFromDynamicFields(
    FIELD_NAMES.TABLE_NAME,
    queryBuildConfig?.queryType,
    queryBuildConfig,
    element
);

If queryBuildConfig is undefined (or queryType is missing), this will still call _buildQueryFromDynamicFields with an undefined operationType and config, which will later access config.* and switch on operationType. That’s a potential runtime error and also breaks the intended type contract for operationType.

Also, Biome is flagging noSwitchDeclarations here because const parentField = ... is declared directly in a case without a block.

Consider:

  • Guarding before calling _buildQueryFromDynamicFields.
  • Wrapping the case body in a block so declarations are scoped to that case.

Example fix:

-                case 'handleDynamicContent':
-                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
-                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig?.queryType , queryBuildConfig, element);
-                    } else {
-                        await this._handleDynamicContentChange(value, fieldName, element);
-                    }
-                    break;
+                case 'handleDynamicContent': {
+                    if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
+                        const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element,
+                            );
+                        } else {
+                            console.warn(
+                                "'handleDynamicContent' called but queryBuildConfig/queryType is missing for TABLE_NAME",
+                                { parentElement, fieldName: element.name },
+                            );
+                        }
+                    } else {
+                        await this._handleDynamicContentChange(value, fieldName, element);
+                    }
+                    break;
+                }

You’ll also want to apply the same { ... } wrapping pattern to other switch cases that declare const/let (e.g., in _buildQueryFromDynamicFields and _handleManualQueryChange) to satisfy Biome’s noSwitchDeclarations and keep case scopes tight.


355-399: Fix setElementVisibility flag logic: updatedOffline && updatedOnline can never be true.

In the TABLE/ORDER_BY special‑case branch:

let updatedOffline = false;
let updatedOnline = false;
// ...
if (isHidden) {
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.OFFLINE) {
        element.value.hidden = true;
        updatedOffline = true;
    }
    if (element?.enableCondition?.[0]?.queryType === UI_MODES.ONLINE) {
        element.value.hidden = false;
        updatedOnline = true;
    }
} else {
    // symmetric logic...
}
if (updatedOffline && updatedOnline) {
    return true;
}

Because each element’s enableCondition[0].queryType is either offline or online, only one of updatedOffline / updatedOnline can ever be set for a given element, so the && condition is effectively dead code.

If the goal is to return true when either variant is updated, switch to ||:

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

This also aligns with the earlier review comment on this block.


631-687: Correct driverPath / isDriverDownloaded handling and bail out when the driver is unavailable.

There are two intertwined issues in _getValidConnectionDetails:

  1. False “download failed” error when a driver path already exists.

    • isDriverDownloaded is initialized to false:
      let isDriverDownloaded = false;
      let retryCount = 0;
      const maxRetries = 5;
      if (!driverPath) {
          // retry loop...
      }
      if (!isDriverDownloaded) {
          this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY),
              "Failed to download the DB driver after 5 attempts.");
      }
    • If driverPath is already present on the connection, the retry loop is skipped and isDriverDownloaded stays false, so you always set the “after 5 attempts” error even though no download was attempted and a driver path exists.
  2. Continuing with connection test when no driver was obtained.

    • Even if downloads fail and driverPath remains falsy, the code still:
      • Pushes a DRIVER_PATH parameter with an undefined value.
      • Proceeds to loadDriverAndTestConnection, which will inevitably fail and generate a second, less clear error.

You can fix both by:

  • Treating an existing driverPath as “downloaded”.
  • Early‑returning when, after retries, there is still no driverPath, instead of proceeding to push parameters and test the connection.

Example refactor:

-            let driverPath = connection.parameters.find(p => p.name === FIELD_NAMES.DRIVER_PATH)?.value;
+            let driverPath = connection.parameters.find(p => p.name === FIELD_NAMES.DRIVER_PATH)?.value;
@@
-            let isDriverDownloaded = false;
+            let isDriverDownloaded = !!driverPath;
             let retryCount = 0;
             const maxRetries = 5;   
             if (!driverPath) {
                 while (!isDriverDownloaded && retryCount < maxRetries) {
@@
                     if (driverPath) {
                         isDriverDownloaded = true;
                     }
                     retryCount++;
                 }
             }
-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
-            connection.parameters.push({
-                name: FIELD_NAMES.DRIVER_PATH,
-                value: driverPath,
-            });
+            if (!isDriverDownloaded || !driverPath) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download the DB driver after 5 attempts.",
+                );
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                return undefined;
+            }
+
+            const existingDriverParam = connection.parameters.find(
+                (p) => p.name === FIELD_NAMES.DRIVER_PATH,
+            );
+            if (existingDriverParam) {
+                existingDriverParam.value = driverPath;
+            } else {
+                connection.parameters.push({
+                    name: FIELD_NAMES.DRIVER_PATH,
+                    value: driverPath,
+                });
+            }

This resolves the incorrect error when a driver is already configured and avoids an unnecessary failing RPC when the driver path cannot be obtained, matching the earlier “stop validation early when download fails” guidance.


693-699: Use getNameForController when setting CONFIG_KEY RPC error.

In the RPC error path you still call:

this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");

Everywhere else in the file you pass getNameForController(FIELD_NAMES.CONFIG_KEY) as the field identifier. For consistency, and to ensure the error attaches to the same controller as other CONFIG_KEY errors, align this call:

-            this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");
+            this.setCustomError(
+                getNameForController(FIELD_NAMES.CONFIG_KEY),
+                ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)",
+            );
🧹 Nitpick comments (2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)

1172-1199: Remove unused parseSuccess flag (minor cleanup).

In _handleManualQueryChange:

let parseSuccess = true;
let parseErrorMessage = '';
// ...
if (!match?.groups) {
    parseSuccess = false;
    this.setCustomError(queryFieldName, ERROR_MESSAGES.COMPLEX_QUERY);
    // ...
    return;
}

parseSuccess is assigned but never read; the control flow already returns early on failure and the catch block bases its behavior on thrown errors, not this flag.

You can drop parseSuccess entirely to reduce noise:

-        let parseSuccess = true;
         let parseErrorMessage = '';
@@
-            parseSuccess = false;
             this.setCustomError(queryFieldName, ERROR_MESSAGES.COMPLEX_QUERY);

1630-1638: Await onConnectionChange inside _handleAssistanceModeChange (optional).

Here:

if (value === true) {
    this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
    this.onConnectionChange(fieldName, rpc)
} else {
    // ...
}

onConnectionChange is async and performs RPCs and UI updates. Calling it without await means any thrown error won’t be caught by callers awaiting _handleAssistanceModeChange, and the method can appear “done” before connection state is updated.

Consider:

-        if (value === true) {
-            this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
-            this.onConnectionChange(fieldName, rpc)
-        } else {
+        if (value === true) {
+            this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.ONLINE);
+            await this.onConnectionChange(fieldName, rpc);
+        } else {

This keeps async error handling and sequencing consistent with the rest of the handler.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d30aef0 and 8f46872.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Comment on lines +1351 to +1606
const connectionData = await this._getDbConnectionDetails();
const dbType = this._getConnectionDbType(connectionData);
const encodedTableName = this._encodeColumnName(cleanTableName, dbType);

switch (operationType) {
case QUERY_TYPES.SELECT:
preparedStatement = this._buildSelectPreparedStatement(columns, encodedTableName, matchedFields, orderBy, limit, offset);
break;
case QUERY_TYPES.INSERT:
preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
break;
case QUERY_TYPES.DELETE:
preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
break;
case QUERY_TYPES.CALL:
preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields);
break;
}
this._setFieldValue(config.preparedResultField!, preparedStatement, false);
// Clear any previous error message upon successful parsing
this.setCustomError(queryFieldName, null);

} catch (error: any) {
parseErrorMessage = error.message || ERROR_MESSAGES.COMPLEX_QUERY;
console.warn(`Query parsing error: ${parseErrorMessage}`);
this.setCustomError(queryFieldName, `Query parsing error: ${parseErrorMessage}`);

// Clear dependent fields on parse error
this._clearFieldValue(config.preparedResultField!);
this._clearFieldValue(config.columnNamesField!);
}

}


/** Helper: Parses WHERE clause conditions */
private _parseWhereClause(whereClause: string | undefined, availableFields: Record<string, DynamicFieldValue>, connectionInfo: boolean): { success: boolean, fields: Record<string, DynamicFieldValue>, errorMessage?: string } {
const matchedFields: Record<string, DynamicFieldValue> = {};
if (!whereClause) {
return { success: true, fields: matchedFields };
}

// conditions within parenthesis are considered out of scope since it introduces unwanted complexicity
const conditions = whereClause.trim().split(/\s+AND\s+/i);

for (const condition of conditions) {
const conditionMatch = condition.match(REGEX.WHERE_CONDITION_PAIR);

if (!conditionMatch) {
console.warn(`${ERROR_MESSAGES.PARSE_WHERE_CONDITION}: ${condition}`);
return { success: false, fields: {}, errorMessage: `${ERROR_MESSAGES.PARSE_WHERE_CONDITION}: "${condition}"` };
}

// Extract parts: column, operator, valueStr
const [, rawColumn, , rawValueStr] = conditionMatch;
//const columnName = rawColumn.replace(/[^a-zA-Z0-9]/g, '').trim();
const columnName = rawColumn.trim().replace(/[`'"\[\]]/g, ''); // Remove quotes/brackets

const valueStr = rawValueStr.trim();

// if connection info is false build a dummy field
const dynamicField = connectionInfo ? availableFields[columnName] : {
name: columnName,
displayName: columnName,
value: valueStr,
isExpression: false,
columnType: "VARCHAR",
helpTip: ''
};

if (!dynamicField) {
console.warn(`Dynamic field definition not found for WHERE column: ${columnName}`);
return { success: false, fields: {}, errorMessage: `Field "${rawColumn}" not found for this table.` };
}

// Remove leading/trailing single or double quotes
const fieldValue = valueStr.replace(/^['"]|['"]$/g, '');

matchedFields[columnName] = dynamicField;
matchedFields[columnName].value = fieldValue;
const isExpression = REGEX.SYNAPSE_EXPRESSION.test(fieldValue);

matchedFields[columnName] = { ...dynamicField, value: fieldValue, isExpression: isExpression };
}
return { success: true, fields: matchedFields };
}

/** Helper: Parses INSERT columns and values */
private _parseInsertValues(columnsStr: string | undefined, valuesStr: string | undefined, availableFields: Record<string, DynamicFieldValue>, connectionInfo: boolean): { success: boolean, fields: Record<string, DynamicFieldValue>, errorMessage?: string } {
const matchedFields: Record<string, DynamicFieldValue> = {};

if (!columnsStr || !valuesStr) {
return { success: false, fields: {}, errorMessage: ERROR_MESSAGES.INSERT_MISMATCH + " (missing columns or values)" };
}

// this does not support column names such as `Random, "``Name,' , .````test`
// this is an acceptable limitation since this only occurs when user manually enters the query

const columns = columnsStr.split(',').map(col => col.trim().replace(/[`'"\[\]]/g, ''));
const values = valuesStr.split(',').map(val => val.trim().replace(/^['"]|['"]$/g, '')); // Remove quotes

if (columns.length !== values.length || columns.length === 0) {
console.warn(ERROR_MESSAGES.INSERT_MISMATCH);
return { success: false, fields: {}, errorMessage: ERROR_MESSAGES.INSERT_MISMATCH };
}
for (let i = 0; i < columns.length; i++) {
const columnName = columns[i];
const valueStr = values[i];
const dynamicField = connectionInfo ? availableFields[columnName] : {
name: columnName,
displayName: columnName,
value: valueStr,
isExpression: false,
columnType: "VARCHAR",
helpTip: ''
};
if (!dynamicField) {
console.warn(`Dynamic field definition not found for INSERT column: ${columnName}`);
return { success: false, fields: {}, errorMessage: `Field "${columnName}" not found for this table.` };
}

// Detect if value is expression or literal
const isSynapseExpr = REGEX.SYNAPSE_EXPRESSION.test(valueStr);
const isLegacyExpr = REGEX.EXPRESSION.test(valueStr);
const isExpression = isSynapseExpr || isLegacyExpr;

matchedFields[columnName] = { ...dynamicField, value: valueStr, isExpression: isExpression };
}

return { success: true, fields: matchedFields };
}

/** Helper: Parses CALL parameters */
private _parseCallParams(valuesStr: string | undefined, availableFields: Record<string, DynamicFieldValue>, connectionInfo: boolean): { success: boolean, fields: Record<string, DynamicFieldValue>, errorMessage?: string } {
const matchedFields: Record<string, DynamicFieldValue> = {};
if (!valuesStr) {
return { success: false, fields: {}, errorMessage: "No parameters provided." };
}
const values = valuesStr.split(',').map(val => val.trim().replace(/^['"]|['"]$/g, ''));

// there should be values matching all of the dynamic fields
// iterate over availbale fields with counter
if (!connectionInfo) {
// For offline mode, iterate over values array
for (let i = 0; i < values.length; i++) {
const valueStr = values[i];
const fieldName = `param${i + 1}`;

const dynamicField = {
name: fieldName,
displayName: fieldName,
value: valueStr,
isExpression: false,
columnType: "VARCHAR",
helpTip: ''
};

const isSynapseExpr = REGEX.SYNAPSE_EXPRESSION.test(valueStr);
const isLegacyExpr = REGEX.EXPRESSION.test(valueStr);
const isExpression = isSynapseExpr || isLegacyExpr;

matchedFields[fieldName] = { ...dynamicField, value: valueStr, isExpression: isExpression };
}
} else {
// For online mode, iterate over available fields
let counter = 0;
for (const fieldName in availableFields) {
const dynamicField = availableFields[fieldName];

if (!dynamicField) {
console.warn(`Dynamic field definition not found for CALL column: ${fieldName}`);
return { success: false, fields: {}, errorMessage: `Field "${fieldName}" not found for this table.` };
}

// Check if value exists for this field
if (counter >= values.length) {
console.warn(`Not enough values provided for CALL parameters.`);
return { success: false, fields: {}, errorMessage: `Not enough values provided for CALL parameters.` };
}

const valueStr = values[counter];
const isSynapseExpr = REGEX.SYNAPSE_EXPRESSION.test(valueStr);
const isLegacyExpr = REGEX.EXPRESSION.test(valueStr);
const isExpression = isSynapseExpr || isLegacyExpr;

matchedFields[fieldName] = { ...dynamicField, value: valueStr, isExpression: isExpression };
counter++;
}
}

return { success: true, fields: matchedFields };
}
/** Helper: Parses SELECT columns */
private _parseSelectColumns(columnsStr: string | undefined, availableFields: Record<string, DynamicFieldValue>, connectionInfo: boolean): { success: boolean, fields: Record<string, DynamicFieldValue>, errorMessage?: string } {
if (!columnsStr) return { success: false, fields: {}, errorMessage: "No columns specified for SELECT." };
// Handle SELECT * case
// if (columnsStr.trim() === '*') {
// return { success: true, fields: availableFields };
// }
const columns = columnsStr.split(',').map(col => col.trim());
const matchedFields: Record<string, DynamicFieldValue> = {};
for (const col of columns) {
const field = Object.values(availableFields).find(f => f.columnName && f.columnName === col);
if (!field) {
console.warn(`No matching field found for column: ${col}`);
return { success: false, fields: {}, errorMessage: `No matching field found for column: ${col}` };
}
matchedFields[col] = field;
}
return { success: true, fields: matchedFields };
}

/** Helper: Handles optional clauses like ORDER BY, LIMIT, OFFSET during parsing */
private _handleOptionalClause(clauseValue: string | undefined, fieldName: string, checkExpression: boolean = false, isCombo: boolean = false) {

if (clauseValue) {
const cleanValue = clauseValue.replace(/[`"\[\]]/g, '');
const isExpression = checkExpression && REGEX.SYNAPSE_EXPRESSION.test(clauseValue);
this._setFieldValue(fieldName, cleanValue, isExpression);
} else {
this._clearFieldValue(fieldName); // Clear the field if clause not present
}
}

/** Helper: Builds a prepared statement string for SELECT */
private _buildSelectPreparedStatement(columnsStr: string, tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>, orderBy?: string, limit?: string, offset?: string): string {

const selectCols = columnsStr?.trim() || '*';
let statement = `SELECT ${selectCols} FROM ${tableName}`;

const wherePlaceholders = Object.keys(matchedWhereFields)
.map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`) // Use displayName for column name
.join(' AND ');

if (wherePlaceholders) {
statement += ` WHERE ${wherePlaceholders}`;
}

if (orderBy) {
const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
statement += ` ORDER BY ${cleanOrderBy}`;
}

if (limit) { statement += ` LIMIT ?`; }
if (offset) { statement += ` OFFSET ?`; }

return statement;
}

/** Helper: Builds a prepared statement string for INSERT */
private _buildInsertPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
const columns = Object.values(matchedFields).map(f => this._encodeColumnName(f.displayName)).join(', ');
const placeholders = Object.keys(matchedFields).map(() => '?').join(', ');
if (!columns) return `INSERT INTO ${tableName} DEFAULT VALUES`; // Handle case with no columns
return `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders})`;
}
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

Pass dbType into prepared‑statement builders to keep identifier quoting consistent.

For manually entered queries parsed in _handleManualQueryChange, you correctly derive dbType and an encodedTableName:

const connectionData = await this._getDbConnectionDetails();
const dbType = this._getConnectionDbType(connectionData);
const encodedTableName = this._encodeColumnName(cleanTableName, dbType);

switch (operationType) {
    case QUERY_TYPES.SELECT:
        preparedStatement = this._buildSelectPreparedStatement(
            columns,
            encodedTableName,
            matchedFields,
            orderBy,
            limit,
            offset,
        );
        break;
    case QUERY_TYPES.INSERT:
        preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
        break;
    case QUERY_TYPES.DELETE:
        preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
        break;
    // ...
}

But the helper methods themselves call _encodeColumnName without passing dbType, so they fall back to the "default" quoting rule (double quotes) regardless of the actual database. That’s inconsistent with _buildQueryFromDynamicFields, which always passes dbType, and can be wrong for engines like MySQL that expect backticks by default.

Example: for MySQL you can end up with:

INSERT INTO `my_table` ("id", "name") VALUES (?, ?)

instead of using backticks for columns as you do elsewhere.

Consider threading dbType through these helpers:

-    private _buildSelectPreparedStatement(
-        columnsStr: string,
-        tableName: string,
-        matchedWhereFields: Record<string, DynamicFieldValue>,
-        orderBy?: string,
-        limit?: string,
-        offset?: string,
-    ): string {
+    private _buildSelectPreparedStatement(
+        columnsStr: string,
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+        orderBy?: string,
+        limit?: string,
+        offset?: string,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');
@@
-        if (orderBy) {
-            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
-            statement += ` ORDER BY ${cleanOrderBy}`;
-        }
+        if (orderBy) {
+            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
+            const encodedOrderBy = this._encodeColumnName(cleanOrderBy, dbType);
+            statement += ` ORDER BY ${encodedOrderBy}`;
+        }
-    private _buildInsertPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
-        const columns = Object.values(matchedFields).map(f => this._encodeColumnName(f.displayName)).join(', ');
+    private _buildInsertPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
+        const columns = Object.values(matchedFields)
+            .map(f => this._encodeColumnName(f.displayName, dbType))
+            .join(', ');
-    private _buildDeletePreparedStatement(tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>): string {
+    private _buildDeletePreparedStatement(
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');

And update the call‑sites in _handleManualQueryChange:

-                case QUERY_TYPES.SELECT:
-                    preparedStatement = this._buildSelectPreparedStatement(columns, encodedTableName, matchedFields, orderBy, limit, offset);
+                case QUERY_TYPES.SELECT:
+                    preparedStatement = this._buildSelectPreparedStatement(
+                        columns,
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                        orderBy,
+                        limit,
+                        offset,
+                    );
                     break;
                 case QUERY_TYPES.INSERT:
-                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields, dbType);
                     break;
                 case QUERY_TYPES.DELETE:
-                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields, dbType);
                     break;

This keeps quoting behavior consistent between auto‑built and parsed queries and avoids DB‑specific syntax issues.

🤖 Prompt for AI Agents
In
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
around lines 1351 to 1606, helper methods that build prepared statements call
this._encodeColumnName without the dbType, causing inconsistent identifier
quoting; change the signatures of _buildSelectPreparedStatement,
_buildInsertPreparedStatement, _buildDeletePreparedStatement and
_buildCallPreparedStatement to accept a dbType parameter, update every internal
call to this._encodeColumnName to pass that dbType, and update the call sites in
_handleManualQueryChange to forward the dbType (derived from
_getConnectionDbType) when invoking these builders so quoting is consistent with
the connection.

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

♻️ Duplicate comments (5)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (2)

893-918: Fix React Hooks rule violations in helper functions and switch cases.

useEffect is being called from helpers invoked like plain functions (ParamManagerComponent, ExpressionFieldComponent, FormExpressionFieldComponent) and directly inside renderFormElement switch cases (combo*, connection). This breaks the Rules of Hooks and will cause Biome errors and potentially inconsistent React state.

Refactor these to proper child components/custom hooks and render them via JSX instead of calling them as plain functions, and move the switch‑case effects into those components.

Example for ParamManagerComponent and its case:

-function ParamManagerComponent(element: Element, isRequired: boolean, helpTipElement: JSX.Element, field: any) {
-    useEffect(() => {
-        handleValueChange(field.value, element.name.toString(), element);
-    }, []); // run only on mount
-    return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
-        ...
-    </ComponentCard>;
-}
+const ParamManagerComponent: React.FC<{
+    element: Element;
+    isRequired: boolean;
+    helpTipElement: JSX.Element;
+    field: any;
+}> = ({ element, isRequired, helpTipElement, field }) => {
+    useEffect(() => {
+        void handleValueChange(field.value, element.name.toString(), element);
+    }, [element.name, field.value]);
+
+    return (
+        <ComponentCard id={`parameterManager-${element.name}`} sx={cardStyle} disbaleHoverEffect>
+            ...
+        </ComponentCard>
+    );
+};
@@
-        case 'ParamManager': {
-            return (
-                ParamManagerComponent(element, isRequired, helpTipElement, field)
-            );
-        }
+        case 'ParamManager': {
+            return (
+                <ParamManagerComponent
+                    element={element}
+                    isRequired={isRequired}
+                    helpTipElement={helpTipElement}
+                    field={field}
+                />
+            );
+        }

Apply the same pattern to ExpressionFieldComponent and FormExpressionFieldComponent (define them as components and render via <ExpressionFieldComponent ... /> / <FormExpressionFieldComponent ... />), and extract the 'combo*' and 'connection' branches into small child components that own their useEffect calls instead of calling hooks inside renderFormElement.

This will resolve the Biome useHookAtTopLevel / noSwitchDeclarations errors and restore hook ordering guarantees.

Also applies to: 920-968, 970-1031, 1202-1207, 1459-1461


287-295: Avoid saving driver config from stale driverConfig state.

handleDriverTypeChange mutates driverConfig via setDriverConfig and then immediately calls saveDriverConfig, which reads driverConfig from state. Because setState is async, validations and saves can run against the previous config. renderDriverConfiguration also passes onSave={saveDriverConfig} so child components can invoke saves with whatever state happened to be current when the closure was created.

Refactor to derive a nextConfig object synchronously, update state from it, and pass that same object into saveDriverConfig (and from child components):

-    const handleDriverTypeChange = async (driverType: string, element: Element) => {
+    const handleDriverTypeChange = async (driverType: string, element: Element) => {
         const option = DRIVER_OPTIONS.find(opt => opt.label === driverType);
         if (!option) return;
 
-        setDriverConfig(prev => ({ ...prev, type: option.configType }));
-        setDriverError(null);
+        setDriverError(null);
+        let nextConfig: DriverConfig = {
+            ...driverConfig,
+            type: option.configType,
+        };
@@
-            if (option.configType === 'custom') {
-                if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: existingDriverPath
-                    }));
-                }
-            } else {
-                if (existingGroupId && existingArtifactId && existingVersion) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: null
-                    }));
-                } else if (existingDriverPath) {
-                    setValue("driverPath", null);
-                }
-            }
-            saveDriverConfig();
+            if (option.configType === 'custom') {
+                if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: existingDriverPath,
+                    };
+                }
+            } else {
+                if (existingGroupId && existingArtifactId && existingVersion) {
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: null,
+                    };
+                } else if (existingDriverPath) {
+                    setValue("driverPath", null);
+                }
+            }
+            setDriverConfig(nextConfig);
+            await saveDriverConfig(nextConfig);
         }
     };
@@
-    const saveDriverConfig = async () => {
+    const saveDriverConfig = async (configOverride?: DriverConfig) => {
+        const cfg = configOverride ?? driverConfig;
         try {
             // Validate required fields based on driver type
-            if (driverConfig.type === 'maven' &&
-                (!driverConfig.groupId || !driverConfig.artifactId || !driverConfig.version)) {
+            if (cfg.type === 'maven' &&
+                (!cfg.groupId || !cfg.artifactId || !cfg.version)) {
                 setDriverError("All Maven coordinates are required");
                 return;
             }
@@
-            if (driverConfig.type === 'maven') {
+            if (cfg.type === 'maven') {
                 const validDriver = await rpcClient.getMiDiagramRpcClient().downloadDriverForConnector({
-                    groupId: driverConfig.groupId,
-                    artifactId: driverConfig.artifactId,
-                    version: driverConfig.version
+                    groupId: cfg.groupId,
+                    artifactId: cfg.artifactId,
+                    version: cfg.version,
                 });
@@
-            setValue("groupId", driverConfig.groupId);
-            setValue("artifactId", driverConfig.artifactId);
-            setValue("version", driverConfig.version);
-            if (driverConfig.type === 'custom') {
-                setValue("driverPath", driverConfig.driverPath);
+            setValue("groupId", cfg.groupId);
+            setValue("artifactId", cfg.artifactId);
+            setValue("version", cfg.version);
+            if (cfg.type === 'custom') {
+                setValue("driverPath", cfg.driverPath);
             }

And in renderDriverConfiguration pass the current config through the onSave closures:

-                    <DefaultDriverConfig
+                    <DefaultDriverConfig
                         config={driverConfig}
                         isReadOnly={true}
                         onConfigChange={setDriverConfig}
-                        onSave={saveDriverConfig}
+                        onSave={() => saveDriverConfig(driverConfig)}
                     />
@@
-                    <CustomDriverConfig
+                    <CustomDriverConfig
                         config={driverConfig}
                         onConfigChange={setDriverConfig}
-                        onSave={saveDriverConfig}
+                        onSave={() => saveDriverConfig(driverConfig)}
@@
-                    <MavenDriverConfig
+                    <MavenDriverConfig
                         config={driverConfig}
                         onConfigChange={setDriverConfig}
-                        onSave={saveDriverConfig}
+                        onSave={() => saveDriverConfig(driverConfig)}

This keeps validation, downloads, and form persistence consistent with the actual config the user sees.

Also applies to: 296-327, 483-527, 548-587, 645-684, 1608-1650

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (3)

355-399: Correct unreachable updatedOffline && updatedOnline condition in setElementVisibility.

For TABLE_NAME / ORDER_BY, updatedOffline and updatedOnline are set in mutually exclusive branches based on enableCondition[0].queryType, so the if (updatedOffline && updatedOnline) can never be true and the function always falls through false for those names.

Use OR to indicate “found and updated in at least one mode”:

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

This makes the return value meaningful if any of the special‑cased elements were toggled.


595-700: Stop connection validation when DB driver download ultimately fails.

After the bounded retry in _getValidConnectionDetails, you still append a DRIVER_PATH parameter and call loadDriverAndTestConnection even when isDriverDownloaded is false and driverPath is falsy. That produces an extra failing RPC and a less clear UX.

Short‑circuit when the driver cannot be obtained:

-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
-            connection.parameters.push({
+            if (!isDriverDownloaded) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download the DB driver after 5 attempts."
+                );
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                return undefined;
+            }
+
+            connection.parameters.push({
                 name: FIELD_NAMES.DRIVER_PATH,
                 value: driverPath,
             });
@@
-            const testResult = await this.rpcClient.getMiDiagramRpcClient().loadDriverAndTestConnection(testArgs);
+            const testResult = await this.rpcClient.getMiDiagramRpcClient().loadDriverAndTestConnection(testArgs);

This avoids testing a connection with a known‑bad/undefined driver and cleanly falls back to offline mode.


913-1151: Pass dbType into prepared‑statement builders to keep identifier quoting consistent.

For auto‑built queries in _buildQueryFromDynamicFields you already pass dbType into _encodeColumnName, but for manually entered queries parsed in _handleManualQueryChange the prepared‑statement helpers don’t receive dbType and call _encodeColumnName without it. That can generate incorrect quoting for non‑default dialects (e.g., MySQL prefers backticks), producing SQL that parses differently from the dynamic‑field path.

Thread dbType through the builders and use it for column/order‑by quoting:

-    private _buildSelectPreparedStatement(columnsStr: string, tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>, orderBy?: string, limit?: string, offset?: string): string {
+    private _buildSelectPreparedStatement(
+        columnsStr: string,
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+        orderBy?: string,
+        limit?: string,
+        offset?: string,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');
@@
-        if (orderBy) {
-            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
-            statement += ` ORDER BY ${cleanOrderBy}`;
-        }
+        if (orderBy) {
+            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
+            const encodedOrderBy = this._encodeColumnName(cleanOrderBy, dbType);
+            statement += ` ORDER BY ${encodedOrderBy}`;
+        }
-    private _buildInsertPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
-        const columns = Object.values(matchedFields).map(f => this._encodeColumnName(f.displayName)).join(', ');
+    private _buildInsertPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
+        const columns = Object.values(matchedFields)
+            .map(f => this._encodeColumnName(f.displayName, dbType))
+            .join(', ');
-    private _buildDeletePreparedStatement(tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>): string {
+    private _buildDeletePreparedStatement(
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');
-    private _buildCallPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
+    private _buildCallPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
         const placeholders = Object.keys(matchedFields).map(() => '?').join(', ');
-        return `CALL ${tableName}(${placeholders})`;
+        return `CALL ${tableName}(${placeholders})`;
     }

And in _handleManualQueryChange:

-            const connectionData = await this._getDbConnectionDetails();
-            const dbType = this._getConnectionDbType(connectionData);
+            const connectionData = await this._getDbConnectionDetails();
+            const dbType = this._getConnectionDbType(connectionData);
@@
-                case QUERY_TYPES.SELECT:
-                    preparedStatement = this._buildSelectPreparedStatement(columns, encodedTableName, matchedFields, orderBy, limit, offset);
+                case QUERY_TYPES.SELECT:
+                    preparedStatement = this._buildSelectPreparedStatement(
+                        columns,
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                        orderBy,
+                        limit,
+                        offset,
+                    );
                     break;
                 case QUERY_TYPES.INSERT:
-                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields, dbType);
                     break;
                 case QUERY_TYPES.DELETE:
-                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields, dbType);
                     break;
                 case QUERY_TYPES.CALL:
-                    preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields, dbType);
                     break;

This aligns manual‑query parsing with the db‑aware encoding already used in _buildQueryFromDynamicFields.

Also applies to: 1575-1628, 1355-1368

🧹 Nitpick comments (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

271-281: Consider removing unused local state helpers to reduce noise.

setElements/updateElements and isDriverConfigLoading are currently written but not used elsewhere in this component. Unless you plan to wire them in a follow‑up (e.g., passing updateElements into DynamicFieldsHandler), consider deleting them to keep the FormGenerator surface lean.

-    const [, setElements] = useState(formData.elements);
-    const updateElements = (newElements: any) => {
-        setElements(newElements);
-        formData.elements = newElements;
-    };
@@
-    const [isDriverConfigLoading, setIsDriverConfigLoading] = useState(true);
+    // Remove isDriverConfigLoading until you actually surface a loading indicator

If these are placeholders for upcoming behavior, a short comment/TODO would help clarify the intent.

Also applies to: 287-295

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34c2dae and 0074b26.

📒 Files selected for processing (2)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T22:16:28.380Z
Learnt from: dan-niles
Repo: wso2/vscode-extensions PR: 980
File: workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx:384-412
Timestamp: 2025-11-24T22:16:28.380Z
Learning: In the RichTextTemplateEditor component (workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx), token fetching on external `value` prop changes is intentionally disabled. Users cannot edit both the minimized and expanded editors simultaneously, so tokens only need to be generated based on user typing in the expanded editor view, not on external prop updates.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)
  • DynamicFieldsHandler (162-1640)
workspaces/mi/mi-diagram/src/components/Form/DBConnector/DriverConfiguration.tsx (5)
  • DriverConfig (24-30)
  • DRIVER_OPTIONS (50-69)
  • DefaultDriverConfig (83-127)
  • CustomDriverConfig (129-212)
  • MavenDriverConfig (214-276)
workspaces/mi/mi-diagram/src/components/Form/RadioButtonGroup/GenericRadioGroup.tsx (1)
  • GenericRadioGroup (23-59)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 894-895: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 922-923: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 973-974: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1202-1203: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1203-1204: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1204-1205: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1459-1460: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

197-221: Wrap switch case bodies that declare variables in blocks (Biome noSwitchDeclarations).

Several switch cases declare const/let directly in the case body without block scoping. Biome's noSwitchDeclarations rule flags these because variable bindings are visible for the entire switch block but only initialized when their case runs, creating temporal-dead-zone issues and enabling unintended cross-case access.

The buildQuery case and cases in _buildQueryFromDynamicFields (QUERY_TYPES.INSERT, DELETE, SELECT, CALL) and _handleManualQueryChange need wrapping in { ... } blocks:

-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
+                    }
+                    break;
+                }

Apply this pattern to all affected cases at lines 1023-1151 and 1247-1275.

Comment on lines +403 to +449
/** Handles the specific logic for 'handleDynamicContent' onChange */
private async _handleDynamicContentChange(value: any, fieldName: string, element: Element): Promise<void> {
const newFields = await this.fetchDynamicFields(element, value, fieldName);
// Update dynamic fields state only if fetch was successful (newFields is not null)
if (newFields !== null && newFields !== undefined) {
//let augmentedFields = newFields;
if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.SELECT) {
const augmentedFields: typeof newFields = [];
newFields.forEach((field: any) => {
// Original field
augmentedFields.push(field);
// Extra checkbox field for "Include in Response"
augmentedFields.push({
...field,
value: {
...field.value,
name: `${field.value.name}_include`,
columnName: field.value.displayName,
helpTip: `Check to include the column '${field.value.displayName}' in the SELECT response.`,
displayName: field.value.displayName.charAt(0).toUpperCase() + field.value.displayName.slice(1),
inputType: "checkbox",
defaultValue: false,
parentField: fieldName,
},
});
});
this.setDynamicFields({
...this.dynamicFields, [fieldName]: {
header: "Table Columns",
fields: augmentedFields,
},
});
} else if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.CALL) {
this.setDynamicFields({
...this.dynamicFields, [fieldName]: {
header: "Parameters",
fields: newFields,
},
});
} else {
this.setDynamicFields({
...this.dynamicFields, [fieldName]: {
header: "Table Columns",
fields: newFields,
},
});
}
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:

cd workspaces/mi/mi-diagram && head -n 450 src/components/Form/DynamicFields/DynamicFieldsHandler.tsx | tail -n 50

Repository: wso2/vscode-extensions

Length of output: 188


🏁 Script executed:

cd workspaces/mi/mi-diagram && cat -n src/components/Form/DynamicFields/DynamicFieldsHandler.tsx | sed -n '400,455p'

Repository: wso2/vscode-extensions

Length of output: 188


🌐 Web query:

TypeScript optional chaining semantics short-circuit undefined property access

💡 Result:

Short answer

  • The optional chaining operator (?.) checks only the value immediately to its left; if that value is null or undefined the whole optional access short‑circuits and yields undefined — and any expressions to the right (including side effects like x++) are not evaluated. [1][2]

Important details

  • It only tests for nullish (null or undefined), not other falsy values (0, "", false). [1][2]
  • Short‑circuiting applies along a continuous chain (a?.b?.c). If you break the chain with parentheses (a?.b).c, the parenthesized result may be undefined and the subsequent access will still run and can throw. [1][2]
  • Optional call and element access follow the same rule: fn?.() and arr?.[i] evaluate the left operand first and return undefined if it’s nullish; they do not prevent evaluation of unrelated expressions outside the optional chain. [1][2]

Minimal examples

  • Side effect skipped:
    const o = null; let i = 0; o?.[i++]; // i remains 0. [1]
  • Grouping breaks short‑circuit:
    (a?.b).c // can throw if a is nullish because .c runs on undefined. [1][2]

Sources

  • MDN: Optional chaining. [1]
  • TypeScript release notes (Optional Chaining). [2]

Fix optional‑chaining on onDynamicFieldChange to avoid runtime TypeError.

In _handleDynamicContentChange, the checks for queryType use:

element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType

If onDynamicFieldChange is undefined, the .params access will still throw despite the earlier ?.. This breaks when onValueChange is configured without onDynamicFieldChange.

Guard the access fully with ?.:

-            if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.SELECT) {
+            if (element.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0]?.queryType === QUERY_TYPES.SELECT) {
@@
-            } else if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.CALL) {
+            } else if (element.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0]?.queryType === QUERY_TYPES.CALL) {

This keeps the handler safe when dynamic‑field wiring is partially configured.

🤖 Prompt for AI Agents
In
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
around lines 403 to 449, the code accesses
element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType
which will throw if onDynamicFieldChange is undefined because .params is
accessed without optional chaining; update those checks to fully guard the chain
(e.g.
element.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0]?.queryType)
in both the SELECT and CALL branches (and any other similar occurrences in this
block) so the handler safely handles partially configured onValueChange wiring
and avoids runtime TypeError.

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

Caution

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

⚠️ Outside diff range comments (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

1198-1251: CRITICAL: Remove hook from switch case and wrap declarations in block.

Three issues:

  1. CRITICAL: useEffect at lines 1200-1202 is called conditionally inside a switch case, violating the Rules of Hooks.

  2. MAJOR: Variable declarations items and allowItemCreate at lines 1198-1199 can leak to other switch cases.

  3. MINOR: The useEffect dependency array is missing name, which the past review flagged (line 1202).

Apply these fixes:

  1. Move the hook to component top level or extract into a small component:
 case 'comboOrExpression':
 case 'combo':
+    {
     const items = element.inputType === 'booleanOrExpression' ? ["true", "false"] : (props.comboValuesMap?.[name] || element.comboValues);
     const allowItemCreate = element.inputType === 'comboOrExpression';
-    useEffect(() => {
-        handleValueChange(field.value, name, element);
-    }, [props.comboValuesMap?.[name]]); // run on mount and on props.comboValuesMap
     return (
         <>
             <AutoComplete
                 name={name}
                 label={element.displayName}
                 labelAdornment={helpTipElement}
                 errorMsg={errorMsg}
                 items={items}
                 value={field.value}
                 onValueChange={(e: any) => {
                     field.onChange(e);
                     handleValueChange(e, name, element);
                 }}
                 required={isRequired}
                 allowItemCreate={allowItemCreate}
             />

             {dynamicFields[name]?.fields?.length > 0 && (
                 <>
                     {/* ... dynamic fields rendering ... */}
                 </>
             )}
         </>
     );
+    }
  1. For the value change on mount, consider calling handleValueChange directly when items change, or use a top-level useEffect with proper conditions:
useEffect(() => {
    const items = props.comboValuesMap?.[someFieldName] || [];
    if (items.length > 0) {
        const fieldValue = getValues(someFieldName);
        handleValueChange(fieldValue, someFieldName, /* element */);
    }
}, [props.comboValuesMap]);

Alternatively, extract the combo rendering into a separate component that owns its useEffect.

Based on learnings, past review and Biome flag these violations.

♻️ Duplicate comments (7)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (7)

889-914: CRITICAL: Fix React hooks rule violation in ParamManagerComponent.

ParamManagerComponent contains a useEffect hook (lines 890-892) but is invoked as a plain function at line 1367 rather than rendered as a React component. This violates the Rules of Hooks and can cause state inconsistencies.

Convert it to a proper component that takes props and render it via JSX:

-function ParamManagerComponent(element: Element, isRequired: boolean, helpTipElement: JSX.Element, field: any) {
+function ParamManagerComponent(props: {
+    element: Element;
+    isRequired: boolean;
+    helpTipElement: JSX.Element;
+    field: any;
+}) {
+    const { element, isRequired, helpTipElement, field } = props;
     useEffect(() => {
         handleValueChange(field.value, element.name.toString(), element);
     }, []); // run only on mount
     return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
         {/* ... rest unchanged ... */}
     </ComponentCard>;
 }

Then at line 1367:

 case 'ParamManager': {
     return (
-        ParamManagerComponent(element, isRequired, helpTipElement, field)
+        <ParamManagerComponent
+            element={element}
+            isRequired={isRequired}
+            helpTipElement={helpTipElement}
+            field={field}
+        />
     );
 }

Apply the same pattern to ExpressionFieldComponent and FormExpressionFieldComponent. See past review comment (lines 271-282) for complete fix.

Based on learnings, past review comment provides detailed solution.


916-964: CRITICAL: Fix React hooks rule violation in ExpressionFieldComponent.

ExpressionFieldComponent contains a useEffect hook (lines 918-921) but is invoked as a plain function at line 1185. This violates the Rules of Hooks.

Convert to a proper component with props object and render via JSX as described in the past review comment (lines 271-282). The same pattern applies as ParamManagerComponent.

Based on learnings, past review comment flags this violation.


966-1027: CRITICAL: Fix React hooks rule violation in FormExpressionFieldComponent.

FormExpressionFieldComponent contains a useEffect hook (lines 969-971) but is invoked as a plain function at line 1194. This violates the Rules of Hooks.

Convert to a proper component as described in the past review comment (lines 271-282).

However, the custom error handling integration at lines 968 and 991 is well done! The prioritization of custom errors over standard errors is correct.

Based on learnings, past review comment flags this violation.


544-582: Update saveDriverConfig to accept config parameter.

This function reads from the driverConfig state, which can be stale when called immediately after setDriverConfig. Update the signature to accept an optional config parameter as suggested in the past review (lines 271-283).

Apply this diff:

-    const saveDriverConfig = async () => {
+    const saveDriverConfig = async (configOverride?: DriverConfig) => {
+        const cfg = configOverride ?? driverConfig;
         try {
             // Validate required fields based on driver type
-            if (driverConfig.type === 'maven' &&
-                (!driverConfig.groupId || !driverConfig.artifactId || !driverConfig.version)) {
+            if (cfg.type === 'maven' &&
+                (!cfg.groupId || !cfg.artifactId || !cfg.version)) {
                 setDriverError("All Maven coordinates are required");
                 return;
             }
             //check if valid coordinates
-            if (driverConfig.type === 'maven') {
+            if (cfg.type === 'maven') {
                 const validDriver = await rpcClient.getMiDiagramRpcClient().downloadDriverForConnector({
-                    groupId: driverConfig.groupId,
-                    artifactId: driverConfig.artifactId,
-                    version: driverConfig.version
+                    groupId: cfg.groupId,
+                    artifactId: cfg.artifactId,
+                    version: cfg.version
                 });
                 // ... rest of validation
             }
 
             // Save to form values
-            setValue("groupId", driverConfig.groupId);
-            setValue("artifactId", driverConfig.artifactId);
-            setValue("version", driverConfig.version);
-            if (driverConfig.type === 'custom') {
-                setValue("driverPath", driverConfig.driverPath);
+            setValue("groupId", cfg.groupId);
+            setValue("artifactId", cfg.artifactId);
+            setValue("version", cfg.version);
+            if (cfg.type === 'custom') {
+                setValue("driverPath", cfg.driverPath);
             }

Based on learnings, past review comment provides detailed fix.


1455-1457: CRITICAL: Remove hook from switch case.

useEffect at lines 1455-1457 is called conditionally inside the 'connection' switch case, violating the Rules of Hooks.

Move this logic to component top level or extract into a separate component. The past review comment (line 1457) also notes missing dependencies: field.value, name, and element should be included in the dependency array if kept as a useEffect.

Wrap the case in a block and remove the hook as shown in the previous comment for the combo case.

Based on learnings, past review and Biome flag this violation.


290-290: Remove unused state variable.

The isDriverConfigLoading variable is declared but never used to control rendering or logic flow.

Apply this diff:

-    const [isDriverConfigLoading, setIsDriverConfigLoading] = useState(true);
+    const [, setIsDriverConfigLoading] = useState(true);

Alternatively, if you intend to use this for loading states, integrate it into the driver configuration rendering logic.


479-523: Fix stale state usage and logic error.

Two issues:

  1. Stale state issue (MAJOR): handleDriverTypeChange calls setDriverConfig multiple times (lines 483, 500-506, 510-516) then immediately calls saveDriverConfig() at line 521. Since setDriverConfig is asynchronous, saveDriverConfig will read the previous driverConfig state, not the updated values. This can cause validation errors or incorrect values being saved.

  2. Logic error (MINOR): Line 517 has } else if (existingDriverPath) which appears correct, but the past review flagged inconsistent spacing. Verify the logic flow is as intended.

For the stale state issue, apply the fix suggested in the past review comment (lines 271-283): compute nextConfig synchronously, update state with it, and pass it to saveDriverConfig(nextConfig). The past review provides a detailed diff.

     const handleDriverTypeChange = async (driverType: string, element: Element) => {
         const option = DRIVER_OPTIONS.find(opt => opt.label === driverType);
         if (!option) return;
 
-        setDriverConfig(prev => ({ ...prev, type: option.configType }));
         setDriverError(null);
+        
+        let nextConfig: DriverConfig = {
+            ...driverConfig,
+            type: option.configType
+        };
 
         if (option.configType === 'default') {
             handleClearDriver();
             await loadDefaultDriverDetails();
         } else {
             const paramValues = parameters?.paramValues || [];
             const getParamValue = (key: string) => paramValues.find((p: any) => p.key === key)?.value;
 
             const existingGroupId = getParamValue('groupId');
             const existingArtifactId = getParamValue('artifactId');
             const existingVersion = getParamValue('version');
             const existingDriverPath = getParamValue('driverPath');
             if (option.configType === 'custom') {
                 if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
-                    setDriverConfig(prev => ({
-                        ...prev,
+                    nextConfig = {
+                        ...nextConfig,
                         groupId: existingGroupId,
                         artifactId: existingArtifactId,
                         version: existingVersion,
                         driverPath: existingDriverPath
-                    }));
+                    };
                 }
             } else {
                 if (existingGroupId && existingArtifactId && existingVersion) {
-                    setDriverConfig(prev => ({
-                        ...prev,
+                    nextConfig = {
+                        ...nextConfig,
                         groupId: existingGroupId,
                         artifactId: existingArtifactId,
                         version: existingVersion,
                         driverPath: null
-                    }));
+                    };
                 } else if (existingDriverPath) {
                     setValue("driverPath", null);
                 }
             }
-            saveDriverConfig();
+            setDriverConfig(nextConfig);
+            await saveDriverConfig(nextConfig);
         }
     };

Also update saveDriverConfig signature to accept an optional config parameter as shown in the past review.

Based on learnings, past review comment flags this exact issue.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0074b26 and 7833df0.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-24T22:16:28.380Z
Learnt from: dan-niles
Repo: wso2/vscode-extensions PR: 980
File: workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx:384-412
Timestamp: 2025-11-24T22:16:28.380Z
Learning: In the RichTextTemplateEditor component (workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx), token fetching on external `value` prop changes is intentionally disabled. Users cannot edit both the minimized and expanded editors simultaneously, so tokens only need to be generated based on user typing in the expanded editor view, not on external prop updates.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 890-891: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 918-919: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 969-970: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1198-1199: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1199-1200: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1200-1201: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1455-1456: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (6)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (6)

292-323: LGTM!

The driver configuration initialization logic correctly reads from both parameters and form values, determines the appropriate driver type, and handles errors appropriately.


1033-1036: LGTM! Good error handling pattern.

The custom error prioritization is correctly implemented and consistent throughout the component.


1604-1646: LGTM! Well-structured radio button implementation.

The radio button handling is well-implemented:

  • Driver-specific radio (driverSelectOption) correctly integrates with handleDriverTypeChange and renders configuration UI
  • Generic radio buttons use the same GenericRadioGroup component consistently
  • Conditional value change propagation (lines 1640-1642) correctly checks for element.onValueChange before calling the handler

465-470: LGTM! Clean value change propagation.

The handleValueChange function provides a clean integration point for dynamic field updates with appropriate null checking and async handling.


641-680: LGTM! Clean driver configuration rendering.

The renderDriverConfiguration function cleanly separates the three driver configuration types (default, custom, Maven) into dedicated components with appropriate props and error handling.


160-177: LGTM! Well-defined interfaces for dynamic fields.

The DynamicField and DynamicFieldGroup interfaces provide clear type definitions for the dynamic fields feature, including all necessary metadata for rendering.

Comment on lines +413 to +433
useEffect(() => {
try {
if (!dynamicFieldsHandler.current) {
dynamicFieldsHandler.current = new DynamicFieldsHandler({
rpcClient,
formData,
getValues,
setValue,
setComboValues,
documentUri: props.documentUri,
parameters,
dynamicFields,
setDynamicFields,
connectionName,
setCustomError
});
}
} catch (error) {
console.error("Error initializing dynamicFieldsHandler:", error);
}
}, []);
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

Consider adding dependencies to avoid stale closures.

The DynamicFieldsHandler is initialized once on mount with an empty dependency array. If props like parameters, setComboValues, or connectionName change after mount, the handler will continue using stale references, potentially causing bugs.

Either add all captured props to the dependency array, or consider whether this handler needs to be recreated on prop changes:

-    }, []);
+    }, [parameters, setComboValues, connectionName, documentUri]);

Alternatively, if recreation is expensive, consider using refs for values that might change or restructure to avoid closures over props.

📝 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
useEffect(() => {
try {
if (!dynamicFieldsHandler.current) {
dynamicFieldsHandler.current = new DynamicFieldsHandler({
rpcClient,
formData,
getValues,
setValue,
setComboValues,
documentUri: props.documentUri,
parameters,
dynamicFields,
setDynamicFields,
connectionName,
setCustomError
});
}
} catch (error) {
console.error("Error initializing dynamicFieldsHandler:", error);
}
}, []);
useEffect(() => {
try {
if (!dynamicFieldsHandler.current) {
dynamicFieldsHandler.current = new DynamicFieldsHandler({
rpcClient,
formData,
getValues,
setValue,
setComboValues,
documentUri: props.documentUri,
parameters,
dynamicFields,
setDynamicFields,
connectionName,
setCustomError
});
}
} catch (error) {
console.error("Error initializing dynamicFieldsHandler:", error);
}
}, [parameters, setComboValues, connectionName, documentUri]);
🤖 Prompt for AI Agents
In workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx around lines
413 to 433, the DynamicFieldsHandler is created inside a useEffect with an empty
dependency array which can cause captured props (rpcClient, formData, getValues,
setValue, setComboValues, props.documentUri, parameters, dynamicFields,
setDynamicFields, connectionName, setCustomError) to become stale; fix by either
adding those dependencies to the useEffect dependency array so the handler is
recreated when they change, or keep the empty array but move any changing values
into stable refs (e.g., create refs for parameters, connectionName,
setComboValues, etc.) and read from those refs inside the handler so it always
uses current values without re-instantiating the handler.

imedyaosandi and others added 3 commits December 8, 2025 02:59
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 0

♻️ Duplicate comments (6)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (6)

197-227: Wrap switch case bodies that declare variables in blocks (Biome noSwitchDeclarations).

Cases at Line 214 (buildQuery), Line 1025 (INSERT), Line 1069 (SELECT), Line 1114 (CALL), and Lines 1249–1274 declare const/let directly under case. This matches Biome’s noSwitchDeclarations errors and can let variables bleed across cases in JS.

Wrap those case bodies in blocks to scope declarations and satisfy linting:

@@
-            switch (config.function) {
+            switch (config.function) {
@@
-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
+                    }
+                    break;
+                }
@@
-        switch (operationType) {
-            case QUERY_TYPES.INSERT:
-                const insertColumns = Object.values(activeFields);
+        switch (operationType) {
+            case QUERY_TYPES.INSERT: {
+                const insertColumns = Object.values(activeFields);
@@
-                } else {
+                } else {
                     query = `INSERT INTO ${encodedTableName} () VALUES ()`; // Or handle as error?
                     preparedStatement = `INSERT INTO ${encodedTableName} () VALUES ()`;
                 }
 
-                break;
+                break;
+            }
 
-            case QUERY_TYPES.DELETE:
+            case QUERY_TYPES.DELETE: {
                 query = `DELETE FROM ${encodedTableName}`;
@@
-                }
-
-                break;
-
-            case QUERY_TYPES.SELECT:
+                }
+
+                break;
+            }
+
+            case QUERY_TYPES.SELECT: {
@@
-                // Append ORDER BY, LIMIT, OFFSET for SELECT
-                const orderByVal = this.getValues(getNameForController(FIELD_NAMES.ORDER_BY));
-                const limitVal = this.getValues(getNameForController(FIELD_NAMES.LIMIT));
-                const offsetVal = this.getValues(getNameForController(FIELD_NAMES.OFFSET));
+                // Append ORDER BY, LIMIT, OFFSET for SELECT
+                const orderByVal = this.getValues(getNameForController(FIELD_NAMES.ORDER_BY));
+                const limitVal = this.getValues(getNameForController(FIELD_NAMES.LIMIT));
+                const offsetVal = this.getValues(getNameForController(FIELD_NAMES.OFFSET));
@@
-                }
-
-                break;
-
-            case QUERY_TYPES.CALL:
-
-                let callTemplate;
+                }
+
+                break;
+            }
+
+            case QUERY_TYPES.CALL: {
+
+                let callTemplate;
@@
-                const callParams = Object.values(allFields).map(f => {
+                const callParams = Object.values(allFields).map(f => {
@@
-                const prepParams = Object.values(allFields).map(f => '?').join(', ');
+                const prepParams = Object.values(allFields).map(f => '?').join(', ');
@@
-                columnTypes.push(...Object.values(allFields).map(f => f.columnType ?? 'UNKNOWN'));
-
-                break;
+                columnTypes.push(...Object.values(allFields).map(f => f.columnType ?? 'UNKNOWN'));
+
+                break;
+            }
         }
-            switch (operationType) {
-                case QUERY_TYPES.SELECT:
-                    if(columns.trim() !== '*') {
-                    const selectResult = this._parseSelectColumns(columns, dynamicFieldDefs, connectionInfo !== undefined);
-                    if (!selectResult.success) throw new Error(selectResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
-                    Object.assign(selectMatchedFields, selectResult.fields);
-                }
-
-                    const whereResult = this._parseWhereClause(whereClause, dynamicFieldDefs, connectionInfo !== undefined);
-                    if (!whereResult.success) throw new Error(whereResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
-                    Object.assign(matchedFields, whereResult.fields);
-                    break;
-                case QUERY_TYPES.DELETE:
-                    const deleteWhereResult = this._parseWhereClause(whereClause, dynamicFieldDefs, connectionInfo !== undefined);
-                    if (!deleteWhereResult.success) throw new Error(deleteWhereResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
-                    Object.assign(matchedFields, deleteWhereResult.fields);
-                    break;
-                case QUERY_TYPES.INSERT:
-                    const insertResult = this._parseInsertValues(columns, values, dynamicFieldDefs, connectionInfo !== undefined);
-                    if (!insertResult.success) throw new Error(insertResult.errorMessage || ERROR_MESSAGES.INSERT_MISMATCH);
-                    Object.assign(matchedFields, insertResult.fields);
-                    break;
-                case QUERY_TYPES.CALL:
-                    const callResult = this._parseCallParams(values, dynamicFieldDefs, connectionInfo !== undefined);
-                    if (!callResult.success) throw new Error(callResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
-                    Object.assign(matchedFields, callResult.fields);
-                    break;
-            }
+            switch (operationType) {
+                case QUERY_TYPES.SELECT: {
+                    if (columns.trim() !== '*') {
+                        const selectResult = this._parseSelectColumns(
+                            columns,
+                            dynamicFieldDefs,
+                            connectionInfo !== undefined,
+                        );
+                        if (!selectResult.success) {
+                            throw new Error(selectResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
+                        }
+                        Object.assign(selectMatchedFields, selectResult.fields);
+                    }
+
+                    const whereResult = this._parseWhereClause(
+                        whereClause,
+                        dynamicFieldDefs,
+                        connectionInfo !== undefined,
+                    );
+                    if (!whereResult.success) {
+                        throw new Error(whereResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
+                    }
+                    Object.assign(matchedFields, whereResult.fields);
+                    break;
+                }
+                case QUERY_TYPES.DELETE: {
+                    const deleteWhereResult = this._parseWhereClause(
+                        whereClause,
+                        dynamicFieldDefs,
+                        connectionInfo !== undefined,
+                    );
+                    if (!deleteWhereResult.success) {
+                        throw new Error(deleteWhereResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
+                    }
+                    Object.assign(matchedFields, deleteWhereResult.fields);
+                    break;
+                }
+                case QUERY_TYPES.INSERT: {
+                    const insertResult = this._parseInsertValues(
+                        columns,
+                        values,
+                        dynamicFieldDefs,
+                        connectionInfo !== undefined,
+                    );
+                    if (!insertResult.success) {
+                        throw new Error(insertResult.errorMessage || ERROR_MESSAGES.INSERT_MISMATCH);
+                    }
+                    Object.assign(matchedFields, insertResult.fields);
+                    break;
+                }
+                case QUERY_TYPES.CALL: {
+                    const callResult = this._parseCallParams(
+                        values,
+                        dynamicFieldDefs,
+                        connectionInfo !== undefined,
+                    );
+                    if (!callResult.success) {
+                        throw new Error(callResult.errorMessage || ERROR_MESSAGES.FIELD_NOT_FOUND);
+                    }
+                    Object.assign(matchedFields, callResult.fields);
+                    break;
+                }
+            }

This should clear the Biome noSwitchDeclarations errors. Based on static analysis hints (Biome).

Also applies to: 1023-1151, 1248-1275


355-399: Unreachable updatedOffline && updatedOnline condition in setElementVisibility.

At Line 383, if (updatedOffline && updatedOnline) can never be true because updatedOffline and updatedOnline are initialized to false for each element and only one of the mutually exclusive queryType checks can pass per element. The method still mutates visibility correctly, but the return path is effectively dead.

If the intent is to return true as soon as either offline or online element is updated, use || instead, or drop the flags and always return true once the matching element is processed:

-                    if (updatedOffline && updatedOnline) {
-                        return true;
-                    }
+                    if (updatedOffline || updatedOnline) {
+                        return true;
+                    }

Please double-check the desired behavior (especially if there can be separate online/offline variants of the same field) before applying.


403-481: Fix optional chaining on onDynamicFieldChange to avoid runtime TypeError.

In _handleDynamicContentChange, Line 409 and Line 435 access onDynamicFieldChange.params without optional chaining. If onDynamicFieldChange is undefined, this will throw despite earlier ?. usage on onValueChange.

Refactor to cache the config and use ?. on params:

-        const newFields = await this.fetchDynamicFields(element, value, fieldName);
-        // Update dynamic fields state only if fetch was successful (newFields is not null)
-        if (newFields !== null && newFields !== undefined) {
-            //let augmentedFields = newFields;
-            if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.SELECT) {
+        const newFields = await this.fetchDynamicFields(element, value, fieldName);
+        // Update dynamic fields state only if fetch was successful (newFields is not null)
+        if (newFields !== null && newFields !== undefined) {
+            const onDynamicFieldChangeConfig = element.onValueChange?.params?.[0]?.onDynamicFieldChange;
+            const queryType = onDynamicFieldChangeConfig?.params?.[0]?.queryType;
+
+            if (queryType === QUERY_TYPES.SELECT) {
@@
-                this.setDynamicFields({
-                    ...this.dynamicFields, [fieldName]: {
-                        header: "Table Columns",
-                        fields: augmentedFields,
-                    },
-                });
-            } else if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.CALL) {
-                this.setDynamicFields({
-                    ...this.dynamicFields, [fieldName]: {
-                        header: "Parameters",
-                        fields: newFields,
-                    },
-                });
-            } else {
-                this.setDynamicFields({
-                    ...this.dynamicFields, [fieldName]: {
-                        header: "Table Columns",
-                        fields: newFields,
-                    },
-                });
-            }
+                this.setDynamicFields({
+                    ...this.dynamicFields,
+                    [fieldName]: {
+                        header: "Table Columns",
+                        fields: augmentedFields,
+                    },
+                });
+            } else if (queryType === QUERY_TYPES.CALL) {
+                this.setDynamicFields({
+                    ...this.dynamicFields,
+                    [fieldName]: {
+                        header: "Parameters",
+                        fields: newFields,
+                    },
+                });
+            } else {
+                this.setDynamicFields({
+                    ...this.dynamicFields,
+                    [fieldName]: {
+                        header: "Table Columns",
+                        fields: newFields,
+                    },
+                });
+            }
@@
-                        const dummyElement = {
+                        const dummyElement = {
@@
-                                onValueChange: element.onValueChange?.params?.[0]?.onDynamicFieldChange,
+                                onValueChange: onDynamicFieldChangeConfig,

This keeps the handler safe even when onDynamicFieldChange is not wired.


595-700: Stop connection validation when DB driver cannot be obtained.

In _getValidConnectionDetails you now bound the retry loop, which is good, but:

  • isDriverDownloaded starts as false even when a driverPath is already configured.
  • If download ultimately fails, you still push a DRIVER_PATH parameter with a falsy value and call loadDriverAndTestConnection, causing a guaranteed failing RPC and unclear UX.

You can treat an existing driverPath as already “downloaded” and bail out early when no path is available after retries:

-            let isDriverDownloaded = false;
+            let isDriverDownloaded = !!driverPath;
             let retryCount = 0;
             const maxRetries = 5;   
             if (!driverPath) {
                 while (!isDriverDownloaded && retryCount < maxRetries) {
@@
                     driverPath = await this.rpcClient.getMiDiagramRpcClient().downloadDriverForConnector(args);
                     if (driverPath) {
                         isDriverDownloaded = true;
                     }
                     retryCount++;
                 }
             }
-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
+            if (!isDriverDownloaded || !driverPath) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download the DB driver after 5 attempts."
+                );
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                return undefined;
+            }
             connection.parameters.push({
                 name: FIELD_NAMES.DRIVER_PATH,
                 value: driverPath,
             });

This avoids a redundant failing connection test and cleanly falls back to offline mode when the driver isn’t available.


693-699: Align CONFIG_KEY error field with other paths in RPC catch.

In the catch block of _getValidConnectionDetails (Line 695) you call setCustomError with FIELD_NAMES.CONFIG_KEY directly, whereas every other path uses getNameForController(FIELD_NAMES.CONFIG_KEY). If setCustomError expects the controller name, this branch may not attach the error to the right field.

Align it with the rest:

         } catch (error) {
             console.error("Error testing DB connection:", error);
-            this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");
+            this.setCustomError(
+                getNameForController(FIELD_NAMES.CONFIG_KEY),
+                ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)"
+            );
             this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
             this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
             return undefined;
         }

This keeps error targeting consistent for all connection-validation failures.


1354-1367: Thread dbType into prepared-statement builders to keep identifier quoting consistent.

In _handleManualQueryChange, you correctly compute dbType and encodedTableName, but _buildSelectPreparedStatement, _buildInsertPreparedStatement, and _buildDeletePreparedStatement call _encodeColumnName without passing dbType, so they always use the default quoting style. This can produce inconsistent SQL compared to _buildQueryFromDynamicFields (e.g., double quotes for MySQL columns instead of backticks).

Update the helper signatures and call sites to pass dbType:

@@
-            switch (operationType) {
-                case QUERY_TYPES.SELECT:
-                    preparedStatement = this._buildSelectPreparedStatement(columns, encodedTableName, matchedFields, orderBy, limit, offset);
-                    break;
-                case QUERY_TYPES.INSERT:
-                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
-                    break;
-                case QUERY_TYPES.DELETE:
-                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
-                    break;
-                case QUERY_TYPES.CALL:
-                    preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields);
-                    break;
-            }
+            switch (operationType) {
+                case QUERY_TYPES.SELECT:
+                    preparedStatement = this._buildSelectPreparedStatement(
+                        columns,
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                        orderBy,
+                        limit,
+                        offset,
+                    );
+                    break;
+                case QUERY_TYPES.INSERT:
+                    preparedStatement = this._buildInsertPreparedStatement(
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                    );
+                    break;
+                case QUERY_TYPES.DELETE:
+                    preparedStatement = this._buildDeletePreparedStatement(
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                    );
+                    break;
+                case QUERY_TYPES.CALL:
+                    preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields);
+                    break;
+            }
-    /** Helper: Builds a prepared statement string for SELECT */
-    private _buildSelectPreparedStatement(columnsStr: string, tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>, orderBy?: string, limit?: string, offset?: string): string {
-
-        const selectCols = columnsStr?.trim() || '*';
-        let statement = `SELECT ${selectCols} FROM ${tableName}`;
-
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`) // Use displayName for column name
-            .join(' AND ');
+    /** Helper: Builds a prepared statement string for SELECT */
+    private _buildSelectPreparedStatement(
+        columnsStr: string,
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+        orderBy?: string,
+        limit?: string,
+        offset?: string,
+    ): string {
+
+        const selectCols = columnsStr?.trim() || '*';
+        let statement = `SELECT ${selectCols} FROM ${tableName}`;
+
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(
+                key =>
+                    `${this._encodeColumnName(
+                        matchedWhereFields[key].displayName,
+                        dbType,
+                    )} = ?`,
+            ) // Use displayName for column name
+            .join(' AND ');
@@
-        if (orderBy) {
-            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
-            statement += ` ORDER BY ${cleanOrderBy}`;
-        }
+        if (orderBy) {
+            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
+            const encodedOrderBy = this._encodeColumnName(cleanOrderBy, dbType);
+            statement += ` ORDER BY ${encodedOrderBy}`;
+        }
-    /** Helper: Builds a prepared statement string for INSERT */
-    private _buildInsertPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
-        const columns = Object.values(matchedFields).map(f => this._encodeColumnName(f.displayName)).join(', ');
+    /** Helper: Builds a prepared statement string for INSERT */
+    private _buildInsertPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
+        const columns = Object.values(matchedFields)
+            .map(f => this._encodeColumnName(f.displayName, dbType))
+            .join(', ');
-    /** Helper: Builds a prepared statement string for DELETE */
-    private _buildDeletePreparedStatement(tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>): string {
-        let statement = `DELETE FROM ${tableName}`; // tableName should be encoded already
-
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
-            .join(' AND ');
+    /** Helper: Builds a prepared statement string for DELETE */
+    private _buildDeletePreparedStatement(
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
+        let statement = `DELETE FROM ${tableName}`; // tableName should be encoded already
+
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(
+                key =>
+                    `${this._encodeColumnName(
+                        matchedWhereFields[key].displayName,
+                        dbType,
+                    )} = ?`,
+            )
+            .join(' AND ');

This keeps quoting consistent for parsed manual queries across different DBs (MySQL, PostgreSQL, SQL Server, etc.).

Also applies to: 1575-1621

🧹 Nitpick comments (2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)

197-207: Guard TABLE_NAME branch in handleValueChange when queryBuildConfig is missing.

In the 'handleDynamicContent' case at Line 200, you now use optional chaining for queryBuildConfig, but _buildQueryFromDynamicFields is still called even if queryBuildConfig is undefined, which will pass undefined as operationType and can lead to surprising behavior (no-op switch, fields reset).

Consider guarding before calling:

                 case 'handleDynamicContent':
                     if (element.inputType === 'string' && element.name === FIELD_NAMES.TABLE_NAME) {
                         const parentElement = this.findElementByName(this.formData.elements, FIELD_NAMES.TABLE_NAME);
-                        const queryBuildConfig = parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
-                        await this._buildQueryFromDynamicFields(FIELD_NAMES.TABLE_NAME, queryBuildConfig?.queryType , queryBuildConfig, element);
+                        const queryBuildConfig =
+                            parentElement?.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0];
+                        if (queryBuildConfig?.queryType) {
+                            await this._buildQueryFromDynamicFields(
+                                FIELD_NAMES.TABLE_NAME,
+                                queryBuildConfig.queryType,
+                                queryBuildConfig,
+                                element,
+                            );
+                        } else {
+                            console.warn(
+                                `'handleDynamicContent' called for TABLE_NAME without a valid queryType in onDynamicFieldChange config.`,
+                            );
+                        }
                     } else {

This aligns the behavior with onDynamicFieldChange, which already validates queryBuildConfig before using it.


156-158: Remove duplicate 'stringOrExpression' entry from EXPRESSION_TYPES.

EXPRESSION_TYPES currently includes 'stringOrExpression' twice (Line 156 and again at Line 157–158). That’s harmless but redundant and can confuse future readers.

You can deduplicate and format for readability:

-const EXPRESSION_TYPES = ['stringOrExpression', 'integerOrExpression', 'expression', 'keyOrExpression', 'resourceOrExpression',
-    'textOrExpression', 'textAreaOrExpression', 'stringOrExpression'
-];
+const EXPRESSION_TYPES = [
+    'stringOrExpression',
+    'integerOrExpression',
+    'expression',
+    'keyOrExpression',
+    'resourceOrExpression',
+    'textOrExpression',
+    'textAreaOrExpression',
+];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7833df0 and 3405b20.

📒 Files selected for processing (1)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/**/*.{ts,tsx} : Define all constants (node types, sizing, spacing) in src/resources/constants.ts and import them where needed instead of hardcoding values

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-data-mapper-utils/src/dm-utils.ts (1)
  • match (231-233)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1)

162-191: Overall DynamicFieldsHandler structure looks solid.

Constructor wiring, prop shape (DynamicFieldsHandlerProps), and the public methods (handleValueChange, fetchDynamicFields, onConnectionChange, onDynamicFieldChange) form a coherent API around the form generator and RPC layer. Error handling and separation between online/offline modes are well thought through.

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

♻️ Duplicate comments (11)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (7)

693-699: Use getNameForController consistently when setting CONFIG_KEY error on RPC failure.

Elsewhere, setCustomError uses getNameForController(FIELD_NAMES.CONFIG_KEY), but the RPC error path uses the raw field name. Aligning this avoids any mismatch if controller naming changes.

-            console.error("Error testing DB connection:", error);
-            this.setCustomError(FIELD_NAMES.CONFIG_KEY, ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)");
+            console.error("Error testing DB connection:", error);
+            this.setCustomError(
+                getNameForController(FIELD_NAMES.CONFIG_KEY),
+                ERROR_MESSAGES.CONNECTION_FAILED + " (RPC Error)",
+            );

213-220: Wrap buildQuery case body in a block to satisfy switch scoping (Biome noSwitchDeclarations).

Declaring const parentField directly in the case 'buildQuery' clause leaks its scope to other cases and triggers Biome’s noSwitchDeclarations error. Wrap the case body in a block.

-                case 'buildQuery':
-                    const parentField = (element as any).parentField;
-                    if (parentField) {
-                        await this.onDynamicFieldChange(value, element, parentField);
-                    } else {
-                        console.warn(`'buildQuery' called without parentField for element:`, element.name);
-                    }
-                    break;
+                case 'buildQuery': {
+                    const parentField = (element as any).parentField;
+                    if (parentField) {
+                        await this.onDynamicFieldChange(value, element, parentField);
+                    } else {
+                        console.warn(
+                            `'buildQuery' called without parentField for element:`,
+                            element.name
+                        );
+                    }
+                    break;
+                }

361-385: Fix unreachable updatedOffline && updatedOnline condition in setElementVisibility.

updatedOffline and updatedOnline are set in mutually exclusive branches (offline vs online enableCondition), so updatedOffline && updatedOnline can never be true; the function will never return true for TABLE_NAME / ORDER_BY.

-                    if (updatedOffline && updatedOnline) {
+                    if (updatedOffline || updatedOnline) {
                         return true;
                     }

403-449: Fully guard onDynamicFieldChange access with optional chaining to avoid runtime TypeError.

In _handleDynamicContentChange, both SELECT and CALL checks access .params on onDynamicFieldChange without ?.. If onDynamicFieldChange is undefined, this will throw.

-            if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.SELECT) {
+            if (
+                element.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0]
+                    ?.queryType === QUERY_TYPES.SELECT
+            ) {
@@
-            } else if (element.onValueChange?.params?.[0]?.onDynamicFieldChange.params?.[0]?.queryType === QUERY_TYPES.CALL) {
+            } else if (
+                element.onValueChange?.params?.[0]?.onDynamicFieldChange?.params?.[0]
+                    ?.queryType === QUERY_TYPES.CALL
+            ) {

631-691: Correct driver download logic and bail early when download fails (avoid false errors and bad test calls).

Two issues in _getValidConnectionDetails:

  1. If driverPath already exists, isDriverDownloaded stays false, so you still set “Failed to download…” even though no download was attempted.
  2. When download retries fail, you still push an undefined driverPath and call loadDriverAndTestConnection, causing an unnecessary failing RPC.
-            let isDriverDownloaded = false;
+            let isDriverDownloaded = !!driverPath;
             let retryCount = 0;
             const maxRetries = 5;   
-            if (!driverPath) {
+            if (!driverPath) {
                 while (!isDriverDownloaded && retryCount < maxRetries) {
@@
                     retryCount++;
                 }
             }
-            if (!isDriverDownloaded) {
-                this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY), "Failed to download the DB driver after 5 attempts.");
-            }
-            connection.parameters.push({
+            if (!driverPath || !isDriverDownloaded) {
+                this.setCustomError(
+                    getNameForController(FIELD_NAMES.CONFIG_KEY),
+                    "Failed to download the DB driver after 5 attempts."
+                );
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                return undefined;
+            }
+
+            connection.parameters.push({
                 name: FIELD_NAMES.DRIVER_PATH,
                 value: driverPath,
             });
@@
             if (!testResult.success) {
                 this.setCustomError(getNameForController(FIELD_NAMES.CONFIG_KEY),
                     ERROR_MESSAGES.CONNECTION_FAILED + (testResult.message ? `: ${testResult.message}` : ''));
-                    this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
-                    this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);
+                this.setValue(FIELD_NAMES.ASSISTANCE_MODE, false);
+                this.setValue(FIELD_NAMES.QUERY_TYPE, UI_MODES.OFFLINE);

1353-1365: Thread dbType into prepared‑statement builders to keep identifier quoting consistent across DBs.

In _handleManualQueryChange, you derive dbType and encodedTableName, but _buildSelectPreparedStatement, _buildInsertPreparedStatement, _buildDeletePreparedStatement, and _buildCallPreparedStatement internally call _encodeColumnName without dbType, so they fall back to the “default” quoting rule. This can generate inconsistent SQL for engines like MySQL vs PostgreSQL.

-            switch (operationType) {
-                case QUERY_TYPES.SELECT:
-                    preparedStatement = this._buildSelectPreparedStatement(columns, encodedTableName, matchedFields, orderBy, limit, offset);
+            switch (operationType) {
+                case QUERY_TYPES.SELECT:
+                    preparedStatement = this._buildSelectPreparedStatement(
+                        columns,
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                        orderBy,
+                        limit,
+                        offset,
+                    );
                     break;
                 case QUERY_TYPES.INSERT:
-                    preparedStatement = this._buildInsertPreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildInsertPreparedStatement(
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                    );
                     break;
                 case QUERY_TYPES.DELETE:
-                    preparedStatement = this._buildDeletePreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildDeletePreparedStatement(
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                    );
                     break;
                 case QUERY_TYPES.CALL:
-                    preparedStatement = this._buildCallPreparedStatement(encodedTableName, matchedFields);
+                    preparedStatement = this._buildCallPreparedStatement(
+                        encodedTableName,
+                        matchedFields,
+                        dbType,
+                    );
                     break;
             }
-    private _buildSelectPreparedStatement(columnsStr: string, tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>, orderBy?: string, limit?: string, offset?: string): string {
+    private _buildSelectPreparedStatement(
+        columnsStr: string,
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+        orderBy?: string,
+        limit?: string,
+        offset?: string,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');
@@
-        if (orderBy) {
-            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
-            statement += ` ORDER BY ${cleanOrderBy}`;
-        }
+        if (orderBy) {
+            const cleanOrderBy = orderBy.replace(/[`"\[\]]/g, '').trim();
+            const encodedOrderBy = this._encodeColumnName(cleanOrderBy, dbType);
+            statement += ` ORDER BY ${encodedOrderBy}`;
+        }
-    private _buildInsertPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
-        const columns = Object.values(matchedFields).map(f => this._encodeColumnName(f.displayName)).join(', ');
+    private _buildInsertPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
+        const columns = Object.values(matchedFields)
+            .map(f => this._encodeColumnName(f.displayName, dbType))
+            .join(', ');
-    private _buildDeletePreparedStatement(tableName: string, matchedWhereFields: Record<string, DynamicFieldValue>): string {
+    private _buildDeletePreparedStatement(
+        tableName: string,
+        matchedWhereFields: Record<string, DynamicFieldValue>,
+        dbType: string | undefined,
+    ): string {
@@
-        const wherePlaceholders = Object.keys(matchedWhereFields)
-            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName)} = ?`)
+        const wherePlaceholders = Object.keys(matchedWhereFields)
+            .map(key => `${this._encodeColumnName(matchedWhereFields[key].displayName, dbType)} = ?`)
             .join(' AND ');
-    private _buildCallPreparedStatement(tableName: string, matchedFields: Record<string, DynamicFieldValue>): string {
+    private _buildCallPreparedStatement(
+        tableName: string,
+        matchedFields: Record<string, DynamicFieldValue>,
+        _dbType: string | undefined,
+    ): string {
         const placeholders = Object.keys(matchedFields).map(() => '?').join(', ');
         return `CALL ${tableName}(${placeholders})`;
     }

Also applies to: 1573-1625


1023-1151: Block‑scope switch (operationType) cases in _buildQueryFromDynamicFields to satisfy Biome and avoid declaration bleed.

const declarations in the INSERT, SELECT, and CALL cases (insertColumns, orderByVal, limitVal, offsetVal, callTemplate, callParams, prepParams) are currently in bare case bodies, triggering noSwitchDeclarations and risking accidental reuse.

-        switch (operationType) {
-            case QUERY_TYPES.INSERT:
-                const insertColumns = Object.values(activeFields);
+        switch (operationType) {
+            case QUERY_TYPES.INSERT: {
+                const insertColumns = Object.values(activeFields);
@@
-                }
-
-                break;
-
-            case QUERY_TYPES.DELETE:
+                }
+
+                break;
+            }
+
+            case QUERY_TYPES.DELETE: {
@@
-                }
-
-                break;
-
-            case QUERY_TYPES.SELECT:
+                }
+
+                break;
+            }
+
+            case QUERY_TYPES.SELECT: {
@@
-                const orderByVal = this.getValues(getNameForController(FIELD_NAMES.ORDER_BY));
-                const limitVal = this.getValues(getNameForController(FIELD_NAMES.LIMIT));
-                const offsetVal = this.getValues(getNameForController(FIELD_NAMES.OFFSET));
+                const orderByVal = this.getValues(getNameForController(FIELD_NAMES.ORDER_BY));
+                const limitVal = this.getValues(getNameForController(FIELD_NAMES.LIMIT));
+                const offsetVal = this.getValues(getNameForController(FIELD_NAMES.OFFSET));
@@
-                break;
-
-            case QUERY_TYPES.CALL:
-
-                let callTemplate;
+                break;
+            }
+
+            case QUERY_TYPES.CALL: {
+
+                let callTemplate;
@@
-                const callParams = Object.values(allFields).map(f => {
+                const callParams = Object.values(allFields).map(f => {
@@
-                const prepParams = Object.values(allFields).map(f => '?').join(', ');
+                const prepParams = Object.values(allFields).map(f => '?').join(', ');
@@
-                break;
+                break;
+            }
         }
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)

412-432: Reconsider empty dependency array when constructing DynamicFieldsHandler (stale props risk).

DynamicFieldsHandler is created once with useEffect(..., []), but it captures parameters, setComboValues, connectionName, and documentUri. If any of those props change after mount, the handler will continue using stale references.

Consider either:

  • Adding dependencies so the handler is re‑created when inputs change:
-    useEffect(() => {
+    useEffect(() => {
         try {
             if (!dynamicFieldsHandler.current) {
                 dynamicFieldsHandler.current = new DynamicFieldsHandler({
                     rpcClient,
                     formData,
                     getValues,
                     setValue,
                     setComboValues,
                     documentUri: props.documentUri,
                     parameters,
                     dynamicFields,
                     setDynamicFields,
                     connectionName,
                     setCustomError
                 });
             }
         } catch (error) {
             console.error("Error initializing dynamicFieldsHandler:", error);
         }
-    }, []);
+    }, [rpcClient, formData, getValues, setValue, setComboValues, props.documentUri, parameters, connectionName]);

or

  • Keeping the empty array but moving changing values into useRefs and passing those refs into DynamicFieldsHandler so it always reads current values.

889-913: Fix React Hooks rule violations in nested helpers (ParamManagerComponent, ExpressionFieldComponent, FormExpressionFieldComponent).

These helpers call useEffect but are invoked as plain functions (e.g. ParamManagerComponent(...)), not as React components. This breaks the Rules of Hooks and is flagged by Biome.

Refactor them into proper components used via JSX. For example:

-    function ParamManagerComponent(element: Element, isRequired: boolean, helpTipElement: JSX.Element, field: any) {
-        useEffect(() => {
-            handleValueChange(field.value, element.name.toString(), element);
-        }, []); // run only on mount
-        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
+    function ParamManagerComponent(props: {
+        element: Element;
+        isRequired: boolean;
+        helpTipElement: JSX.Element;
+        field: any;
+        handleValueChange: (value: any, fieldName: string, element: Element) => Promise<void>;
+        documentUri?: string;
+        range?: Range;
+    }) {
+        const { element, isRequired, helpTipElement, field, handleValueChange, documentUri, range } = props;
+        useEffect(() => {
+            handleValueChange(field.value, element.name.toString(), element);
+        }, [field.value, element, handleValueChange]);
+        return <ComponentCard id={'parameterManager-' + element.name} sx={cardStyle} disbaleHoverEffect>
@@
-            <ParameterManager
-                documentUri={documentUri}
+            <ParameterManager
+                documentUri={documentUri}
@@
-    }
+    }
@@
-            case 'ParamManager': {
-                return (
-                    ParamManagerComponent(element, isRequired, helpTipElement, field)
-                );
-            }
+            case 'ParamManager': {
+                return (
+                    <ParamManagerComponent
+                        element={element}
+                        isRequired={isRequired}
+                        helpTipElement={helpTipElement}
+                        field={field}
+                        handleValueChange={handleValueChange}
+                        documentUri={documentUri}
+                        range={range}
+                    />
+                );
+            }

Apply the same pattern to ExpressionFieldComponent and FormExpressionFieldComponent: define them as components taking a props object and render them as <ExpressionFieldComponent ... /> / <FormExpressionFieldComponent ... /> inside the switch instead of calling them like regular functions.

Also applies to: 915-971, 965-1002


271-290: Avoid stale driverConfig when saving; pass the next config into saveDriverConfig.

handleDriverTypeChange mutates driverConfig via setDriverConfig and then immediately calls saveDriverConfig, but saveDriverConfig reads driverConfig from state. Because setState is async, saveDriverConfig can run with the previous config, causing validation and saved values to disagree with the UI.

A safer pattern is to compute nextConfig synchronously and pass it into saveDriverConfig:

-    const handleDriverTypeChange = async (driverType: string, element: Element) => {
+    const handleDriverTypeChange = async (driverType: string, element: Element) => {
         const option = DRIVER_OPTIONS.find(opt => opt.label === driverType);
         if (!option) return;
 
-        setDriverConfig(prev => ({ ...prev, type: option.configType }));
-        setDriverError(null);
+        setDriverError(null);
+        let nextConfig: DriverConfig = { ...driverConfig, type: option.configType };
 
         if (option.configType === 'default') {
-            handleClearDriver();
-            await loadDefaultDriverDetails();
+            handleClearDriver();
+            await loadDefaultDriverDetails();
         } else {
@@
-            if (option.configType === 'custom') {
+            if (option.configType === 'custom') {
                 if (existingGroupId && existingArtifactId && existingVersion && existingDriverPath) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: existingDriverPath
-                    }));
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: existingDriverPath,
+                    };
                 }
             } else {
                 if (existingGroupId && existingArtifactId && existingVersion) {
-                    setDriverConfig(prev => ({
-                        ...prev,
-                        groupId: existingGroupId,
-                        artifactId: existingArtifactId,
-                        version: existingVersion,
-                        driverPath: null
-                    }));
-                } else if (existingDriverPath) {
+                    nextConfig = {
+                        ...nextConfig,
+                        groupId: existingGroupId,
+                        artifactId: existingArtifactId,
+                        version: existingVersion,
+                        driverPath: null,
+                    };
+                }
+                if (existingDriverPath) {
                     setValue("driverPath", null);
                 }
             }
-            saveDriverConfig();
+            setDriverConfig(nextConfig);
+            await saveDriverConfig(nextConfig);
         }
     };
@@
-    const saveDriverConfig = async () => {
+    const saveDriverConfig = async (configOverride?: DriverConfig) => {
+        const cfg = configOverride ?? driverConfig;
         try {
             // Validate required fields based on driver type
-            if (driverConfig.type === 'maven' &&
-                (!driverConfig.groupId || !driverConfig.artifactId || !driverConfig.version)) {
+            if (cfg.type === 'maven' &&
+                (!cfg.groupId || !cfg.artifactId || !cfg.version)) {
@@
-            if (driverConfig.type === 'maven') {
+            if (cfg.type === 'maven') {
                 const validDriver = await rpcClient.getMiDiagramRpcClient().downloadDriverForConnector({
-                    groupId: driverConfig.groupId,
-                    artifactId: driverConfig.artifactId,
-                    version: driverConfig.version
+                    groupId: cfg.groupId,
+                    artifactId: cfg.artifactId,
+                    version: cfg.version
                 });
@@
-            setValue("groupId", driverConfig.groupId);
-            setValue("artifactId", driverConfig.artifactId);
-            setValue("version", driverConfig.version);
-            if (driverConfig.type === 'custom') {
-                setValue("driverPath", driverConfig.driverPath);
+            setValue("groupId", cfg.groupId);
+            setValue("artifactId", cfg.artifactId);
+            setValue("version", cfg.version);
+            if (cfg.type === 'custom') {
+                setValue("driverPath", cfg.driverPath);
             }

Update onSave props to call saveDriverConfig(driverConfig) where needed.

Also applies to: 478-521, 543-581


1197-1202: Remove or relocate useEffect calls from inside renderFormElement switch cases (combo* and connection).

The useEffect invocations in the 'booleanOrExpression' / 'comboOrExpression' / 'combo' and 'connection' cases run conditionally inside a switch and are not at the top level of a component, violating the Rules of Hooks (Biome useHookAtTopLevel).

Two viable approaches:

  1. Move effects into dedicated child components:

Create small components (e.g. ComboFieldWithDynamic and ConnectionField) that own their own useEffect and render the existing JSX, then use them from the switch:

-            case 'booleanOrExpression':
-            case 'comboOrExpression':
-            case 'combo':
-                const items = ...
-                const allowItemCreate = ...
-                useEffect(() => {
-                    handleValueChange(field.value, name, element);
-                }, [props.comboValuesMap?.[name]]);
-                return (
-                    <>
-                        <AutoComplete ... />
-                        {...dynamic fields...}
-                    </>
-                );
+            case 'booleanOrExpression':
+            case 'comboOrExpression':
+            case 'combo':
+                return (
+                    <ComboFieldWithDynamic
+                        name={name}
+                        element={element}
+                        field={field}
+                        helpTipElement={helpTipElement}
+                        errorMsg={errorMsg}
+                        isRequired={isRequired}
+                        comboValuesMap={props.comboValuesMap}
+                        dynamicFields={dynamicFields}
+                        handleValueChange={handleValueChange}
+                    />
+                );

Inside ComboFieldWithDynamic, call useEffect at its top level.

Do the same for the 'connection' case (wrap in a ConnectionField component and move its useEffect there).

  1. If the mount‑time side‑effects are not essential, drop the useEffect entirely and rely solely on onValueChange handlers to trigger handleValueChange.

Given the linter errors, option 1 is safer if you rely on the initialization behavior.

Also applies to: 1454-1456

🧹 Nitpick comments (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

271-290: Consider removing unused isDriverConfigLoading state.

isDriverConfigLoading is set but never read, which suggests leftover wiring from an earlier spinner UX.

If you don’t plan to show a loading indicator, drop this state to reduce noise:

-    const [isDriverConfigLoading, setIsDriverConfigLoading] = useState(true);
+    // Removed unused isDriverConfigLoading state; reintroduce if a loading UI is added.

and remove the related setIsDriverConfigLoading calls.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3405b20 and 4771921.

📒 Files selected for processing (2)
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (1 hunks)
  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (22 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/**/*.tsx : Use React 18.2.0 features including concurrent rendering and automatic batching; avoid class components in favor of functional components with hooks

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T14:51:49.267Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 998
File: workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx:113-132
Timestamp: 2025-11-24T14:51:49.267Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx, if `textFieldRef.current` is not undefined, `textFieldRef.current.inputElement` is guaranteed to exist. If `inputElement` doesn't exist when `current` exists, it's a fatal error that should reach the error boundary rather than being handled with defensive null checks.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-24T22:16:28.380Z
Learnt from: dan-niles
Repo: wso2/vscode-extensions PR: 980
File: workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx:384-412
Timestamp: 2025-11-24T22:16:28.380Z
Learning: In the RichTextTemplateEditor component (workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/RichTextTemplateEditor/RichTextTemplateEditor.tsx), token fetching on external `value` prop changes is intentionally disabled. Users cannot edit both the minimized and expanded editors simultaneously, so tokens only need to be generated based on user typing in the expanded editor view, not on external prop updates.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx
📚 Learning: 2025-11-27T07:59:33.534Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx:50-59
Timestamp: 2025-11-27T07:59:33.534Z
Learning: In workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx, the `clause.properties.expression` property in the `fillDefaults` function does not require defensive null/undefined checks because it's a required property enforced by form validation in ClauseEditor.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:34:09.752Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:128-134
Timestamp: 2025-11-26T06:34:09.752Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBreadcrumb() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T07:49:56.428Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:136-141
Timestamp: 2025-11-26T07:49:56.428Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, the goPrevViewBackButton() method is only called when in a focused view, ensuring breadcrumbs are always present. No guard for empty breadcrumbs is needed.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement input validation for CDModel structure: verify that all required properties (connections, listeners, services) are present and properly typed

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-25T06:34:10.812Z
Learnt from: CR
Repo: wso2/vscode-extensions PR: 0
File: workspaces/ballerina/component-diagram/AGENTS.md:0-0
Timestamp: 2025-11-25T06:34:10.812Z
Learning: Applies to workspaces/ballerina/component-diagram/src/components/Diagram.tsx : Implement proper error boundaries around the Diagram component to gracefully handle rendering errors without crashing the parent application

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
📚 Learning: 2025-11-26T06:33:22.950Z
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 653
File: workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts:86-112
Timestamp: 2025-11-26T06:33:22.950Z
Learning: In workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts, empty catch blocks around progress ring waitForSelector calls (state: 'attached' and 'detached') are intentional. The progress ring duration depends on machine performance and may appear very briefly, causing the wait to miss the event. The try-catch allows the test to proceed gracefully regardless of timing.

Applied to files:

  • workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx
🧬 Code graph analysis (1)
workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx (2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (4)
  • DynamicFieldGroup (174-177)
  • Element (119-153)
  • getNameForController (179-184)
  • DynamicField (160-172)
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts (1)
  • DynamicField (2255-2266)
🪛 Biome (2.1.2)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx

[error] 889-890: This hook is being called from a nested function, but all hooks must be called unconditionally from the top-level component.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 917-918: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 968-969: This hook is being called indirectly and conditionally, but all hooks must be called in the exact same order in every component render.

This is the call path until the hook.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1197-1198: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1198-1199: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1199-1200: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 1454-1455: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

workspaces/mi/mi-diagram/src/components/Form/DynamicFields/DynamicFieldsHandler.tsx

[error] 214-214: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1025-1025: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1092-1092: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1093-1093: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1094-1094: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1116-1116: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1135-1139: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1141-1141: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1256-1256: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1261-1261: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1266-1266: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 1271-1271: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (1)
workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx (1)

965-971: Surface customErrors in legacy ExpressionFieldComponent as well.

FormExpressionFieldComponent correctly prioritizes customErrors[name] over RHF errors, but ExpressionFieldComponent still uses only errors[name]. For fields using legacy expression mode, custom errors from DynamicFieldsHandler (e.g., parse/connection issues) won't be shown.

Mirror the FormExpressionFieldComponent pattern in ExpressionFieldComponent:

     const ExpressionFieldComponent = ({ element, canChange, field, helpTipElement, placeholder, isRequired }: { element: Element, canChange: boolean, field: any, helpTipElement: JSX.Element, placeholder: string, isRequired: boolean }) => {
         const name = getNameForController(element.name);
+        const customError = customErrors[name];
         useEffect(() => {
             handleValueChange(field.value, name, element);
         }, []);
         return expressionEditorField !== name ? (
             <ExpressionField
                 {...field}
@@
-                errorMsg={errors[name] && errors[name].message.toString()}
+                errorMsg={customError ?? (errors[name] && errors[name].message.toString())}

This keeps error display consistent across both expression field variants.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants