Skip to content

Commit 2726b59

Browse files
Merge branch 'main' into check-error
2 parents 65511b3 + e007ce8 commit 2726b59

File tree

18 files changed

+401
-196
lines changed

18 files changed

+401
-196
lines changed

workspaces/ballerina/ballerina-core/src/interfaces/data-mapper.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ export enum IntermediateClauseType {
5757
LET = "let",
5858
WHERE = "where",
5959
FROM = "from",
60-
ORDER_BY = "order by",
60+
ORDER_BY = "order-by",
6161
LIMIT = "limit",
6262
JOIN = "join",
63+
GROUP_BY = "group-by"
6364
}
6465

6566
export enum ResultClauseType {
@@ -95,6 +96,7 @@ export interface IOType {
9596
defaultValue?: unknown;
9697
optional?: boolean;
9798
isFocused?: boolean;
99+
isSeq?: boolean;
98100
isRecursive?: boolean;
99101
isDeepNested?: boolean;
100102
ref?: string;
@@ -141,6 +143,7 @@ export interface DMModel {
141143
triggerRefresh?: boolean;
142144
traversingRoot?: string;
143145
focusInputRootMap?: Record<string, string>;
146+
groupById?: string;
144147
}
145148

146149
export interface ModelState {
@@ -175,6 +178,7 @@ export interface IOTypeField {
175178
optional?: boolean;
176179
ref?: string;
177180
focusExpression?: string;
181+
isSeq?: boolean;
178182
typeInfo?: TypeInfo;
179183
}
180184

@@ -192,7 +196,7 @@ export interface Query {
192196
output: string,
193197
inputs: string[];
194198
diagnostics?: DMDiagnostic[];
195-
fromClause: FromClause;
199+
fromClause: IntermediateClause;
196200
intermediateClauses?: IntermediateClause[];
197201
resultClause: ResultClause;
198202
}

workspaces/ballerina/ballerina-extension/src/rpc-managers/data-mapper/utils.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ import {
1919
CodeData,
2020
ELineRange,
2121
Flow,
22-
AllDataMapperSourceRequest,
23-
DataMapperSourceRequest,
24-
DataMapperSourceResponse,
2522
NodePosition,
2623
ProjectStructureArtifactResponse,
2724
TextEdit,
@@ -34,7 +31,8 @@ import {
3431
IORoot,
3532
ExpandModelOptions,
3633
ExpandedDMModel,
37-
MACHINE_VIEW
34+
MACHINE_VIEW,
35+
IntermediateClauseType
3836
} from "@wso2/ballerina-core";
3937
import { updateSourceCode, UpdateSourceCodeRequest } from "../../utils";
4038
import { StateMachine, updateDataMapperView } from "../../stateMachine";
@@ -598,16 +596,24 @@ function processArray(
598596
let fieldId = generateFieldId(parentId, member.name);
599597

600598
let isFocused = false;
599+
let isGroupByIdUpdated = false;
600+
const prevGroupById = model.groupById;
601+
601602
if (model.focusInputs) {
602603
const focusMember = model.focusInputs[parentId];
603604
if (focusMember) {
604605
member = focusMember;
605606
parentId = member.name;
606607
fieldId = member.name;
607608
isFocused = true;
609+
model.focusInputRootMap[fieldId] = model.traversingRoot;
608610

609-
if (model.traversingRoot){
610-
model.focusInputRootMap[parentId] = model.traversingRoot;
611+
if(member.isSeq && model.query!.fromClause.properties.name === fieldId){
612+
const groupByClause = model.query!.intermediateClauses?.find(clause => clause.type === IntermediateClauseType.GROUP_BY);
613+
if(groupByClause){
614+
model.groupById = groupByClause.properties.name;
615+
isGroupByIdUpdated = true;
616+
}
611617
}
612618
}
613619
}
@@ -625,6 +631,10 @@ function processArray(
625631

626632
const typeSpecificProps = processTypeKind(member, parentId, model, visitedRefs);
627633

634+
if(isGroupByIdUpdated){
635+
model.groupById = prevGroupById;
636+
}
637+
628638
return {
629639
...ioType,
630640
...typeSpecificProps
@@ -718,13 +728,31 @@ function processTypeFields(
718728
if (!type.fields) { return []; }
719729

720730
return type.fields.map(field => {
721-
const fieldId = generateFieldId(parentId, field.name!);
731+
let fieldId = generateFieldId(parentId, field.name!);
732+
733+
let isFocused = false;
734+
let isSeq = !!model.groupById;
735+
if (model.focusInputs) {
736+
const focusMember = model.focusInputs[fieldId];
737+
if (focusMember) {
738+
field = focusMember;
739+
fieldId = field.name;
740+
isFocused = true;
741+
model.focusInputRootMap[fieldId] = model.traversingRoot;
742+
if (fieldId === model.groupById){
743+
isSeq = false;
744+
}
745+
}
746+
}
747+
722748
const ioType: IOType = {
723749
id: fieldId,
724750
name: field.name,
725751
displayName: field.displayName,
726752
typeName: field.typeName,
727753
kind: field.kind,
754+
...(isFocused && { isFocused }),
755+
...(isSeq && { isSeq }),
728756
...(field.optional !== undefined && { optional: field.optional }),
729757
...(field.typeInfo && { typeInfo: field.typeInfo })
730758
};

workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ import {
4343
MACHINE_VIEW,
4444
VisualizerLocation,
4545
DeleteClauseRequest,
46-
IORoot
46+
IORoot,
47+
IntermediateClauseType,
48+
TriggerKind
4749
} from "@wso2/ballerina-core";
4850
import { CompletionItem, ProgressIndicator } from "@wso2/ui-toolkit";
4951
import { useRpcContext } from "@wso2/ballerina-rpc-client";
@@ -368,9 +370,14 @@ export function DataMapperView(props: DataMapperProps) {
368370
targetField: targetField,
369371
index: index
370372
});
371-
return position;
373+
if (position) {
374+
return position;
375+
} else {
376+
throw new Error("Clause position not found");
377+
}
372378
} catch (error) {
373379
console.error(error);
380+
return { line: 0, offset: 0 };
374381
}
375382
}
376383

@@ -540,11 +547,45 @@ export function DataMapperView(props: DataMapperProps) {
540547
parentField.isDeepNested = false;
541548
}
542549

550+
const genUniqueName = async (name: string, viewId: string): Promise<string> => {
551+
const { property } = await rpcClient.getDataMapperRpcClient().getProperty({
552+
filePath,
553+
codedata: viewState.codedata,
554+
targetField: viewId
555+
})
556+
557+
if (!property?.codedata?.lineRange?.startLine) {
558+
console.error("Failed to get start line for generating unique name");
559+
return name;
560+
}
561+
562+
const completions = await rpcClient.getBIDiagramRpcClient().getDataMapperCompletions({
563+
filePath,
564+
context: {
565+
expression: "",
566+
startLine: property.codedata.lineRange.startLine,
567+
lineOffset: 0,
568+
offset: 0,
569+
codedata: viewState.codedata,
570+
property: property
571+
},
572+
completionContext: {
573+
triggerKind: TriggerKind.INVOKED
574+
}
575+
});
576+
577+
let i = 2;
578+
let uniqueName = name;
579+
while (completions.some(c => c.insertText === uniqueName)) {
580+
uniqueName = name + (i++);
581+
}
543582

583+
return uniqueName;
584+
};
544585

545586
const onDMClose = () => {
546587
onClose ? onClose() : rpcClient.getVisualizerRpcClient()?.goBack();
547-
}
588+
};
548589

549590
const onDMRefresh = async () => {
550591
try {
@@ -573,7 +614,7 @@ export function DataMapperView(props: DataMapperProps) {
573614
};
574615

575616
rpcClient.getVisualizerRpcClient().openView({ type: EVENT_TYPE.OPEN_VIEW, location: context });
576-
}
617+
};
577618

578619

579620
useEffect(() => {
@@ -621,7 +662,7 @@ export function DataMapperView(props: DataMapperProps) {
621662
property: property
622663
},
623664
completionContext: {
624-
triggerKind: triggerCharacter ? 2 : 1,
665+
triggerKind: triggerCharacter ? TriggerKind.TRIGGER_CHARACTER : TriggerKind.INVOKED,
625666
triggerCharacter: triggerCharacter as TriggerCharacter
626667
}
627668
});
@@ -708,6 +749,7 @@ export function DataMapperView(props: DataMapperProps) {
708749
mapWithTransformFn={mapWithTransformFn}
709750
goToFunction={goToFunction}
710751
enrichChildFields={enrichChildFields}
752+
genUniqueName={genUniqueName}
711753
undoRedoGroup={undoRedoGroup}
712754
expressionBar={{
713755
completions: filteredCompletions,
@@ -727,7 +769,13 @@ export function DataMapperView(props: DataMapperProps) {
727769
};
728770

729771
const getModelSignature = (model: DMModel | ExpandedDMModel): ModelSignature => ({
730-
inputs: [...model.inputs.map(i => i.name), ...(model.query?.inputs || [])],
772+
inputs: [...model.inputs.map(i => i.name),
773+
...(model.query?.inputs || []),
774+
...(model.query?.intermediateClauses
775+
?.filter((clause) => (clause.type === IntermediateClauseType.LET || clause.type === IntermediateClauseType.GROUP_BY))
776+
.map(clause => `${clause.properties.type} ${clause.properties.name} ${clause.properties.expression}`)
777+
|| [])
778+
],
731779
output: model.output.name,
732780
subMappings: model.subMappings?.map(s => (s as IORoot | IOType).name) || [],
733781
refs: 'refs' in model ? JSON.stringify(model.refs) : ''

workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) {
144144
mapWithTransformFn,
145145
goToFunction,
146146
enrichChildFields,
147+
genUniqueName,
147148
undoRedoGroup
148149
} = props;
149150
const {
@@ -236,10 +237,12 @@ export function DataMapperEditor(props: DataMapperEditorProps) {
236237
convertToQuery,
237238
deleteMapping,
238239
deleteSubMapping,
240+
addClauses,
239241
mapWithCustomFn,
240242
mapWithTransformFn,
241243
goToFunction,
242-
enrichChildFields
244+
enrichChildFields,
245+
genUniqueName
243246
);
244247

245248
const ioNodeInitVisitor = new IONodeInitVisitor(context);
@@ -364,6 +367,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) {
364367
deleteClause={deleteClause}
365368
getClausePosition={getClausePosition}
366369
generateForm={generateForm}
370+
genUniqueName={genUniqueName}
367371
/>
368372
)}
369373
</>

workspaces/ballerina/data-mapper/src/components/DataMapper/Header/ExpressionBar.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,26 @@ export default function ExpressionBarWrapper({ views }: ExpressionBarProps) {
110110
if (inputPort) {
111111
// Keep the text field focused when an input port is selected
112112
if (textFieldRef.current) {
113+
113114
if (focusedPort || focusedFilter) {
114-
textFieldRef.current.focus(true);
115+
// Update the expression text when an input port is selected
116+
const cursorPosition = textFieldRef.current.inputElement.selectionStart;
117+
118+
const inputAccessExpr = buildInputAccessExpr(inputPort.attributes.fieldFQN);
119+
const updatedText =
120+
textFieldValue.substring(0, cursorPosition) +
121+
inputAccessExpr +
122+
textFieldValue.substring(cursorPosition);
123+
await handleChange(updatedText);
124+
125+
textFieldRef.current.setCursor(updatedText, cursorPosition + inputAccessExpr.length);
126+
115127
} else {
116128
textFieldRef.current.blur();
117129
}
118-
119-
// Update the expression text when an input port is selected
120-
const cursorPosition = textFieldRef.current.shadowRoot.querySelector('input').selectionStart;
121-
const inputAccessExpr = buildInputAccessExpr(inputPort.attributes.fieldFQN);
122-
const updatedText =
123-
textFieldValue.substring(0, cursorPosition) +
124-
inputAccessExpr +
125-
textFieldValue.substring(cursorPosition);
126-
await handleChange(updatedText);
130+
127131
resetInputPort();
132+
128133
}
129134
}
130135
})();
@@ -161,7 +166,7 @@ export default function ExpressionBarWrapper({ views }: ExpressionBarProps) {
161166

162167
return disabled;
163168
// eslint-disable-next-line react-hooks/exhaustive-deps
164-
}, [textFieldRef.current, focusedPort, focusedFilter, views]);
169+
}, [focusedPort, focusedFilter, views]);
165170

166171
const handleChange = async (text: string, cursorPosition?: number) => {
167172
if (textFieldValue === text) {

workspaces/ballerina/data-mapper/src/components/DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ export function ClauseEditor(props: ClauseEditorProps) {
4242

4343
const [clauseType, setClauseType] = React.useState<string>(_clauseType ?? IntermediateClauseType.WHERE);
4444
const clauseTypeItems: OptionProps[] = [
45-
{ content: "condition", value: IntermediateClauseType.WHERE },
46-
{ content: "local variable", value: IntermediateClauseType.LET },
47-
{ content: "sort by", value: IntermediateClauseType.ORDER_BY },
48-
{ content: "limit", value: IntermediateClauseType.LIMIT },
49-
{ content: "from", value: IntermediateClauseType.FROM },
50-
{ content: "join", value: IntermediateClauseType.JOIN },
45+
{ content: "Condition", value: IntermediateClauseType.WHERE },
46+
{ content: "Local variable", value: IntermediateClauseType.LET },
47+
{ content: "Sort by", value: IntermediateClauseType.ORDER_BY },
48+
{ content: "Limit", value: IntermediateClauseType.LIMIT },
49+
{ content: "From", value: IntermediateClauseType.FROM },
50+
{ content: "Join", value: IntermediateClauseType.JOIN },
51+
{ content: "Group by", value: IntermediateClauseType.GROUP_BY }
5152
]
5253

5354
const nameField: DMFormField = {
@@ -76,11 +77,15 @@ export function ClauseEditor(props: ClauseEditorProps) {
7677

7778
const expressionField: DMFormField = {
7879
key: "expression",
79-
label: clauseType === IntermediateClauseType.JOIN ? "Join With Collection" : "Expression",
80+
label: clauseType === IntermediateClauseType.JOIN ? "Join With Collection" :
81+
clauseType === IntermediateClauseType.GROUP_BY ? "Grouping Key" :
82+
"Expression",
8083
type: "EXPRESSION",
8184
optional: false,
8285
editable: true,
83-
documentation: clauseType === IntermediateClauseType.JOIN ? "Collection to be joined" : "Enter the expression of the clause",
86+
documentation: clauseType === IntermediateClauseType.JOIN ? "Collection to be joined" :
87+
clauseType === IntermediateClauseType.GROUP_BY ? "Enter the grouping key expression" :
88+
"Enter the expression of the clause",
8489
value: clauseProps?.expression ?? "",
8590
valueTypeConstraint: "Global",
8691
enabled: true,
@@ -129,10 +134,6 @@ export function ClauseEditor(props: ClauseEditorProps) {
129134
type: clauseType as IntermediateClauseType,
130135
properties: data as IntermediateClauseProps
131136
};
132-
if (clauseType === IntermediateClauseType.JOIN) {
133-
clause.properties.type = "var";
134-
clause.properties.isOuter = false;
135-
}
136137
onSubmit(clause);
137138
}
138139

@@ -151,6 +152,8 @@ export function ClauseEditor(props: ClauseEditorProps) {
151152
return [expressionField, orderField];
152153
case IntermediateClauseType.JOIN:
153154
return [expressionField, nameField, lhsExpressionField, rhsExpressionField];
155+
case IntermediateClauseType.GROUP_BY:
156+
return [expressionField];
154157
default:
155158
return [expressionField];
156159
}

0 commit comments

Comments
 (0)