-
Notifications
You must be signed in to change notification settings - Fork 72
[BI Data Mapper] Improve data mapper converted-variable UI #1545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/bi-1.8.x
Are you sure you want to change the base?
Changes from 4 commits
61aeef2
bd46697
f5e7e6a
0a00baf
9dca054
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -149,15 +149,26 @@ export function InputNodeWidget(props: InputNodeWidgetProps) { | |||||||||||||||||
| onMouseLeave={onMouseLeave} | ||||||||||||||||||
| > | ||||||||||||||||||
| <span className={classes.label}> | ||||||||||||||||||
| {(fields || dmType.convertedField || isConvertibleType) && ( | ||||||||||||||||||
| {dmType.category !== InputCategory.ConvertedVariable ? ( | ||||||||||||||||||
| (fields || dmType.convertedField || isConvertibleType) && ( | ||||||||||||||||||
| <Button | ||||||||||||||||||
| id={"expand-or-collapse-" + id} | ||||||||||||||||||
| appearance="icon" | ||||||||||||||||||
| tooltip="Expand/Collapse" | ||||||||||||||||||
| onClick={handleExpand} | ||||||||||||||||||
| data-testid={`${id}-expand-icon-record-source-node`} | ||||||||||||||||||
| > | ||||||||||||||||||
| {expanded ? <Codicon name="chevron-down" /> : <Codicon name="chevron-right" />} | ||||||||||||||||||
| </Button> | ||||||||||||||||||
| ) | ||||||||||||||||||
| ) : ( | ||||||||||||||||||
| <Button | ||||||||||||||||||
| id={"expand-or-collapse-" + id} | ||||||||||||||||||
| id={"converted-icon-" + id} | ||||||||||||||||||
| appearance="icon" | ||||||||||||||||||
| tooltip="Expand/Collapse" | ||||||||||||||||||
| onClick={handleExpand} | ||||||||||||||||||
| data-testid={`${id}-expand-icon-record-source-node`} | ||||||||||||||||||
| tooltip="Type defined variable" | ||||||||||||||||||
| onClick={() => { }} | ||||||||||||||||||
| > | ||||||||||||||||||
| {expanded ? <Codicon name="chevron-down" /> : <Codicon name="chevron-right" />} | ||||||||||||||||||
| <Icon name="arrow-down-right" /> | ||||||||||||||||||
| </Button> | ||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| )} | ||||||||||||||||||
| {label} | ||||||||||||||||||
|
|
@@ -167,7 +178,7 @@ export function InputNodeWidget(props: InputNodeWidgetProps) { | |||||||||||||||||
| <FieldActionButton | ||||||||||||||||||
| id={"field-action-edit-" + id} | ||||||||||||||||||
| tooltip="Edit" | ||||||||||||||||||
| iconName="settings-gear" | ||||||||||||||||||
| iconName="edit" | ||||||||||||||||||
| onClick={async () => await context.createConvertedVariable(dmType.name, true, dmType.typeName)} | ||||||||||||||||||
| /> | ||||||||||||||||||
| )} | ||||||||||||||||||
|
|
@@ -202,27 +213,21 @@ export function InputNodeWidget(props: InputNodeWidgetProps) { | |||||||||||||||||
| )} | ||||||||||||||||||
| </TreeContainer> | ||||||||||||||||||
| {expanded && dmType.convertedField && | ||||||||||||||||||
| <> | ||||||||||||||||||
| <ArrowWidget direction="down" /> | ||||||||||||||||||
| <InputNodeWidget | ||||||||||||||||||
| id={dmType.convertedField.name} | ||||||||||||||||||
| dmType={dmType.convertedField} | ||||||||||||||||||
| engine={engine} | ||||||||||||||||||
| getPort={getPort} | ||||||||||||||||||
| context={context} | ||||||||||||||||||
| valueLabel={dmType.convertedField.name} | ||||||||||||||||||
| focusedInputs={focusedInputs} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </> | ||||||||||||||||||
| <InputNodeWidget | ||||||||||||||||||
| id={dmType.convertedField.name} | ||||||||||||||||||
| dmType={dmType.convertedField} | ||||||||||||||||||
| engine={engine} | ||||||||||||||||||
| getPort={getPort} | ||||||||||||||||||
| context={context} | ||||||||||||||||||
| valueLabel={dmType.convertedField.name} | ||||||||||||||||||
| focusedInputs={focusedInputs} | ||||||||||||||||||
| /> | ||||||||||||||||||
| } | ||||||||||||||||||
| {expanded && isConvertibleType && !dmType.convertedField && | ||||||||||||||||||
| <> | ||||||||||||||||||
| <ArrowWidget direction="down" /> | ||||||||||||||||||
| <PayloadWidget | ||||||||||||||||||
| onClick={async () => await context.createConvertedVariable(headerLabel, true, undefined, dmType.typeName)} | ||||||||||||||||||
| typeName={dmType.typeName} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </> | ||||||||||||||||||
| <PayloadWidget | ||||||||||||||||||
| onClick={async () => await context.createConvertedVariable(headerLabel, true, undefined, dmType.typeName)} | ||||||||||||||||||
| typeName={dmType.typeName.toUpperCase()} | ||||||||||||||||||
| /> | ||||||||||||||||||
|
Comment on lines
+226
to
+229
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same
🛡️ Proposed fix- <PayloadWidget
- onClick={async () => await context.createConvertedVariable(headerLabel, true, undefined, dmType.typeName)}
- typeName={dmType.typeName.toUpperCase()}
- />
+ <PayloadWidget
+ onClick={async () => await context.createConvertedVariable(headerLabel, true, undefined, dmType.typeName)}
+ typeName={(dmType.typeName ?? typeKind).toUpperCase()}
+ />📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typeName is a required field, interfaces will be refactored in a separate PR
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🐰✨ ✏️ Learnings added
🧠 Learnings used |
||||||||||||||||||
| } | ||||||||||||||||||
| </> | ||||||||||||||||||
| ); | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -152,21 +152,32 @@ export function ObjectOutputWidget(props: ObjectOutputWidgetProps) { | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||
| <span className={classes.label}> | ||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||
| id={"expand-or-collapse-" + id} | ||||||||||||||||||||||||||||||||||||||||
| appearance="icon" | ||||||||||||||||||||||||||||||||||||||||
| tooltip="Expand/Collapse" | ||||||||||||||||||||||||||||||||||||||||
| onClick={handleExpand} | ||||||||||||||||||||||||||||||||||||||||
| data-testid={`${id}-expand-icon-mapping-target-node`} | ||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||
| {expanded ? <Codicon name="chevron-down" /> : <Codicon name="chevron-right" />} | ||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||
| {outputType.category !== InputCategory.ConvertedVariable ? ( | ||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||
| id={"expand-or-collapse-" + id} | ||||||||||||||||||||||||||||||||||||||||
| appearance="icon" | ||||||||||||||||||||||||||||||||||||||||
| tooltip="Expand/Collapse" | ||||||||||||||||||||||||||||||||||||||||
| onClick={handleExpand} | ||||||||||||||||||||||||||||||||||||||||
| data-testid={`${id}-expand-icon-mapping-target-node`} | ||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||
| {expanded ? <Codicon name="chevron-down" /> : <Codicon name="chevron-right" />} | ||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||
| id={"converted-icon-" + id} | ||||||||||||||||||||||||||||||||||||||||
| appearance="icon" | ||||||||||||||||||||||||||||||||||||||||
| tooltip="Type defined variable" | ||||||||||||||||||||||||||||||||||||||||
| onClick={() => { }} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
| onClick={() => { }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find IOType definition and typeName property
fd ".*\.(ts|tsx)$" workspaces/ballerina/data-mapper --type f | head -20 | xargs grep -l "IOType" | head -5Repository: wso2/vscode-extensions
Length of output: 48
🏁 Script executed:
#!/bin/bash
# Search for IOType interface/type definition
rg "type IOType|interface IOType" --type ts -A 5 workspaces/ballerina/Repository: wso2/vscode-extensions
Length of output: 1139
🏁 Script executed:
#!/bin/bash
# Get context around lines 219-224 in the target file
cat -n workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputWidget.tsx | sed -n '210,235p'Repository: wso2/vscode-extensions
Length of output: 732
🏁 Script executed:
#!/bin/bash
# Find type of outputType and understand the isConvertibleType guard
cat -n workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputWidget.tsx | sed -n '75,95p'Repository: wso2/vscode-extensions
Length of output: 804
🏁 Script executed:
#!/bin/bash
# Check the broader context to understand typeKind
cat -n workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputWidget.tsx | sed -n '1,50p'Repository: wso2/vscode-extensions
Length of output: 2599
🏁 Script executed:
#!/bin/bash
# Check TypeKind enum definition
rg "export enum TypeKind|export const TypeKind" --type ts -A 10 workspaces/ballerina/ballerina-core/Repository: wso2/vscode-extensions
Length of output: 1104
🏁 Script executed:
#!/bin/bash
# Check if there are any usages or initialization patterns for outputType.typeName
rg "outputType\.typeName" --type ts -B 2 -A 2 workspaces/ballerina/data-mapper/src/components/Diagram/ | head -50Repository: wso2/vscode-extensions
Length of output: 1578
outputType.typeName?.toUpperCase() can throw if typeName is undefined.
IOType.typeName is optional (typeName?: string). The isConvertibleType guard at line 85 ensures kind is Json or Xml, but does not guarantee typeName is defined. Calling .toUpperCase() on undefined will raise a TypeError at runtime.
Use optional chaining or provide a safe fallback:
💡 Suggested approach
- typeName={outputType.typeName.toUpperCase()}
+ typeName={outputType.typeName?.toUpperCase()}Or with explicit fallback:
- typeName={outputType.typeName.toUpperCase()}
+ typeName={(outputType.typeName ?? typeKind).toUpperCase()}📝 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.
| {expanded && isConvertibleType && !outputType.convertedField && | |
| <> | |
| <ArrowWidget direction="up" /> | |
| <PayloadWidget | |
| onClick={async () => await context.createConvertedVariable(valueLabel, false, undefined, outputType.typeName)} | |
| typeName={outputType.typeName} | |
| /> | |
| </> | |
| <PayloadWidget | |
| onClick={async () => await context.createConvertedVariable(valueLabel, false, undefined, outputType.typeName)} | |
| typeName={outputType.typeName.toUpperCase()} | |
| /> | |
| } | |
| {expanded && isConvertibleType && !outputType.convertedField && | |
| <PayloadWidget | |
| onClick={async () => await context.createConvertedVariable(valueLabel, false, undefined, outputType.typeName)} | |
| typeName={outputType.typeName?.toUpperCase()} | |
| /> | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputWidget.tsx`
around lines 219 - 224, In ObjectOutputWidget.tsx the render for PayloadWidget
calls outputType.typeName.toUpperCase() which can throw if typeName is undefined
(IOType.typeName is optional); change the call in the JSX where PayloadWidget is
created (the block that checks expanded && isConvertibleType &&
!outputType.convertedField) to safely handle missing typeName by using optional
chaining and a fallback (e.g., outputType.typeName?.toUpperCase() ?? 'UNKNOWN'
or similar) so the UI never calls .toUpperCase() on undefined; update any
related props or labels passed to PayloadWidget to accept the fallback string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typeName is a required field, interfaces will be refactored in a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KCSAbeywickrama, thank you for the clarification! If typeName is guaranteed to be present at runtime, then the optional chaining isn't necessary. I appreciate you noting that the interface will be updated in a separate PR to reflect this requirement.
✏️ Learnings added
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 1545
File: workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputWidget.tsx:219-224
Timestamp: 2026-02-24T07:15:43.238Z
Learning: In workspaces/ballerina/data-mapper, IOType.typeName is marked as optional in the TypeScript interface (typeName?: string), but it is guaranteed to be a required field at runtime. The interface will be refactored in a future PR to reflect this requirement.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: sachiniSam
Repo: wso2/vscode-extensions PR: 1143
File: workspaces/ballerina/component-diagram/src/components/nodes/ConnectionNode/ConnectionNodeWidget.tsx:204-204
Timestamp: 2025-12-17T08:04:30.603Z
Learning: In workspaces/ballerina/component-diagram/src/components/nodes/ConnectionNode/ConnectionNodeWidget.tsx, the model.node object does not have a metadata field in its interface yet. It's intentionally accessed via `(model.node as any).metadata` because the metadata structure is expected to change with the edit flow implementation.
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/nodes/EntryNode/EntryNodeWidget.tsx : Route EntryNode rendering to specialized widgets based on service type: use AIServiceWidget for 'ai:Service', GraphQLServiceWidget for 'graphql:Service', and GeneralWidget for HTTP and other services
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/nodes/**/!(*Widget|*Model).tsx : Implement the factory pattern for node instantiation: each node type (Listener, Entry, Connection) must have a corresponding Factory class that extends the appropriate factory interface
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/nodes/EntryNode/components/GeneralWidget.tsx : For regular services with more than 3 functions, show only the first 2 functions with a 'View All' button; for services with 3 or fewer functions, show all functions
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/nodes/**/*.{ts,tsx} : Each node type must have a Model class extending NodeModel, a Factory class implementing the factory pattern, and a Widget React component for visual representation
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: Keep node widgets focused on visual representation; move complex business logic to utility functions and context providers
Learnt from: KCSAbeywickrama
Repo: wso2/vscode-extensions PR: 897
File: workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx:102-107
Timestamp: 2025-11-27T07:58:16.698Z
Learning: In workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx, the `inProgress` state in `wrapWithProgress` intentionally doesn't reset to `false`. The component unmounts when the onClick operation finishes, and resetting the state would cause the mapping options menu to briefly reappear while the data mapper loads new content from file changes.
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/nodes/EntryNode/components/**/*.tsx : Implement lazy expansion of functions: only render function items when they are visible to improve performance for large function lists
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.
Learnt from: kanushka
Repo: wso2/vscode-extensions PR: 1117
File: workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx:1183-1184
Timestamp: 2025-12-16T13:47:11.133Z
Learning: In workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx, the findMatchedType function intentionally uses module?.split('.').pop() to handle simple cases where the last segment of the module name is used for matching. Wildcard imports should be used for collision cases (when different modules have the same last segment), but this is not currently implemented as the code only handles the simple case for now.
Learnt from: pasindufernando1
Repo: wso2/vscode-extensions PR: 1307
File: workspaces/ballerina/data-mapper/src/components/Diagram/Node/commons/DataMapperNode.ts:273-306
Timestamp: 2026-01-26T07:30:11.584Z
Learning: In workspaces/ballerina/data-mapper, tuple member names (field.name) are always set because they are generated from the member index in the backend (processTuple function), so defensive null/undefined checks for fieldName in tuple-specific FQN branches are not required.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty arrow function onClick handler should be replaced with
undefinedif no action is needed, or removed entirely if the button doesn't need to be clickable. Using an empty function is less clear about intent.