diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts index d003b56b632..e289dac5f01 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts @@ -37,6 +37,7 @@ export enum TypeKind { Boolean = "boolean", Enum = "enum", Union = "union", + Tuple = "tuple", Unknown = "$CompilationError$", Anydata = "anydata", Byte = "byte", diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/data-mapper/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/data-mapper/utils.ts index 649e782c2df..5bdad4d7745 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/data-mapper/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/data-mapper/utils.ts @@ -537,6 +537,14 @@ function processTypeKind( } break; + case TypeKind.Tuple: + if (type.members) { + return { + members: processTuple(type.members, parentId, model, visitedRefs) + }; + } + break; + case TypeKind.Record: if (type.ref) { return processTypeReference(type.ref, parentId, model, visitedRefs); @@ -788,3 +796,35 @@ function processEnum( })); } +/** + * Processes tuple type members and returns an array of IOType objects + * Each tuple member is processed with its index-based identifier + */ +function processTuple( + tupleMembers: IOTypeField[], + parentId: string, + model: DMModel, + visitedRefs: Set +): IOType[] { + return tupleMembers.map((tupleMember, index) => { + const memberFieldId = `${parentId}[${index}]`; + + const tupleMemberType: IOType = { + id: memberFieldId, + name: tupleMember.name, + displayName: tupleMember.displayName, + typeName: tupleMember.typeName, + kind: tupleMember.kind, + ...(tupleMember.optional && { optional: tupleMember.optional }), + ...(tupleMember.typeInfo && { typeInfo: tupleMember.typeInfo }) + }; + + const typeSpecificProps = processTypeKind(tupleMember, memberFieldId, model, new Set(visitedRefs)); + + return { + ...tupleMemberType, + ...typeSpecificProps + }; + }); +} + diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ArrayOutput/ArrayOuptutFieldWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ArrayOutput/ArrayOuptutFieldWidget.tsx index d16c04be9ba..9961761bab8 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ArrayOutput/ArrayOuptutFieldWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ArrayOutput/ArrayOuptutFieldWidget.tsx @@ -204,6 +204,7 @@ export function ArrayOutputFieldWidget(props: ArrayOutputFieldWidgetProps) { fieldIndex={index} treeDepth={0} hasHoveredParent={isHovered || hasHoveredParent} + parentFieldKind={field?.kind} />
diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNode.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNode.ts index a6c6df45c90..0b5338a35b2 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNode.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNode.ts @@ -84,6 +84,22 @@ export class InputNode extends DataMapperNodeModel { focusedFieldFQNs }); } + } else if (this.filteredInputType.kind === TypeKind.Tuple) { + const members = this.filteredInputType.members?.filter(m => !!m); + for (const member of members) { + this.numberOfFields += await this.addPortsForInputField({ + field: member, + portType: "OUT", + parentId: this.identifier, + unsafeParentId: this.identifier, + parent: parentPort, + collapsedFields, + expandedFields, + hidden: parentPort.attributes.collapsed, + isOptional: member.optional, + focusedFieldFQNs + }); + } } else if (this.filteredInputType.kind === TypeKind.Enum) { for (const member of this.filteredInputType.members ?? []) { this.numberOfFields += await this.addPortsForInputField({ diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeFactory.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeFactory.tsx index 743c8129ab8..fbfe56dad25 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeFactory.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeFactory.tsx @@ -40,6 +40,7 @@ export class InputNodeFactory extends AbstractReactFactory(PortState.Unselected); const [isHovered, setIsHovered] = useState(false); @@ -51,7 +52,16 @@ export function InputNodeTreeItemWidget(props: InputNodeTreeItemWidgetProps) { const fieldName = dmType.name; const displayName = dmType.displayName || fieldName; const typeName = getTypeName(dmType); - const fieldId = dmType.isFocused ? fieldName : `${parentId}.${fieldName}`; + + let fieldId: string; + if (parentFieldKind === TypeKind.Tuple) { + fieldId = parentId + fieldName; + } else if (dmType.isFocused) { + fieldId = fieldName; + } else { + fieldId = `${parentId}.${fieldName}`; + } + const portOut = getPort(`${fieldId}.OUT`); const isUnknownType = dmType.kind === TypeKind.Unknown; @@ -61,6 +71,8 @@ export function InputNodeTreeItemWidget(props: InputNodeTreeItemWidgetProps) { if (dmType.kind === TypeKind.Record) { fields = dmType.fields; + } else if (dmType.kind === TypeKind.Tuple) { + fields = dmType.members; } else if (dmType.kind === TypeKind.Array) { fields = [ dmType.member ]; } @@ -168,6 +180,7 @@ export function InputNodeTreeItemWidget(props: InputNodeTreeItemWidgetProps) { treeDepth={treeDepth + 1} hasHoveredParent={isHovered || hasHoveredParent} focusedInputs={focusedInputs} + parentFieldKind={dmType.kind} /> ); }) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeWidget.tsx index f994148a33e..b6ab15f6731 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/Input/InputNodeWidget.tsx @@ -68,6 +68,8 @@ export function InputNodeWidget(props: InputNodeWidgetProps) { if (dmType.kind === TypeKind.Record) { fields = dmType.fields; + } else if (dmType.kind === TypeKind.Tuple) { + fields = dmType.members; } else if (dmType.kind === TypeKind.Array) { fields = [ dmType.member ]; } else if (dmType.kind === TypeKind.Enum) { @@ -172,6 +174,7 @@ export function InputNodeWidget(props: InputNodeWidgetProps) { treeDepth={0} hasHoveredParent={isHovered} focusedInputs={focusedInputs} + parentFieldKind={dmType.kind} /> ); }) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputFieldWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputFieldWidget.tsx index d861b17829c..66371704c9c 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputFieldWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputFieldWidget.tsx @@ -47,6 +47,7 @@ export interface ObjectOutputFieldWidgetProps { treeDepth?: number; hasHoveredParent?: boolean; isPortParent?: boolean; + parentFieldKind?: TypeKind; } export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { @@ -59,7 +60,8 @@ export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { fieldIndex, treeDepth = 0, hasHoveredParent, - isPortParent + isPortParent, + parentFieldKind } = props; const classes = useIONodesStyles(); const [isLoading, setLoading] = useState(false); @@ -80,10 +82,11 @@ export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { const isArray = typeKind === TypeKind.Array; const isRecord = typeKind === TypeKind.Record; + const isTuple = typeKind === TypeKind.Tuple; const isEnum = typeKind === TypeKind.Enum; let updatedParentId = parentId; - + if (fieldIndex !== undefined) { updatedParentId = `${updatedParentId}.${fieldIndex}` } @@ -91,13 +94,19 @@ export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { let fieldName = field?.name || ''; let displayName = field?.displayName || fieldName; - let portName = isPortParent - ? parentId - : updatedParentId !== '' - ? fieldName !== '' && fieldIndex === undefined - ? `${updatedParentId}.${fieldName}` - : updatedParentId - : fieldName; + // Handle tuple members by combining port prefix with field.name (bracket notation) + let portName: string; + if (parentFieldKind === TypeKind.Tuple) { + portName = `${parentId}${field.name}`; + } else if (isPortParent) { + portName = parentId; + } else if (updatedParentId !== '') { + portName = fieldName !== '' && fieldIndex === undefined + ? `${updatedParentId}.${fieldName}` + : updatedParentId; + } else { + portName = fieldName; + } const portIn = getPort(portName + ".IN"); const isUnknownType = field?.kind === TypeKind.Unknown; @@ -109,7 +118,8 @@ export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { getDefaultValue(field?.kind) === expression.trim() && !isEnum; - const fields = isRecord && field?.fields?.filter(f => f !== null); + const fields = (isRecord && field?.fields?.filter(f => f !== null)) + || (isTuple && field?.members?.filter(m => m !== null)); const isWithinArray = fieldIndex !== undefined; const handleExpand = () => { @@ -303,7 +313,7 @@ export function ObjectOutputFieldWidget(props: ObjectOutputFieldWidgetProps) { {fields && (