Skip to content

Commit 7c3b112

Browse files
Merge branch 'main' into bi-dm-undo-fix
2 parents aa9e164 + ee28dda commit 7c3b112

File tree

15 files changed

+735
-89
lines changed

15 files changed

+735
-89
lines changed

workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export interface ProjectImports {
121121
// Data-mapper related interfaces
122122
export interface MetadataWithAttachments {
123123
metadata: ExtendedDataMapperMetadata;
124-
attachments?: Attachment[];
124+
attachments: Attachment[];
125125
}
126126

127127
export interface InlineMappingsSourceResult {

workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ import { DefaultableParam, FunctionDefinition, IncludedRecordParam, ModulePart,
2929
import { addMissingRequiredFields, attemptRepairProject, checkProjectDiagnostics } from "../../../src/rpc-managers/ai-panel/repair-utils";
3030
import { NullablePrimitiveType, PrimitiveArrayType, PrimitiveType } from "./constants";
3131
import { INVALID_RECORD_REFERENCE } from "../../../src/views/ai-panel/errorCodes";
32-
import { PackageInfo, TypesGenerationResult } from "./service/datamapper/types";
32+
import { CodeRepairResult, PackageInfo, TypesGenerationResult } from "./service/datamapper/types";
3333
import { URI } from "vscode-uri";
3434
import { getAllDataMapperSource } from "./service/datamapper/datamapper";
3535
import { StateMachine } from "../../stateMachine";
36+
import { CopilotEventHandler } from "./service/event";
3637

3738
// Set to false to include mappings with default values
3839
const OMIT_DEFAULT_MAPPINGS_ENABLED = true;
@@ -473,13 +474,15 @@ export async function createTempFileAndGenerateMetadata(params: CreateTempFileRe
473474

474475
export async function generateMappings(
475476
metadataWithAttachments: MetadataWithAttachments,
476-
context: any
477+
context: any,
478+
eventHandler: CopilotEventHandler
477479
): Promise<AllDataMapperSourceRequest> {
478480
const targetFilePath = metadataWithAttachments.metadata.codeData.lineRange.fileName || context.documentUri;
479481

480482
const generatedMappings = await generateMappingExpressionsFromModel(
481483
metadataWithAttachments.metadata.mappingsModel as DMModel,
482-
metadataWithAttachments.attachments || []
484+
metadataWithAttachments.attachments || [],
485+
eventHandler
483486
);
484487

485488
const customFunctionMappings = generatedMappings.filter(mapping => mapping.isFunctionCall);
@@ -1060,6 +1063,7 @@ export async function generateInlineMappingsSource(
10601063
inlineMappingRequest: MetadataWithAttachments,
10611064
langClient: ExtendedLangClient,
10621065
context: any,
1066+
eventHandler: CopilotEventHandler
10631067
): Promise<InlineMappingsSourceResult> {
10641068
if (!inlineMappingRequest) {
10651069
throw new Error("Inline mapping request is required");
@@ -1096,16 +1100,18 @@ export async function generateInlineMappingsSource(
10961100

10971101
// Prepare mapping request payload
10981102
const mappingRequestPayload: MetadataWithAttachments = {
1099-
metadata: tempFileMetadata
1103+
metadata: tempFileMetadata,
1104+
attachments: []
11001105
};
1101-
if (inlineMappingRequest.attachments && inlineMappingRequest.attachments.length > 0) {
1106+
if (inlineMappingRequest.attachments.length > 0) {
11021107
mappingRequestPayload.attachments = inlineMappingRequest.attachments;
11031108
}
11041109

11051110
// Generate mappings and source code
11061111
const allMappingsRequest = await generateMappings(
11071112
mappingRequestPayload,
1108-
context
1113+
context,
1114+
eventHandler
11091115
);
11101116

11111117
const generatedSourceResponse = await getAllDataMapperSource(allMappingsRequest);
@@ -1315,7 +1321,7 @@ export async function repairCodeAndGetUpdatedContent(
13151321
params: RepairCodeParams,
13161322
langClient: ExtendedLangClient,
13171323
projectRoot: string
1318-
): Promise<{ finalContent: string; customFunctionsContent: string }> {
1324+
): Promise<CodeRepairResult> {
13191325

13201326
// Read main file content
13211327
let finalContent = fs.readFileSync(params.tempFileMetadata.codeData.lineRange.fileName, 'utf8');

workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts

Lines changed: 417 additions & 48 deletions
Large diffs are not rendered by default.

workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ export interface RepairedFiles {
5353
repairedFiles: SourceFile[];
5454
}
5555

56+
export interface CodeRepairResult {
57+
finalContent: string;
58+
customFunctionsContent: string;
59+
}
60+
5661
// =============================================================================
5762
// MAPPING HINTS
5863
// =============================================================================

workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/repair-utils.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,98 @@ export async function addMissingRequiredFields(
330330

331331
return projectModified;
332332
}
333+
334+
export async function addCheckExpressionErrors(
335+
diagnosticsResult: Diagnostics[],
336+
langClient: ExtendedLangClient
337+
): Promise<boolean> {
338+
let projectModified = false;
339+
340+
for (const diag of diagnosticsResult) {
341+
const fileUri = diag.uri;
342+
const diagnostics = diag.diagnostics;
343+
344+
// Filter BCE3032 diagnostics (check expression errors)
345+
const checkExprDiagnostics = diagnostics.filter(d => d.code === "BCE3032");
346+
if (!checkExprDiagnostics.length) {
347+
continue;
348+
}
349+
350+
const astModifications: STModification[] = [];
351+
352+
// Process each diagnostic individually
353+
for (const diagnostic of checkExprDiagnostics) {
354+
try {
355+
// Get code actions for the diagnostic
356+
const codeActions = await langClient.codeAction({
357+
textDocument: { uri: fileUri },
358+
range: {
359+
start: diagnostic.range.start,
360+
end: diagnostic.range.end
361+
},
362+
context: {
363+
diagnostics: [diagnostic],
364+
only: ['quickfix'],
365+
triggerKind: 1
366+
}
367+
});
368+
369+
if (!codeActions?.length) {
370+
console.warn(`No code actions returned for ${fileUri} at line ${diagnostic.range.start.line}`);
371+
continue;
372+
}
373+
374+
// Find the action that adds error to return type
375+
// The language server typically provides actions like "Change return type to ..."
376+
const action = codeActions.find(
377+
action => action.title && (
378+
action.title.toLowerCase().includes("change") &&
379+
action.title.toLowerCase().includes("return") &&
380+
action.title.toLowerCase().includes("error")
381+
)
382+
);
383+
384+
if (!action?.edit?.documentChanges?.length) {
385+
continue;
386+
}
387+
388+
const docEdit = action.edit.documentChanges[0] as TextDocumentEdit;
389+
390+
// Process all edits from the code action
391+
for (const edit of docEdit.edits) {
392+
astModifications.push({
393+
startLine: edit.range.start.line,
394+
startColumn: edit.range.start.character,
395+
endLine: edit.range.end.line,
396+
endColumn: edit.range.end.character,
397+
type: "INSERT",
398+
isImport: false,
399+
config: { STATEMENT: edit.newText }
400+
});
401+
}
402+
} catch (err) {
403+
console.warn(`Could not apply code action for ${fileUri} at line ${diagnostic.range.start.line}:`, err);
404+
}
405+
}
406+
407+
// Apply modifications to syntax tree
408+
if (astModifications.length > 0) {
409+
const syntaxTree = await langClient.stModify({
410+
documentIdentifier: { uri: fileUri },
411+
astModifications: astModifications
412+
});
413+
414+
// Update file content
415+
const { source } = syntaxTree as SyntaxTree;
416+
if (!source) {
417+
// Handle the case where source is undefined, when compiler issue occurs
418+
return false;
419+
}
420+
const absolutePath = fileURLToPath(fileUri);
421+
writeBallerinaFileDidOpenTemp(absolutePath, source);
422+
projectModified = true;
423+
}
424+
}
425+
426+
return projectModified;
427+
}

workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,23 @@ export class AiPanelRpcManager implements AIPanelAPI {
673673
throw new Error("Not a Ballerina project.");
674674
}
675675
await addToIntegration(projectPath, params.fileChanges);
676-
updateView();
676+
677+
const context = StateMachine.context();
678+
const dataMapperMetadata = context.dataMapperMetadata;
679+
if (!dataMapperMetadata || !dataMapperMetadata.codeData) {
680+
updateView();
681+
return true;
682+
}
683+
684+
// Refresh data mapper with the updated code
685+
let filePath = dataMapperMetadata.codeData.lineRange?.fileName;
686+
const varName = dataMapperMetadata.name;
687+
if (!filePath || !varName) {
688+
updateView();
689+
return true;
690+
}
691+
692+
await refreshDataMapper(filePath, dataMapperMetadata.codeData, varName);
677693
return true;
678694
} catch (error) {
679695
console.error(">>> Failed to add files to the project", error);

workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { getAskResponse } from "../../../src/features/ai/service/ask/ask";
2828
import { MappingFileRecord} from "./types";
2929
import { generateAutoMappings, generateRepairCode } from "../../../src/features/ai/service/datamapper/datamapper";
3030
import { ArtifactNotificationHandler, ArtifactsUpdated } from "../../utils/project-artifacts-handler";
31+
import { CopilotEventHandler } from "../../../src/features/ai/service/event";
3132

3233
// const BACKEND_BASE_URL = BACKEND_URL.replace(/\/v2\.0$/, "");
3334
//TODO: Temp workaround as custom domain seem to block file uploads
@@ -154,15 +155,18 @@ async function convertAttachmentToFileData(attachment: Attachment): Promise<File
154155
// Processes data mapper model and optional mapping instruction files to generate mapping expressions
155156
export async function generateMappingExpressionsFromModel(
156157
dataMapperModel: DMModel,
157-
mappingInstructionFiles: Attachment[] = []
158+
mappingInstructionFiles: Attachment[] = [],
159+
eventHandler: CopilotEventHandler
158160
): Promise<Mapping[]> {
159161
let dataMapperResponse: DataMapperModelResponse = {
160162
mappingsModel: dataMapperModel as DMModel
161163
};
162164
if (mappingInstructionFiles.length > 0) {
165+
eventHandler({ type: "content_block", content: "\n<progress>Processing mapping hints from attachments...</progress>" });
163166
const enhancedResponse = await enrichModelWithMappingInstructions(mappingInstructionFiles, dataMapperResponse);
164167
dataMapperResponse = enhancedResponse as DataMapperModelResponse;
165168
}
169+
eventHandler({ type: "content_block", content: "\n<progress>Generating data mappings...</progress>" });
166170

167171
const generatedMappings = await generateAutoMappings(dataMapperResponse);
168172
return generatedMappings.map(mapping => ({

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/ParameterBranch.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import React, { useState } from "react";
1919

2020
import { TypeField } from "@wso2/ballerina-core";
21-
import { Button } from "@wso2/ui-toolkit";
21+
import { Button, Codicon } from "@wso2/ui-toolkit";
2222

2323

2424
import { useHelperPaneStyles } from "./styles";
@@ -64,31 +64,48 @@ export function ParameterBranch(props: ParameterBranchProps) {
6464
}
6565
});
6666

67-
function toggleOptionalParams(e: any) {
67+
function toggleOptionalParams(e?: React.MouseEvent<HTMLDivElement>) {
68+
if (e) {
69+
e.stopPropagation();
70+
}
6871
setShowOptionalParams(!showOptionalParams);
6972
}
7073

74+
const shouldShowOptionalParamsDirectly = (optionalParams.length > 0 && depth === 1) ||
75+
(requiredParams.length === 0 && optionalParams.length > 0 && depth < 3);
76+
7177
return (
7278
<div data-testid="parameter-branch">
7379
{requiredParams}
74-
{(optionalParams.length > 0 && depth === 1) ? (
80+
{shouldShowOptionalParamsDirectly ? (
7581
optionalParams
7682
) : (
7783
<>
7884
{optionalParams.length > 0 && (
7985
<div className={helperStyleClass.listOptionalWrapper}>
80-
{/* <div className={helperStyleClass.listOptionalHeader}>Optional fields </div> */}
81-
<Button
82-
data-testid="optional-toggle-button"
83-
className={helperStyleClass.listOptionalBtn}
84-
onClick={toggleOptionalParams}
85-
appearance="secondary"
86-
>
87-
{showOptionalParams ? "Hide" : "Show"}
88-
</Button>
86+
<div className={helperStyleClass.listOptionalHeader} onClick={toggleOptionalParams}>
87+
<Button
88+
data-testid="optional-toggle-button"
89+
appearance="icon"
90+
sx={{
91+
transition: "all 0.2s ease",
92+
"&:hover": {
93+
backgroundColor: "transparent !important",
94+
},
95+
}}
96+
>
97+
<Codicon
98+
name={showOptionalParams ? "chevron-down" : "chevron-right"}
99+
iconSx={{ fontSize: '13px' }}
100+
/>
101+
</Button>
102+
<div className={helperStyleClass.listOptionalTitle}>Optional fields</div>
103+
</div>
89104
</div>
90105
)}
91-
{showOptionalParams && optionalParams.length > 0 && optionalParams}
106+
<div style={{ marginLeft: '15px' }}>
107+
{showOptionalParams && optionalParams.length > 0 && optionalParams}
108+
</div>
92109
</>
93110
)}
94111
</div>

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/CustomType/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
2222

2323
import { TypeProps } from "../../ParameterBranch";
2424
import { useHelperPaneStyles } from "../../styles";
25-
import { isRequiredParam } from "../../utils";
25+
import { isRequiredParam, resetFieldValues } from "../../utils";
2626

2727
export default function CustomType(props: TypeProps) {
2828
const { param, onChange } = props;
@@ -38,6 +38,12 @@ export default function CustomType(props: TypeProps) {
3838
if (!requiredParam) {
3939
const newSelectedState = !paramSelected;
4040
param.selected = newSelectedState;
41+
42+
// When unchecking, reset the field values
43+
if (!newSelectedState) {
44+
resetFieldValues(param);
45+
}
46+
4147
setParamSelected(newSelectedState);
4248
onChange();
4349
}

workspaces/ballerina/ballerina-visualizer/src/views/BI/HelperPaneNew/Components/RecordConstructView/Types/InclusionType/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { Codicon, Tooltip, Typography } from "@wso2/ui-toolkit";
2323
import { TypeProps } from "../../ParameterBranch";
2424
import { useHelperPaneStyles } from "../../styles";
2525
import { ParameterBranch } from "../../ParameterBranch";
26-
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection } from "../../utils";
26+
import { isAllDefaultableFields, isRequiredParam, updateFieldsSelection, resetFieldValues } from "../../utils";
2727

2828
export default function InclusionType(props: TypeProps) {
2929
const { param, depth, onChange } = props;
@@ -43,6 +43,11 @@ export default function InclusionType(props: TypeProps) {
4343
param.selected = newSelectedState;
4444
param.inclusionType.selected = newSelectedState;
4545

46+
// When unchecking, reset the field values
47+
if (!newSelectedState) {
48+
resetFieldValues(param);
49+
}
50+
4651
// If the inclusion type has fields, update their selection state
4752
if (param.inclusionType?.fields && param.inclusionType.fields.length > 0) {
4853
updateFieldsSelection(param.inclusionType.fields, newSelectedState);

0 commit comments

Comments
 (0)