From ea512642124196325a80d74442448c9854034b59 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 13 Nov 2025 15:59:14 +0530 Subject: [PATCH 01/15] Enhance ClauseEditor to support GROUP BY clause and update clause type options --- .../src/interfaces/data-mapper.ts | 3 ++- .../SidePanel/QueryClauses/ClauseEditor.tsx | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts index 30f0c1a16c7..25ed1e23eeb 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts @@ -57,9 +57,10 @@ export enum IntermediateClauseType { LET = "let", WHERE = "where", FROM = "from", - ORDER_BY = "order by", + ORDER_BY = "order-by", LIMIT = "limit", JOIN = "join", + GROUP_BY = "group-by" } export enum ResultClauseType { diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx index b38e1066947..9044e4d56db 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx @@ -38,12 +38,13 @@ export function ClauseEditor(props: ClauseEditorProps) { const [clauseType, setClauseType] = React.useState(_clauseType ?? IntermediateClauseType.WHERE); const clauseTypeItems: OptionProps[] = [ - { content: "condition", value: IntermediateClauseType.WHERE }, - { content: "local variable", value: IntermediateClauseType.LET }, - { content: "sort by", value: IntermediateClauseType.ORDER_BY }, - { content: "limit", value: IntermediateClauseType.LIMIT }, - { content: "from", value: IntermediateClauseType.FROM }, - { content: "join", value: IntermediateClauseType.JOIN }, + { content: "Condition", value: IntermediateClauseType.WHERE }, + { content: "Local variable", value: IntermediateClauseType.LET }, + { content: "Sort by", value: IntermediateClauseType.ORDER_BY }, + { content: "Limit", value: IntermediateClauseType.LIMIT }, + { content: "From", value: IntermediateClauseType.FROM }, + { content: "Join", value: IntermediateClauseType.JOIN }, + { content: "Group by", value: IntermediateClauseType.GROUP_BY } ] const nameField: DMFormField = { @@ -128,6 +129,8 @@ export function ClauseEditor(props: ClauseEditorProps) { if (clauseType === IntermediateClauseType.JOIN) { clause.properties.type = "var"; clause.properties.isOuter = false; + } else if (clauseType === IntermediateClauseType.GROUP_BY) { + clause.properties.type = "var"; } onSubmit(clause); } @@ -147,6 +150,8 @@ export function ClauseEditor(props: ClauseEditorProps) { return [expressionField, orderField]; case IntermediateClauseType.JOIN: return [expressionField, nameField, lhsExpressionField, rhsExpressionField]; + case IntermediateClauseType.GROUP_BY: + return [nameField, expressionField]; default: return [expressionField]; } From f07704f6e790ef0e3b873bd2683881a2fdd9525b Mon Sep 17 00:00:00 2001 From: ChamodA Date: Fri, 14 Nov 2025 16:55:00 +0530 Subject: [PATCH 02/15] Wrap menuItems and generation functions with useMemo --- .../Diagram/Label/MappingOptionsWidget.tsx | 270 +++++++++--------- 1 file changed, 137 insertions(+), 133 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx index 0ec3c959dbf..76383b86d58 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx @@ -17,7 +17,7 @@ */ // tslint:disable: jsx-no-multiline-js -import React from 'react'; +import React, { useMemo } from 'react'; import { ResultClauseType, TypeKind } from '@wso2/ballerina-core'; import { Codicon, Item, Menu, MenuItem, ProgressRing } from '@wso2/ui-toolkit'; @@ -97,148 +97,152 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { const pendingMappingType = link.pendingMappingType; const [inProgress, setInProgress] = React.useState(false); - const wrapWithProgress = (onClick: () => Promise) => { - return async () => { - setInProgress(true); - await onClick(); + + const menuItems: Item[] = useMemo(() => { + const wrapWithProgress = (onClick: () => Promise) => { + return async () => { + setInProgress(true); + await onClick(); + } + }; + + const onClickMapDirectly = async () => { + await createNewMapping(link); } - }; - const onClickMapDirectly = async () => { - await createNewMapping(link); - } - - const onClickMapIndividualElements = async () => { - await mapWithQuery(link, ResultClauseType.SELECT, context); - }; - - const onClickMapArraysAccessSingleton = async () => { - await createNewMapping(link, (expr: string) => `${expr}${genArrayElementAccessSuffix(link)}`); - }; - - const onClickAggregateArray = async () => { - await mapWithQuery(link, ResultClauseType.COLLECT, context); - }; - - const onClickMapWithCustomFn = async () => { - await mapWithCustomFn(link, context); - }; - - const onClickMapWithTransformFn = async () => { - await mapWithTransformFn(link, context); - } - - const onClickMapWithAggregateFn = async (fn: string) => { - await createNewMapping(link, (expr: string) => `${fn}(${expr})`); - } - - const getItemElement = (id: string, label: string) => { - return ( -
- - {label} -
- ); - } - - const a2aMenuItems: Item[] = [ - { - id: "a2a-direct", - label: getItemElement("a2a-direct", "Map Input Array to Output Array"), - onClick: wrapWithProgress(onClickMapDirectly) - }, - { - id: "a2a-inner", - label: getItemElement("a2a-inner", "Map Array Elements Individually"), - onClick: wrapWithProgress(onClickMapIndividualElements) + const onClickMapIndividualElements = async () => { + await mapWithQuery(link, ResultClauseType.SELECT, context); + }; + + const onClickMapArraysAccessSingleton = async () => { + await createNewMapping(link, (expr: string) => `${expr}${genArrayElementAccessSuffix(link)}`); + }; + + const onClickAggregateArray = async () => { + await mapWithQuery(link, ResultClauseType.COLLECT, context); + }; + + const onClickMapWithCustomFn = async () => { + await mapWithCustomFn(link, context); + }; + + const onClickMapWithTransformFn = async () => { + await mapWithTransformFn(link, context); } - ]; - - const defaultMenuItems: Item[] = [ - { - id: "a2a-direct", - label: getItemElement("direct", "Map Anyway"), - onClick: wrapWithProgress(onClickMapDirectly) + + const onClickMapWithAggregateFn = async (fn: string) => { + await createNewMapping(link, (expr: string) => `${fn}(${expr})`); } - ]; - - const genAggregateItems = () => { - const aggregateFnsNumeric = ["sum", "avg", "min", "max"]; - const aggregateFnsString = ["string:'join"]; - const aggregateFnsCommon = ["first", "last"]; - - const sourcePort = link.getSourcePort() as InputOutputPortModel; - const sourceType = sourcePort.attributes.field.kind; - - const aggregateFns = [...(isNumericType(sourceType) ? aggregateFnsNumeric : - getGenericTypeKind(sourceType) === TypeKind.String ? aggregateFnsString : - []), - ...aggregateFnsCommon]; - const a2sAggregateItems: Item[] = aggregateFns.map((fn) => ({ - id: `a2s-collect-${fn}`, - label: getItemElement(`a2s-collect-${fn}`, `Aggregate using ${fn}`), - onClick: wrapWithProgress(async () => await onClickMapWithAggregateFn(fn)) - })); - return a2sAggregateItems; - }; - - const genArrayToSingletonItems = (): Item[] => { - const a2sMenuItems: Item[] = [ + + const getItemElement = (id: string, label: string) => { + return ( +
+ + {label} +
+ ); + } + + const a2aMenuItems: Item[] = [ + { + id: "a2a-direct", + label: getItemElement("a2a-direct", "Map Input Array to Output Array"), + onClick: wrapWithProgress(onClickMapDirectly) + }, { - id: "a2s-index", - label: getItemElement("a2s-index", "Extract Single Element from Array"), - onClick: wrapWithProgress(onClickMapArraysAccessSingleton) + id: "a2a-inner", + label: getItemElement("a2a-inner", "Map Array Elements Individually"), + onClick: wrapWithProgress(onClickMapIndividualElements) } - ]; - const sourcePort = link.getSourcePort() as InputOutputPortModel; - const targetPort = link.getTargetPort() as InputOutputPortModel; - const sourceMemberType = sourcePort.attributes.field.member?.kind; - const targetType = targetPort.attributes.field.kind; - - if (sourceMemberType === targetType && isPrimitive(sourceMemberType)) { - a2sMenuItems.push({ - id: "a2s-aggregate", - label: getItemElement("a2s-aggregate", "Aggregate and map"), - onClick: wrapWithProgress(onClickAggregateArray) - }); - } - - return a2sMenuItems; - }; - - const genMenuItems = (): Item[] => { - switch (pendingMappingType) { - case MappingType.ArrayToArray: - return a2aMenuItems; - case MappingType.ArrayToSingleton: - return genArrayToSingletonItems(); - case MappingType.ArrayToSingletonAggregate: - return genAggregateItems(); - default: - return defaultMenuItems; - } - }; - - const menuItems = genMenuItems(); - - if (pendingMappingType !== MappingType.ArrayToSingletonAggregate) { - menuItems.push({ - id: "a2a-a2s-custom-func", - label: getItemElement("a2a-a2s-custom-func", "Map Using Custom Function"), - onClick: wrapWithProgress(onClickMapWithCustomFn) - }); - if (pendingMappingType !== MappingType.ContainsUnions) { + + const defaultMenuItems: Item[] = [ + { + id: "a2a-direct", + label: getItemElement("direct", "Map Anyway"), + onClick: wrapWithProgress(onClickMapDirectly) + } + ]; + + const genAggregateItems = () => { + const aggregateFnsNumeric = ["sum", "avg", "min", "max"]; + const aggregateFnsString = ["string:'join"]; + const aggregateFnsCommon = ["first", "last"]; + + const sourcePort = link.getSourcePort() as InputOutputPortModel; + const sourceType = sourcePort.attributes.field.kind; + + const aggregateFns = [...(isNumericType(sourceType) ? aggregateFnsNumeric : + getGenericTypeKind(sourceType) === TypeKind.String ? aggregateFnsString : + []), + ...aggregateFnsCommon]; + const a2sAggregateItems: Item[] = aggregateFns.map((fn) => ({ + id: `a2s-collect-${fn}`, + label: getItemElement(`a2s-collect-${fn}`, `Aggregate using ${fn}`), + onClick: wrapWithProgress(async () => await onClickMapWithAggregateFn(fn)) + })); + return a2sAggregateItems; + }; + + const genArrayToSingletonItems = (): Item[] => { + const a2sMenuItems: Item[] = [ + { + id: "a2s-index", + label: getItemElement("a2s-index", "Extract Single Element from Array"), + onClick: wrapWithProgress(onClickMapArraysAccessSingleton) + } + + ]; + const sourcePort = link.getSourcePort() as InputOutputPortModel; + const targetPort = link.getTargetPort() as InputOutputPortModel; + const sourceMemberType = sourcePort.attributes.field.member?.kind; + const targetType = targetPort.attributes.field.kind; + + if (sourceMemberType === targetType && isPrimitive(sourceMemberType)) { + a2sMenuItems.push({ + id: "a2s-aggregate", + label: getItemElement("a2s-aggregate", "Aggregate and map"), + onClick: wrapWithProgress(onClickAggregateArray) + }); + } + + return a2sMenuItems; + }; + + const genMenuItems = (): Item[] => { + switch (pendingMappingType) { + case MappingType.ArrayToArray: + return a2aMenuItems; + case MappingType.ArrayToSingleton: + return genArrayToSingletonItems(); + case MappingType.ArrayToSingletonAggregate: + return genAggregateItems(); + default: + return defaultMenuItems; + } + }; + + const menuItems = genMenuItems(); + + if (pendingMappingType !== MappingType.ArrayToSingletonAggregate) { menuItems.push({ - id: "a2a-a2s-transform-func", - label: getItemElement("a2a-a2s-transform-func", "Map Using Transform Function"), - onClick: wrapWithProgress(onClickMapWithTransformFn) + id: "a2a-a2s-custom-func", + label: getItemElement("a2a-a2s-custom-func", "Map Using Custom Function"), + onClick: wrapWithProgress(onClickMapWithCustomFn) }); + if (pendingMappingType !== MappingType.ContainsUnions) { + menuItems.push({ + id: "a2a-a2s-transform-func", + label: getItemElement("a2a-a2s-transform-func", "Map Using Transform Function"), + onClick: wrapWithProgress(onClickMapWithTransformFn) + }); + } } - } + return menuItems; + }, [pendingMappingType, link, context]); return (
From c0f8a36863d877b85436cb812e7b17fd71cc7260 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 20 Nov 2025 17:47:51 +0530 Subject: [PATCH 03/15] Enhance IOType interface with isSeq property and refactor focus input handling in processArray and processTypeFields functions --- .../src/interfaces/data-mapper.ts | 1 + .../src/rpc-managers/data-mapper/utils.ts | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts index 19ebe1ea5d2..9124ab4929a 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts @@ -96,6 +96,7 @@ export interface IOType { defaultValue?: unknown; optional?: boolean; isFocused?: boolean; + isSeq?: boolean; isRecursive?: boolean; isDeepNested?: boolean; ref?: string; 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 36c87a3a3e1..e07b2fbf999 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 @@ -605,10 +605,7 @@ function processArray( parentId = member.name; fieldId = member.name; isFocused = true; - - if (model.traversingRoot){ - model.focusInputRootMap[parentId] = model.traversingRoot; - } + model.focusInputRootMap[fieldId] = model.traversingRoot; } } @@ -718,13 +715,26 @@ function processTypeFields( if (!type.fields) { return []; } return type.fields.map(field => { - const fieldId = generateFieldId(parentId, field.name!); + let fieldId = generateFieldId(parentId, field.name!); + + let isFocused = false; + if (model.focusInputs) { + const focusMember = model.focusInputs[fieldId]; + if (focusMember) { + field = focusMember; + fieldId = field.name; + isFocused = true; + model.focusInputRootMap[fieldId] = model.traversingRoot; + } + } + const ioType: IOType = { id: fieldId, name: field.name, displayName: field.displayName, typeName: field.typeName, kind: field.kind, + ...(isFocused && { isFocused }), ...(field.optional !== undefined && { optional: field.optional }), ...(field.typeInfo && { typeInfo: field.typeInfo }) }; From 73ad32e8a96b2da4d6645f5a4e08b25bb815f822 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Fri, 21 Nov 2025 09:28:04 +0530 Subject: [PATCH 04/15] Add groupById and isSeq properties to DMModel and IOTypeField and update related processing logic --- .../src/interfaces/data-mapper.ts | 4 +++- .../src/rpc-managers/data-mapper/utils.ts | 24 ++++++++++++++++++- .../components/Diagram/utils/type-utils.ts | 4 ++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts index 9124ab4929a..4cb36fcf30d 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts @@ -143,6 +143,7 @@ export interface DMModel { triggerRefresh?: boolean; traversingRoot?: string; focusInputRootMap?: Record; + groupById?: string; } export interface ModelState { @@ -177,6 +178,7 @@ export interface IOTypeField { optional?: boolean; ref?: string; focusExpression?: string; + isSeq?: boolean; typeInfo?: TypeInfo; } @@ -194,7 +196,7 @@ export interface Query { output: string, inputs: string[]; diagnostics?: DMDiagnostic[]; - fromClause: FromClause; + fromClause: IntermediateClause; intermediateClauses?: IntermediateClause[]; resultClause: ResultClause; } 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 e07b2fbf999..1c54b129e8d 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 @@ -34,11 +34,13 @@ import { IORoot, ExpandModelOptions, ExpandedDMModel, - MACHINE_VIEW + MACHINE_VIEW, + IntermediateClauseType } from "@wso2/ballerina-core"; import { updateSourceCode, UpdateSourceCodeRequest } from "../../utils"; import { StateMachine, updateDataMapperView } from "../../stateMachine"; import { VariableFindingVisitor } from "./VariableFindingVisitor"; +import { is } from "zod/v4/locales"; const MAX_NESTED_DEPTH = 4; @@ -598,6 +600,9 @@ function processArray( let fieldId = generateFieldId(parentId, member.name); let isFocused = false; + let isGroupByIdUpdated = false; + const prevGroupById = model.groupById; + if (model.focusInputs) { const focusMember = model.focusInputs[parentId]; if (focusMember) { @@ -606,6 +611,14 @@ function processArray( fieldId = member.name; isFocused = true; model.focusInputRootMap[fieldId] = model.traversingRoot; + + if(member.isSeq && model.query!.fromClause.properties.name === fieldId){ + const groupByClause = model.query!.intermediateClauses?.find(clause => clause.type === IntermediateClauseType.GROUP_BY); + if(groupByClause){ + model.groupById = groupByClause.properties.name; + isGroupByIdUpdated = true; + } + } } } @@ -622,6 +635,10 @@ function processArray( const typeSpecificProps = processTypeKind(member, parentId, model, visitedRefs); + if(isGroupByIdUpdated){ + model.groupById = prevGroupById; + } + return { ...ioType, ...typeSpecificProps @@ -718,6 +735,7 @@ function processTypeFields( let fieldId = generateFieldId(parentId, field.name!); let isFocused = false; + let isSeq = !!model.groupById; if (model.focusInputs) { const focusMember = model.focusInputs[fieldId]; if (focusMember) { @@ -725,6 +743,9 @@ function processTypeFields( fieldId = field.name; isFocused = true; model.focusInputRootMap[fieldId] = model.traversingRoot; + if (fieldId === model.groupById){ + isSeq = false; + } } } @@ -735,6 +756,7 @@ function processTypeFields( typeName: field.typeName, kind: field.kind, ...(isFocused && { isFocused }), + ...(isSeq && { isSeq }), ...(field.optional !== undefined && { optional: field.optional }), ...(field.typeInfo && { typeInfo: field.typeInfo }) }; diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/type-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/type-utils.ts index 93706781864..53b8913c3e0 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/type-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/type-utils.ts @@ -28,6 +28,10 @@ export function getTypeName(fieldType: IOType): string { } let typeName = fieldType?.typeName || fieldType.kind; + + if (fieldType.isSeq) { + return `...${typeName}`; + } return typeName; } From 674844e19204f91d2d8c858371dec1de3b13bb4f Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 15:26:49 +0530 Subject: [PATCH 05/15] Add SeqToPrimitive and SeqToArray mapping options --- .../Diagram/Label/MappingOptionsWidget.tsx | 22 ++++++++++--- .../Link/DataMapperLink/DataMapperLink.ts | 2 ++ .../components/Diagram/utils/common-utils.ts | 9 ++++++ .../Diagram/utils/modification-utils.ts | 31 ++++++++++++++++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx index 76383b86d58..402730e4293 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx @@ -25,7 +25,7 @@ import { css } from '@emotion/css'; import { MappingType } from '../Link'; import { ExpressionLabelModel } from './ExpressionLabelModel'; -import { createNewMapping, mapWithCustomFn, mapWithQuery, mapWithTransformFn } from '../utils/modification-utils'; +import { createNewMapping, mapSeqToPrimitive, mapWithCustomFn, mapWithQuery, mapWithTransformFn } from '../utils/modification-utils'; import classNames from 'classnames'; import { genArrayElementAccessSuffix } from '../utils/common-utils'; import { InputOutputPortModel } from '../Port'; @@ -133,6 +133,14 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { const onClickMapWithAggregateFn = async (fn: string) => { await createNewMapping(link, (expr: string) => `${fn}(${expr})`); } + + const onClickMapSeqToArray = async () => { + await createNewMapping(link, (expr: string) => `${expr}${genArrayElementAccessSuffix(link)}`); + } + + const onClickMapSeqToPrimitive = async (fn: string) => { + await mapSeqToPrimitive(link, context, (expr: string) => `${fn}(${expr})`); + } const getItemElement = (id: string, label: string) => { return ( @@ -167,7 +175,7 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { } ]; - const genAggregateItems = () => { + const genAggregateItems = (onClick: (fn: string) => Promise) => { const aggregateFnsNumeric = ["sum", "avg", "min", "max"]; const aggregateFnsString = ["string:'join"]; const aggregateFnsCommon = ["first", "last"]; @@ -182,7 +190,7 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { const a2sAggregateItems: Item[] = aggregateFns.map((fn) => ({ id: `a2s-collect-${fn}`, label: getItemElement(`a2s-collect-${fn}`, `Aggregate using ${fn}`), - onClick: wrapWithProgress(async () => await onClickMapWithAggregateFn(fn)) + onClick: wrapWithProgress(async () => await onClick(fn)) })); return a2sAggregateItems; }; @@ -219,7 +227,9 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { case MappingType.ArrayToSingleton: return genArrayToSingletonItems(); case MappingType.ArrayToSingletonAggregate: - return genAggregateItems(); + return genAggregateItems(onClickMapWithAggregateFn); + case MappingType.SeqToPrimitive: + return genAggregateItems(onClickMapSeqToPrimitive) default: return defaultMenuItems; } @@ -227,7 +237,9 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { const menuItems = genMenuItems(); - if (pendingMappingType !== MappingType.ArrayToSingletonAggregate) { + if (pendingMappingType !== MappingType.ArrayToSingletonAggregate && + pendingMappingType !== MappingType.SeqToPrimitive && + pendingMappingType !== MappingType.SeqToArray) { menuItems.push({ id: "a2a-a2s-custom-func", label: getItemElement("a2a-a2s-custom-func", "Map Using Custom Function"), diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Link/DataMapperLink/DataMapperLink.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/Link/DataMapperLink/DataMapperLink.ts index 1a2be0c0dce..50f122425d0 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Link/DataMapperLink/DataMapperLink.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Link/DataMapperLink/DataMapperLink.ts @@ -31,6 +31,8 @@ export enum MappingType { ArrayJoin = "array-join", Incompatible = "incompatible", ContainsUnions = "contains-unions", + SeqToPrimitive = "seq-primitive", + SeqToArray = "seq-array", Default = undefined // This is for non-array mappings currently } diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts index 1a54644bae1..8f6246b524d 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts @@ -95,6 +95,15 @@ export function getMappingType(sourcePort: PortModel, targetPort: PortModel): Ma if (sourceField.kind === TypeKind.Union || targetField.kind === TypeKind.Union) { return MappingType.ContainsUnions; } + + if (sourceField.isSeq) { + if (isPrimitive(targetField.kind)){ + return MappingType.SeqToPrimitive; + } + if (targetField.kind === TypeKind.Array) { + return MappingType.SeqToArray; + } + } const sourceDim = getDMTypeDim(sourceField); const targetDim = getDMTypeDim(targetField); diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index b234ba8abc5..1f428592b63 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -176,6 +176,36 @@ export async function mapWithQuery(link: DataMapperLinkModel, clauseType: Result expandArrayFn(context, [input], output, viewId); } + +export async function mapSeqToArray(link: DataMapperLinkModel, context: IDataMapperContext){ + const sourcePort = link.getSourcePort(); + const targetPort = link.getTargetPort(); + if (!sourcePort || !targetPort) { + return; + } + + const sourcePortModel = sourcePort as InputOutputPortModel; + +} + +export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDataMapperContext, modifier?: (expr: string) => string){ + const sourcePort = link.getSourcePort(); + const targetPort = link.getTargetPort(); + if (!sourcePort || !targetPort) { + return; + } + + const sourcePortModel = sourcePort as InputOutputPortModel; + + if (!sourcePortModel.attributes.field.isFocused){ + // need to handle add a let clause + } + + await createNewMapping(link, modifier); + + +} + export function mapWithJoin(link: DataMapperLinkModel) { const sourcePort = link.getSourcePort(); @@ -201,7 +231,6 @@ export function mapWithJoin(link: DataMapperLinkModel) { setIsQueryClausesPanelOpen(true); } - export function buildInputAccessExpr(fieldFqn: string): string { // Regular expression to match either quoted strings or non-quoted strings with dots const regex = /"([^"]+)"|'([^"]+)'|([^".]+)/g; From 66797c114ecf471497816a27eb648fa1e2c09843 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 18:25:22 +0530 Subject: [PATCH 06/15] Implement mapSeq functionality and update related interfaces in DataMapper components --- .../src/views/DataMapper/DataMapperView.tsx | 50 ++++++++++++++++++- .../DataMapper/DataMapperEditor.tsx | 2 + .../Diagram/utils/modification-utils.ts | 31 ++++++++++-- .../ballerina/data-mapper/src/index.tsx | 1 + .../DataMapperContext/DataMapperContext.ts | 4 +- 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index a4ac82b8760..dcea6a6e92f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -43,7 +43,8 @@ import { MACHINE_VIEW, VisualizerLocation, DeleteClauseRequest, - IORoot + IORoot, + IntermediateClauseType } from "@wso2/ballerina-core"; import { CompletionItem, ProgressIndicator } from "@wso2/ui-toolkit"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -491,6 +492,44 @@ export function DataMapperView(props: DataMapperProps) { setIsFileUpdateError(true); } }; + const mapSeq = async (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => { + try { + + const addClausesRequest: AddClausesRequest = { + filePath, + codedata: { + ...viewState.codedata, + isNew: true + }, + index: clauseIndex, + clause, + targetField: viewId, + varName: name, + subMappingName: viewState.subMappingName + }; + console.log(">>> [Data Mapper] addClauses request:", addClausesRequest); + + const resp = await rpcClient + .getDataMapperRpcClient() + .addClauses(addClausesRequest); + console.log(">>> [Data Mapper] addClauses response:", resp); + + const respGetSource = await rpcClient + .getDataMapperRpcClient() + .getDataMapperSource({ + filePath, + codedata: viewState.codedata, + varName: name, + targetField: viewId, + mapping, + subMappingName: viewState.subMappingName + }); + console.log(">>> [Data Mapper] mapSeq response:", respGetSource); + } catch (error) { + console.error(error); + setIsFileUpdateError(true); + } + }; const goToFunction = async (functionRange: LineRange) => { const documentUri: string = (await rpcClient.getVisualizerRpcClient().joinProjectPath({ segments: [functionRange.fileName] })).filePath; @@ -706,6 +745,7 @@ export function DataMapperView(props: DataMapperProps) { deleteSubMapping={deleteSubMapping} mapWithCustomFn={mapWithCustomFn} mapWithTransformFn={mapWithTransformFn} + mapSeq={mapSeq} goToFunction={goToFunction} enrichChildFields={enrichChildFields} undoRedoGroup={undoRedoGroup} @@ -727,7 +767,13 @@ export function DataMapperView(props: DataMapperProps) { }; const getModelSignature = (model: DMModel | ExpandedDMModel): ModelSignature => ({ - inputs: [...model.inputs.map(i => i.name), ...(model.query?.inputs || [])], + inputs: [...model.inputs.map(i => i.name), + ...(model.query?.inputs || []), + ...(model.query?.intermediateClauses + ?.filter((clause) => clause.type === IntermediateClauseType.LET) + .map(clause => `${clause.properties.type} ${clause.properties.name} ${clause.properties.expression}`) + || []) + ], output: model.output.name, subMappings: model.subMappings?.map(s => (s as IORoot | IOType).name) || [], refs: 'refs' in model ? JSON.stringify(model.refs) : '' diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 69bb98736d8..4bc37bf6da4 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -142,6 +142,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { getClausePosition, mapWithCustomFn, mapWithTransformFn, + mapSeq, goToFunction, enrichChildFields, undoRedoGroup @@ -238,6 +239,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { deleteSubMapping, mapWithCustomFn, mapWithTransformFn, + mapSeq, goToFunction, enrichChildFields ); diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index 1f428592b63..0e25f027eb6 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -188,7 +188,7 @@ export async function mapSeqToArray(link: DataMapperLinkModel, context: IDataMap } -export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDataMapperContext, modifier?: (expr: string) => string){ +export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDataMapperContext, modifier: (expr: string) => string){ const sourcePort = link.getSourcePort(); const targetPort = link.getTargetPort(); if (!sourcePort || !targetPort) { @@ -197,11 +197,34 @@ export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDat const sourcePortModel = sourcePort as InputOutputPortModel; - if (!sourcePortModel.attributes.field.isFocused){ - // need to handle add a let clause + if (sourcePortModel.attributes.field.isFocused){ + await createNewMapping(link, modifier); + } else { + const sourcePortModel = sourcePort as InputOutputPortModel; + const outputPortModel = targetPort as InputOutputPortModel; + + const input = sourcePortModel.attributes.fieldFQN; + const output = outputPortModel.attributes.fieldFQN; + const lastView = context.views[context.views.length - 1]; + const viewId = lastView.targetField; + + const mapping: Mapping = { + output: output, + expression: modifier(sourcePortModel.attributes.field.name) + }; + + const clause = { + type: IntermediateClauseType.LET, + properties: { + name: sourcePortModel.attributes.field.name, + type: "var", + expression: sourcePortModel.attributes.fieldFQN, + } + } + + await context.mapSeq(clause, -1, mapping, viewId); } - await createNewMapping(link, modifier); } diff --git a/workspaces/ballerina/data-mapper/src/index.tsx b/workspaces/ballerina/data-mapper/src/index.tsx index 27e713079cf..b3e62c847bf 100644 --- a/workspaces/ballerina/data-mapper/src/index.tsx +++ b/workspaces/ballerina/data-mapper/src/index.tsx @@ -74,6 +74,7 @@ export interface DataMapperEditorProps { deleteSubMapping: (index: number, viewId: string) => Promise; mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; + mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise; goToFunction: (functionRange: LineRange) => Promise; enrichChildFields: (parentField: IOType) => Promise; onRefresh: () => Promise; diff --git a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts index 0ad7a314d45..4f5944b8dfa 100644 --- a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts +++ b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts @@ -15,7 +15,7 @@ * specific language governing permissions and limitations * under the License. */ -import { FnMetadata, ExpandedDMModel, IOType, LineRange, Mapping, ResultClauseType } from "@wso2/ballerina-core"; +import { FnMetadata, ExpandedDMModel, IOType, LineRange, Mapping, ResultClauseType, IntermediateClause } from "@wso2/ballerina-core"; import { View } from "../../components/DataMapper/Views/DataMapperView"; export interface IDataMapperContext { @@ -30,6 +30,7 @@ export interface IDataMapperContext { deleteSubMapping: (index: number, viewId: string) => Promise; mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; + mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise; goToFunction: (functionRange: LineRange) => Promise; enrichChildFields: (parentField: IOType) => Promise; } @@ -48,6 +49,7 @@ export class DataMapperContext implements IDataMapperContext { public deleteSubMapping: (index: number, viewId: string) => Promise, public mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, + public mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise, public goToFunction: (functionRange: LineRange) => Promise, public enrichChildFields: (parentField: IOType) => Promise ){} From 617974948b2c3c52f8bf9e29db694e4325cc1d65 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 19:14:28 +0530 Subject: [PATCH 07/15] Add addClauses method to IDataMapperContext and update mapSeqToPrimitive implementation --- .../DataMapper/DataMapperEditor.tsx | 1 + .../Diagram/utils/modification-utils.ts | 23 +++++++------------ .../DataMapperContext/DataMapperContext.ts | 2 ++ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 4bc37bf6da4..6bfb39fdeb8 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -237,6 +237,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { convertToQuery, deleteMapping, deleteSubMapping, + addClauses, mapWithCustomFn, mapWithTransformFn, mapSeq, diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index 0e25f027eb6..e7c968f22e0 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -197,22 +197,11 @@ export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDat const sourcePortModel = sourcePort as InputOutputPortModel; - if (sourcePortModel.attributes.field.isFocused){ - await createNewMapping(link, modifier); - } else { - const sourcePortModel = sourcePort as InputOutputPortModel; - const outputPortModel = targetPort as InputOutputPortModel; - - const input = sourcePortModel.attributes.fieldFQN; - const output = outputPortModel.attributes.fieldFQN; + if (!sourcePortModel.attributes.field.isFocused){ + const lastView = context.views[context.views.length - 1]; const viewId = lastView.targetField; - const mapping: Mapping = { - output: output, - expression: modifier(sourcePortModel.attributes.field.name) - }; - const clause = { type: IntermediateClauseType.LET, properties: { @@ -222,11 +211,15 @@ export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDat } } - await context.mapSeq(clause, -1, mapping, viewId); + await context.addClauses(clause, viewId, true, -1); + + sourcePortModel.attributes.fieldFQN = clause.properties.name; + sourcePortModel.attributes.optionalOmittedFieldFQN = clause.properties.name; + } + await createNewMapping(link, modifier); - } export function mapWithJoin(link: DataMapperLinkModel) { diff --git a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts index 4f5944b8dfa..9c82ecb1997 100644 --- a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts +++ b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts @@ -28,6 +28,7 @@ export interface IDataMapperContext { convertToQuery: (mapping: Mapping, clauseType: ResultClauseType, viewId: string, name: string) => Promise; deleteMapping: (mapping: Mapping, viewId: string) => Promise; deleteSubMapping: (index: number, viewId: string) => Promise; + addClauses: (clause: IntermediateClause, targetField: string, isNew: boolean, index:number) => Promise; mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise; @@ -47,6 +48,7 @@ export class DataMapperContext implements IDataMapperContext { public convertToQuery: (mapping: Mapping, clauseType: ResultClauseType, viewId: string, name: string) => Promise, public deleteMapping: (mapping: Mapping, viewId: string) => Promise, public deleteSubMapping: (index: number, viewId: string) => Promise, + public addClauses: (clause: IntermediateClause, targetField: string, isNew: boolean, index:number) => Promise, public mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise, From d7160386a88ced92a2204d85857e09687726c145 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 19:16:54 +0530 Subject: [PATCH 08/15] Remove mapSeq method and its references from DataMapper components --- .../src/views/DataMapper/DataMapperView.tsx | 39 ------------------- .../DataMapper/DataMapperEditor.tsx | 2 - .../ballerina/data-mapper/src/index.tsx | 1 - .../DataMapperContext/DataMapperContext.ts | 2 - 4 files changed, 44 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index dcea6a6e92f..2da0fa61329 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -492,44 +492,6 @@ export function DataMapperView(props: DataMapperProps) { setIsFileUpdateError(true); } }; - const mapSeq = async (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => { - try { - - const addClausesRequest: AddClausesRequest = { - filePath, - codedata: { - ...viewState.codedata, - isNew: true - }, - index: clauseIndex, - clause, - targetField: viewId, - varName: name, - subMappingName: viewState.subMappingName - }; - console.log(">>> [Data Mapper] addClauses request:", addClausesRequest); - - const resp = await rpcClient - .getDataMapperRpcClient() - .addClauses(addClausesRequest); - console.log(">>> [Data Mapper] addClauses response:", resp); - - const respGetSource = await rpcClient - .getDataMapperRpcClient() - .getDataMapperSource({ - filePath, - codedata: viewState.codedata, - varName: name, - targetField: viewId, - mapping, - subMappingName: viewState.subMappingName - }); - console.log(">>> [Data Mapper] mapSeq response:", respGetSource); - } catch (error) { - console.error(error); - setIsFileUpdateError(true); - } - }; const goToFunction = async (functionRange: LineRange) => { const documentUri: string = (await rpcClient.getVisualizerRpcClient().joinProjectPath({ segments: [functionRange.fileName] })).filePath; @@ -745,7 +707,6 @@ export function DataMapperView(props: DataMapperProps) { deleteSubMapping={deleteSubMapping} mapWithCustomFn={mapWithCustomFn} mapWithTransformFn={mapWithTransformFn} - mapSeq={mapSeq} goToFunction={goToFunction} enrichChildFields={enrichChildFields} undoRedoGroup={undoRedoGroup} diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 6bfb39fdeb8..f6dec6ae66d 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -142,7 +142,6 @@ export function DataMapperEditor(props: DataMapperEditorProps) { getClausePosition, mapWithCustomFn, mapWithTransformFn, - mapSeq, goToFunction, enrichChildFields, undoRedoGroup @@ -240,7 +239,6 @@ export function DataMapperEditor(props: DataMapperEditorProps) { addClauses, mapWithCustomFn, mapWithTransformFn, - mapSeq, goToFunction, enrichChildFields ); diff --git a/workspaces/ballerina/data-mapper/src/index.tsx b/workspaces/ballerina/data-mapper/src/index.tsx index b3e62c847bf..27e713079cf 100644 --- a/workspaces/ballerina/data-mapper/src/index.tsx +++ b/workspaces/ballerina/data-mapper/src/index.tsx @@ -74,7 +74,6 @@ export interface DataMapperEditorProps { deleteSubMapping: (index: number, viewId: string) => Promise; mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; - mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise; goToFunction: (functionRange: LineRange) => Promise; enrichChildFields: (parentField: IOType) => Promise; onRefresh: () => Promise; diff --git a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts index 9c82ecb1997..878faf38c1e 100644 --- a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts +++ b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts @@ -31,7 +31,6 @@ export interface IDataMapperContext { addClauses: (clause: IntermediateClause, targetField: string, isNew: boolean, index:number) => Promise; mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; - mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise; goToFunction: (functionRange: LineRange) => Promise; enrichChildFields: (parentField: IOType) => Promise; } @@ -51,7 +50,6 @@ export class DataMapperContext implements IDataMapperContext { public addClauses: (clause: IntermediateClause, targetField: string, isNew: boolean, index:number) => Promise, public mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, - public mapSeq: (clause: IntermediateClause, clauseIndex: number, mapping: Mapping, viewId: string) => Promise, public goToFunction: (functionRange: LineRange) => Promise, public enrichChildFields: (parentField: IOType) => Promise ){} From 6cdec143fad306547e17c83ecedb18fbfcbbafa3 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 20:09:57 +0530 Subject: [PATCH 09/15] Implement genUniqueName --- .../src/views/DataMapper/DataMapperView.tsx | 38 +++++++++++++++++-- .../DataMapper/DataMapperEditor.tsx | 4 +- .../Diagram/utils/modification-utils.ts | 2 +- .../ballerina/data-mapper/src/index.tsx | 1 + .../DataMapperContext/DataMapperContext.ts | 4 +- 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index 2da0fa61329..f25b331456d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -44,7 +44,8 @@ import { VisualizerLocation, DeleteClauseRequest, IORoot, - IntermediateClauseType + IntermediateClauseType, + TriggerKind } from "@wso2/ballerina-core"; import { CompletionItem, ProgressIndicator } from "@wso2/ui-toolkit"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -541,11 +542,39 @@ export function DataMapperView(props: DataMapperProps) { parentField.isDeepNested = false; } + const genUniqueName = async (name: string, viewId: string): Promise => { + const { property } = await rpcClient.getDataMapperRpcClient().getProperty({ + filePath, + codedata: viewState.codedata, + targetField: viewId + }) + // TODO: need to handle undfined property case + const completions = await rpcClient.getBIDiagramRpcClient().getDataMapperCompletions({ + filePath, + context: { + expression: "", + startLine: property.codedata.lineRange.startLine, + lineOffset: 0, + offset: 0, + codedata: viewState.codedata, + property: property + }, + completionContext: { + triggerKind: TriggerKind.INVOKED + } + }); + let i = 2; + while (completions.some(c => c.insertText === name)) { + name = name + (i++); + } + + return name; + }; const onDMClose = () => { onClose ? onClose() : rpcClient.getVisualizerRpcClient()?.goBack(); - } + }; const onDMRefresh = async () => { try { @@ -574,7 +603,7 @@ export function DataMapperView(props: DataMapperProps) { }; rpcClient.getVisualizerRpcClient().openView({ type: EVENT_TYPE.OPEN_VIEW, location: context }); - } + }; useEffect(() => { @@ -622,7 +651,7 @@ export function DataMapperView(props: DataMapperProps) { property: property }, completionContext: { - triggerKind: triggerCharacter ? 2 : 1, + triggerKind: triggerCharacter ? TriggerKind.TRIGGER_CHARACTER : TriggerKind.INVOKED, triggerCharacter: triggerCharacter as TriggerCharacter } }); @@ -709,6 +738,7 @@ export function DataMapperView(props: DataMapperProps) { mapWithTransformFn={mapWithTransformFn} goToFunction={goToFunction} enrichChildFields={enrichChildFields} + genUniqueName={genUniqueName} undoRedoGroup={undoRedoGroup} expressionBar={{ completions: filteredCompletions, diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index f6dec6ae66d..70eeeb0669c 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -144,6 +144,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { mapWithTransformFn, goToFunction, enrichChildFields, + genUniqueName, undoRedoGroup } = props; const { @@ -240,7 +241,8 @@ export function DataMapperEditor(props: DataMapperEditorProps) { mapWithCustomFn, mapWithTransformFn, goToFunction, - enrichChildFields + enrichChildFields, + genUniqueName ); const ioNodeInitVisitor = new IONodeInitVisitor(context); diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index e7c968f22e0..a82964e2510 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -205,7 +205,7 @@ export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDat const clause = { type: IntermediateClauseType.LET, properties: { - name: sourcePortModel.attributes.field.name, + name: await context.genUniqueName(sourcePortModel.attributes.field.name, viewId), type: "var", expression: sourcePortModel.attributes.fieldFQN, } diff --git a/workspaces/ballerina/data-mapper/src/index.tsx b/workspaces/ballerina/data-mapper/src/index.tsx index 27e713079cf..b73c5a647fa 100644 --- a/workspaces/ballerina/data-mapper/src/index.tsx +++ b/workspaces/ballerina/data-mapper/src/index.tsx @@ -82,6 +82,7 @@ export interface DataMapperEditorProps { onEdit?: () => void; handleView: (viewId: string, isSubMapping?: boolean) => void; generateForm: (formProps: DMFormProps) => JSX.Element; + genUniqueName: (name: string, viewId: string) => Promise; undoRedoGroup: () => JSX.Element; } diff --git a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts index 878faf38c1e..504604e8229 100644 --- a/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts +++ b/workspaces/ballerina/data-mapper/src/utils/DataMapperContext/DataMapperContext.ts @@ -33,6 +33,7 @@ export interface IDataMapperContext { mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise; goToFunction: (functionRange: LineRange) => Promise; enrichChildFields: (parentField: IOType) => Promise; + genUniqueName: (name: string, viewId: string) => Promise; } export class DataMapperContext implements IDataMapperContext { @@ -51,6 +52,7 @@ export class DataMapperContext implements IDataMapperContext { public mapWithCustomFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public mapWithTransformFn: (mapping: Mapping, metadata: FnMetadata, viewId: string) => Promise, public goToFunction: (functionRange: LineRange) => Promise, - public enrichChildFields: (parentField: IOType) => Promise + public enrichChildFields: (parentField: IOType) => Promise, + public genUniqueName: (name: string, viewId: string) => Promise ){} } From b070385396928c8b9a9e6b79e9c05ee24a035d80 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 22 Nov 2025 20:19:46 +0530 Subject: [PATCH 10/15] Update mapSeqToPrimitive to adjust letClauseIndex based on GROUP_BY clause index --- .../src/components/Diagram/utils/modification-utils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index a82964e2510..f25abf35595 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -210,8 +210,13 @@ export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDat expression: sourcePortModel.attributes.fieldFQN, } } - - await context.addClauses(clause, viewId, true, -1); + + const groupByClauseIndex = context.model.query?.intermediateClauses + ?.findIndex(c => c.type === IntermediateClauseType.GROUP_BY); + + const letClauseIndex = (groupByClauseIndex !== -1 && groupByClauseIndex !== undefined) ? groupByClauseIndex - 1 : -1; + + await context.addClauses(clause, viewId, true, letClauseIndex); sourcePortModel.attributes.fieldFQN = clause.properties.name; sourcePortModel.attributes.optionalOmittedFieldFQN = clause.properties.name; From 8802f67d666f7ebf86f3802fc01f5ae598705b77 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 24 Nov 2025 14:44:40 +0530 Subject: [PATCH 11/15] Fix unique name generation in DataMapperView --- .../src/views/DataMapper/DataMapperView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index f25b331456d..66de3630a84 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -565,11 +565,12 @@ export function DataMapperView(props: DataMapperProps) { }); let i = 2; - while (completions.some(c => c.insertText === name)) { - name = name + (i++); + let uniqueName = name; + while (completions.some(c => c.insertText === uniqueName)) { + uniqueName = name + (i++); } - return name; + return uniqueName; }; const onDMClose = () => { From 5ca4fddf5a24d946ede91a710b383a5395f4989b Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 25 Nov 2025 16:41:30 +0530 Subject: [PATCH 12/15] Enhance GroupBy clause type processing and UI components --- .../src/views/DataMapper/DataMapperView.tsx | 2 +- .../components/DataMapper/DataMapperEditor.tsx | 1 + .../SidePanel/QueryClauses/ClauseEditor.tsx | 16 +++++++--------- .../SidePanel/QueryClauses/ClausesPanel.tsx | 17 +++++++++++++++-- .../components/Diagram/hooks/useDiagramModel.ts | 8 ++++++-- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index 66de3630a84..bfa5cbc74ec 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -762,7 +762,7 @@ const getModelSignature = (model: DMModel | ExpandedDMModel): ModelSignature => inputs: [...model.inputs.map(i => i.name), ...(model.query?.inputs || []), ...(model.query?.intermediateClauses - ?.filter((clause) => clause.type === IntermediateClauseType.LET) + ?.filter((clause) => (clause.type === IntermediateClauseType.LET || clause.type === IntermediateClauseType.GROUP_BY)) .map(clause => `${clause.properties.type} ${clause.properties.name} ${clause.properties.expression}`) || []) ], diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 70eeeb0669c..3e9e0118f76 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -367,6 +367,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { deleteClause={deleteClause} getClausePosition={getClausePosition} generateForm={generateForm} + genUniqueName={genUniqueName} /> )} diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx index 88c0d807a13..75a57a37094 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx @@ -77,11 +77,15 @@ export function ClauseEditor(props: ClauseEditorProps) { const expressionField: DMFormField = { key: "expression", - label: clauseType === IntermediateClauseType.JOIN ? "Join With Collection" : "Expression", + label: clauseType === IntermediateClauseType.JOIN ? "Join With Collection" : + clauseType === IntermediateClauseType.GROUP_BY ? "Grouping Key" : + "Expression", type: "EXPRESSION", optional: false, editable: true, - documentation: clauseType === IntermediateClauseType.JOIN ? "Collection to be joined" : "Enter the expression of the clause", + documentation: clauseType === IntermediateClauseType.JOIN ? "Collection to be joined" : + clauseType === IntermediateClauseType.GROUP_BY ? "Enter the grouping key expression" : + "Enter the expression of the clause", value: clauseProps?.expression ?? "", valueTypeConstraint: "Global", enabled: true, @@ -130,12 +134,6 @@ export function ClauseEditor(props: ClauseEditorProps) { type: clauseType as IntermediateClauseType, properties: data as IntermediateClauseProps }; - if (clauseType === IntermediateClauseType.JOIN) { - clause.properties.type = "var"; - clause.properties.isOuter = false; - } else if (clauseType === IntermediateClauseType.GROUP_BY) { - clause.properties.type = "var"; - } onSubmit(clause); } @@ -155,7 +153,7 @@ export function ClauseEditor(props: ClauseEditorProps) { case IntermediateClauseType.JOIN: return [expressionField, nameField, lhsExpressionField, rhsExpressionField]; case IntermediateClauseType.GROUP_BY: - return [nameField, expressionField]; + return [expressionField]; default: return [expressionField]; } diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx index 52d329b3f02..4826a1af99f 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClausesPanel.tsx @@ -23,7 +23,7 @@ import { useDMQueryClausesPanelStore } from "../../../../store/store"; import { AddButton, ClauseItem } from "./ClauseItem"; import { ClauseEditor } from "./ClauseEditor"; import { ClauseItemListContainer } from "./styles"; -import { DMFormProps, IntermediateClause, LinePosition, Query } from "@wso2/ballerina-core"; +import { DMFormProps, IntermediateClause, IntermediateClauseType, LinePosition, Query } from "@wso2/ballerina-core"; export interface ClausesPanelProps { query: Query; @@ -32,12 +32,13 @@ export interface ClausesPanelProps { deleteClause: (targetField: string, index: number) => Promise; getClausePosition: (targetField: string, index: number) => Promise; generateForm: (formProps: DMFormProps) => JSX.Element; + genUniqueName: (name: string, viewId: string) => Promise; } export function ClausesPanel(props: ClausesPanelProps) { const { isQueryClausesPanelOpen, setIsQueryClausesPanelOpen } = useDMQueryClausesPanelStore(); const { clauseToAdd, setClauseToAdd } = useDMQueryClausesPanelStore.getState(); - const { query, targetField, addClauses, deleteClause, getClausePosition, generateForm } = props; + const { query, targetField, addClauses, deleteClause, getClausePosition, generateForm , genUniqueName} = props; const [adding, setAdding] = React.useState(); const [editing, setEditing] = React.useState(); @@ -46,8 +47,20 @@ export function ClausesPanel(props: ClausesPanelProps) { const clauses = query?.intermediateClauses || []; + const fillDefaults = async (clause: IntermediateClause) => { + const clauseType = clause.type; + if (clauseType === IntermediateClauseType.JOIN) { + clause.properties.type = "var"; + clause.properties.isOuter = false; + } else if (clauseType === IntermediateClauseType.GROUP_BY) { + clause.properties.type = "var"; + clause.properties.name = await genUniqueName(clause.properties.expression.split('.').pop(), targetField); + } + }; + const setClauses = async (clause: IntermediateClause, isNew: boolean, index: number) => { setSaving(index); + await fillDefaults(clause); await addClauses(clause, targetField, isNew, index); setSaving(undefined); } diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/hooks/useDiagramModel.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/hooks/useDiagramModel.ts index b91569cbfad..309f4e9576f 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/hooks/useDiagramModel.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/hooks/useDiagramModel.ts @@ -26,7 +26,7 @@ import { useDMSearchStore } from "../../../store/store"; import { InputNode } from "../Node"; import { getErrorKind } from "../utils/common-utils"; import { OverlayLayerModel } from "../OverlayLayer/OverlayLayerModel"; -import { IOType } from "@wso2/ballerina-core"; +import { IntermediateClauseType, IOType } from "@wso2/ballerina-core"; import { useEffect } from "react"; export const useDiagramModel = ( @@ -48,6 +48,9 @@ export const useDiagramModel = ( const mappings = model?.mappings.map(mapping => mapping.output + ':' + mapping.expression).toString(); const subMappings = model?.subMappings?.map(mapping => (mapping as IOType).id).toString(); const queryIOs = model?.query ? model.query.inputs.toString() + ':' + model.query.output : ''; + const queryClauses = model?.query?.intermediateClauses + ?.filter(clause => clause.type === IntermediateClauseType.LET || clause.type === IntermediateClauseType.GROUP_BY) + .map(clause => clause.properties.name + ':' + clause.properties.expression).toString(); const collapsedFields = useDMCollapsedFieldsStore(state => state.fields); // Subscribe to collapsedFields const expandedFields = useDMExpandedFieldsStore(state => state.fields); // Subscribe to expandedFields const { inputSearch, outputSearch } = useDMSearchStore(); @@ -101,7 +104,8 @@ export const useDiagramModel = ( outputSearch, mappings, subMappings, - queryIOs + queryIOs, + queryClauses ], queryFn: genModel, networkMode: 'always', From def98fa2575f57b28e039d34071bbda28e608b00 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 25 Nov 2025 16:52:51 +0530 Subject: [PATCH 13/15] Add map seq to array option --- .../Diagram/Label/MappingOptionsWidget.tsx | 12 ++++-------- .../Diagram/Port/model/InputOutputPortModel.ts | 8 +++++++- .../src/components/Diagram/utils/common-utils.ts | 2 +- .../components/Diagram/utils/modification-utils.ts | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx index 402730e4293..e1d0e6c0a1a 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/Label/MappingOptionsWidget.tsx @@ -25,7 +25,7 @@ import { css } from '@emotion/css'; import { MappingType } from '../Link'; import { ExpressionLabelModel } from './ExpressionLabelModel'; -import { createNewMapping, mapSeqToPrimitive, mapWithCustomFn, mapWithQuery, mapWithTransformFn } from '../utils/modification-utils'; +import { createNewMapping, mapSeqToX, mapWithCustomFn, mapWithQuery, mapWithTransformFn } from '../utils/modification-utils'; import classNames from 'classnames'; import { genArrayElementAccessSuffix } from '../utils/common-utils'; import { InputOutputPortModel } from '../Port'; @@ -134,14 +134,10 @@ export function MappingOptionsWidget(props: MappingOptionsWidgetProps) { await createNewMapping(link, (expr: string) => `${fn}(${expr})`); } - const onClickMapSeqToArray = async () => { - await createNewMapping(link, (expr: string) => `${expr}${genArrayElementAccessSuffix(link)}`); - } - const onClickMapSeqToPrimitive = async (fn: string) => { - await mapSeqToPrimitive(link, context, (expr: string) => `${fn}(${expr})`); + await mapSeqToX(link, context, (expr: string) => `${fn}(${expr})`); } - + const getItemElement = (id: string, label: string) => { return (
`[${expr}]`); + return; + } await createNewMapping(lm); }) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts index 8f6246b524d..5384e0e195a 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/common-utils.ts @@ -65,7 +65,7 @@ export function hasChildMappingsForInput(mappings: Mapping[], inputId: string): } export function isPendingMappingRequired(mappingType: MappingType): boolean { - return mappingType !== MappingType.Default; + return mappingType !== MappingType.Default && mappingType !== MappingType.SeqToArray; } export function getMappingType(sourcePort: PortModel, targetPort: PortModel): MappingType { diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts index f25abf35595..d211e18f87a 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/utils/modification-utils.ts @@ -188,7 +188,7 @@ export async function mapSeqToArray(link: DataMapperLinkModel, context: IDataMap } -export async function mapSeqToPrimitive(link: DataMapperLinkModel, context: IDataMapperContext, modifier: (expr: string) => string){ +export async function mapSeqToX(link: DataMapperLinkModel, context: IDataMapperContext, modifier: (expr: string) => string){ const sourcePort = link.getSourcePort(); const targetPort = link.getTargetPort(); if (!sourcePort || !targetPort) { From 1b7ff3655d5ca4753fb68c69cb01bcd8b4f6c347 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 27 Nov 2025 13:23:31 +0530 Subject: [PATCH 14/15] Remove unused imports from utils.ts --- .../ballerina-extension/src/rpc-managers/data-mapper/utils.ts | 4 ---- 1 file changed, 4 deletions(-) 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 1c54b129e8d..b3357ad2696 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 @@ -19,9 +19,6 @@ import { CodeData, ELineRange, Flow, - AllDataMapperSourceRequest, - DataMapperSourceRequest, - DataMapperSourceResponse, NodePosition, ProjectStructureArtifactResponse, TextEdit, @@ -40,7 +37,6 @@ import { import { updateSourceCode, UpdateSourceCodeRequest } from "../../utils"; import { StateMachine, updateDataMapperView } from "../../stateMachine"; import { VariableFindingVisitor } from "./VariableFindingVisitor"; -import { is } from "zod/v4/locales"; const MAX_NESTED_DEPTH = 4; From 92af0db9403a07de45fe36bbfbd7ea5f0f33c115 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 27 Nov 2025 13:32:04 +0530 Subject: [PATCH 15/15] Improve error handling in DataMapperView for position data retrieval --- .../src/views/DataMapper/DataMapperView.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index bfa5cbc74ec..804d1222d74 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -370,9 +370,14 @@ export function DataMapperView(props: DataMapperProps) { targetField: targetField, index: index }); - return position; + if (position) { + return position; + } else { + throw new Error("Clause position not found"); + } } catch (error) { console.error(error); + return { line: 0, offset: 0 }; } } @@ -548,7 +553,12 @@ export function DataMapperView(props: DataMapperProps) { codedata: viewState.codedata, targetField: viewId }) - // TODO: need to handle undfined property case + + if (!property?.codedata?.lineRange?.startLine) { + console.error("Failed to get start line for generating unique name"); + return name; + } + const completions = await rpcClient.getBIDiagramRpcClient().getDataMapperCompletions({ filePath, context: {