Skip to content

Commit aa9e164

Browse files
Merge branch 'main' into bi-dm-undo-fix
2 parents 0358d3a + e007ce8 commit aa9e164

File tree

32 files changed

+1075
-228
lines changed

32 files changed

+1075
-228
lines changed

.trivyignore

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
# Trivy ignore file for known low-risk vulnerabilities
22
# Format: CVE-ID or vulnerability ID
33

4-
# Low severity vulnerability in jsondiffpatch package
5-
# Used as transitive dependency via 'ai' package
6-
# Risk Assessment: LOW severity, limited impact on diff operations
7-
# Decision: Acceptable risk - waiting for ai package to update jsondiffpatch dependency
8-
# Related Issue: https://github.com/wso2/product-ballerina-integrator/issues/1274
9-
CVE-2025-9910
10-
114
# No fix released by the author
125
# https://github.com/wso2/vscode-extensions/issues/550
136
CVE-2020-36851

common/config/rush/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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-core/src/rpc-types/common/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
FileOrDirRequest,
3333
WorkspaceRootResponse,
3434
ShowErrorMessageRequest,
35-
WorkspaceTypeResponse
35+
WorkspaceTypeResponse,
36+
SampleDownloadRequest
3637
} from "./interfaces";
3738

3839
export interface CommonRPCAPI {
@@ -51,4 +52,5 @@ export interface CommonRPCAPI {
5152
showErrorMessage: (params: ShowErrorMessageRequest) => void;
5253
getCurrentProjectTomlValues: () => Promise<Record<string, any>>;
5354
getWorkspaceType: () => Promise<WorkspaceTypeResponse>;
55+
downloadSelectedSampleFromGithub: (params: SampleDownloadRequest) => Promise<boolean>;
5456
}

workspaces/ballerina/ballerina-core/src/rpc-types/common/interfaces.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,7 @@ export interface PackageTomlValues {
113113
export interface WorkspaceTypeResponse {
114114
type: "SINGLE_PROJECT" | "MULTIPLE_PROJECTS" | "BALLERINA_WORKSPACE" | "VSCODE_WORKSPACE" | "UNKNOWN"
115115
}
116+
117+
export interface SampleDownloadRequest {
118+
zipFileName: string;
119+
}

workspaces/ballerina/ballerina-core/src/rpc-types/common/rpc-type.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
FileOrDirRequest,
3434
WorkspaceRootResponse,
3535
ShowErrorMessageRequest,
36-
WorkspaceTypeResponse
36+
WorkspaceTypeResponse,
37+
SampleDownloadRequest
3738
} from "./interfaces";
3839
import { RequestType, NotificationType } from "vscode-messenger-common";
3940

@@ -53,3 +54,4 @@ export const getWorkspaceRoot: RequestType<void, WorkspaceRootResponse> = { meth
5354
export const showErrorMessage: NotificationType<ShowErrorMessageRequest> = { method: `${_preFix}/showErrorMessage` };
5455
export const getCurrentProjectTomlValues: RequestType<void, void> = { method: `${_preFix}/getCurrentProjectTomlValues` };
5556
export const getWorkspaceType: RequestType<void, WorkspaceTypeResponse> = { method: `${_preFix}/getWorkspaceType` };
57+
export const downloadSelectedSampleFromGithub: RequestType<SampleDownloadRequest, boolean> = { method: `${_preFix}/downloadSelectedSampleFromGithub` };

workspaces/ballerina/ballerina-core/src/state-machine-types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ export enum MACHINE_VIEW {
9999
AIAgentDesigner = "AI Agent Designer",
100100
AIChatAgentWizard = "AI Chat Agent Wizard",
101101
ResolveMissingDependencies = "Resolve Missing Dependencies",
102-
ServiceFunctionForm = "Service Function Form"
102+
ServiceFunctionForm = "Service Function Form",
103+
BISamplesView = "BI Samples View"
103104
}
104105

105106
export interface MachineEvent {

workspaces/ballerina/ballerina-extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,7 @@
12141214
"portfinder": "^1.0.32",
12151215
"source-map-support": "^0.5.21",
12161216
"toml": "^3.0.0",
1217+
"unzipper": "~0.12.3",
12171218
"uuid": "^11.1.0",
12181219
"vscode-debugadapter": "^1.51.0",
12191220
"vscode-debugprotocol": "^1.51.0",

workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-handler.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,29 @@
2020
import {
2121
BallerinaDiagnosticsRequest,
2222
CommandsRequest,
23-
FileOrDirRequest,
24-
GoToSourceRequest,
25-
OpenExternalUrlRequest,
26-
RunExternalCommandRequest,
27-
ShowErrorMessageRequest,
28-
WorkspaceFileRequest,
23+
downloadSelectedSampleFromGithub,
2924
executeCommand,
3025
experimentalEnabled,
26+
FileOrDirRequest,
3127
getBallerinaDiagnostics,
3228
getCurrentProjectTomlValues,
3329
getTypeCompletions,
3430
getWorkspaceFiles,
3531
getWorkspaceRoot,
3632
getWorkspaceType,
3733
goToSource,
34+
GoToSourceRequest,
3835
isNPSupported,
3936
openExternalUrl,
37+
OpenExternalUrlRequest,
4038
runBackgroundTerminalCommand,
39+
RunExternalCommandRequest,
40+
SampleDownloadRequest,
4141
selectFileOrDirPath,
4242
selectFileOrFolderPath,
43-
showErrorMessage
43+
showErrorMessage,
44+
ShowErrorMessageRequest,
45+
WorkspaceFileRequest
4446
} from "@wso2/ballerina-core";
4547
import { Messenger } from "vscode-messenger";
4648
import { CommonRpcManager } from "./rpc-manager";
@@ -62,4 +64,5 @@ export function registerCommonRpcHandlers(messenger: Messenger) {
6264
messenger.onNotification(showErrorMessage, (args: ShowErrorMessageRequest) => rpcManger.showErrorMessage(args));
6365
messenger.onRequest(getCurrentProjectTomlValues, () => rpcManger.getCurrentProjectTomlValues());
6466
messenger.onRequest(getWorkspaceType, () => rpcManger.getWorkspaceType());
67+
messenger.onRequest(downloadSelectedSampleFromGithub, (args: SampleDownloadRequest) => rpcManger.downloadSelectedSampleFromGithub(args));
6568
}

workspaces/ballerina/ballerina-extension/src/rpc-managers/common/rpc-manager.ts

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,24 @@ import {
3131
FileOrDirResponse,
3232
GoToSourceRequest,
3333
OpenExternalUrlRequest,
34+
PackageTomlValues,
3435
RunExternalCommandRequest,
3536
RunExternalCommandResponse,
37+
SampleDownloadRequest,
3638
ShowErrorMessageRequest,
3739
SyntaxTree,
38-
PackageTomlValues,
3940
TypeResponse,
4041
WorkspaceFileRequest,
4142
WorkspaceRootResponse,
4243
WorkspacesFileResponse,
43-
WorkspaceTypeResponse,
44+
WorkspaceTypeResponse
4445
} from "@wso2/ballerina-core";
4546
import child_process from 'child_process';
46-
import { Uri, commands, env, window, workspace, MarkdownString } from "vscode";
47+
import path from "path";
48+
import os from "os";
49+
import fs from "fs";
50+
import * as unzipper from 'unzipper';
51+
import { commands, env, MarkdownString, ProgressLocation, Uri, window, workspace } from "vscode";
4752
import { URI } from "vscode-uri";
4853
import { extension } from "../../BalExtensionContext";
4954
import { StateMachine } from "../../stateMachine";
@@ -60,9 +65,11 @@ import {
6065
askFilePath,
6166
askProjectPath,
6267
BALLERINA_INTEGRATOR_ISSUES_URL,
63-
getUpdatedSource
68+
getUpdatedSource,
69+
handleDownloadFile,
70+
selectSampleDownloadPath
6471
} from "./utils";
65-
import path from "path";
72+
import { VisualizerWebview } from "../../views/visualizer/webview";
6673

6774
export class CommonRpcManager implements CommonRPCAPI {
6875
async getTypeCompletions(): Promise<TypeResponse> {
@@ -304,4 +311,149 @@ export class CommonRpcManager implements CommonRPCAPI {
304311

305312
return { type: "UNKNOWN" };
306313
}
314+
315+
316+
async downloadSelectedSampleFromGithub(params: SampleDownloadRequest): Promise<boolean> {
317+
const repoUrl = 'https://raw.githubusercontent.com/wso2/integration-samples/refs/heads/main/ballerina-integrator/samples/';
318+
const rawFileLink = repoUrl + params.zipFileName + '.zip';
319+
const defaultDownloadsPath = path.join(os.homedir(), 'Downloads'); // Construct the default downloads path
320+
const pathFromDialog = await selectSampleDownloadPath();
321+
if (pathFromDialog === "") {
322+
return false;
323+
}
324+
const selectedPath = pathFromDialog === "" ? defaultDownloadsPath : pathFromDialog;
325+
const filePath = path.join(selectedPath, params.zipFileName + '.zip');
326+
let isSuccess = false;
327+
328+
if (fs.existsSync(filePath)) {
329+
// already downloaded
330+
isSuccess = true;
331+
} else {
332+
await window.withProgress({
333+
location: ProgressLocation.Notification,
334+
title: 'Downloading file',
335+
cancellable: true
336+
}, async (progress, cancellationToken) => {
337+
338+
let cancelled: boolean = false;
339+
cancellationToken.onCancellationRequested(async () => {
340+
cancelled = true;
341+
// Clean up partial download
342+
if (fs.existsSync(filePath)) {
343+
fs.unlinkSync(filePath);
344+
}
345+
});
346+
347+
try {
348+
await handleDownloadFile(rawFileLink, filePath, progress);
349+
isSuccess = true;
350+
return;
351+
} catch (error) {
352+
window.showErrorMessage(`Error while downloading the file: ${error}`);
353+
}
354+
});
355+
}
356+
357+
if (isSuccess) {
358+
const successMsg = `The Integration sample file has been downloaded successfully to the following directory: ${filePath}.`;
359+
const zipReadStream = fs.createReadStream(filePath);
360+
if (fs.existsSync(path.join(selectedPath, params.zipFileName))) {
361+
// already extracted
362+
let uri = Uri.file(path.join(selectedPath, params.zipFileName));
363+
commands.executeCommand("vscode.openFolder", uri, true);
364+
return true;
365+
}
366+
367+
let extractionError: Error | null = null;
368+
const parseStream = unzipper.Parse();
369+
370+
// Handle errors on the read stream
371+
zipReadStream.on("error", (error) => {
372+
extractionError = error;
373+
window.showErrorMessage(`Failed to read zip file: ${error.message}`);
374+
});
375+
376+
// Handle errors on the parse stream
377+
parseStream.on("error", (error) => {
378+
extractionError = error;
379+
window.showErrorMessage(`Failed to parse zip file. The file may be corrupted: ${error.message}`);
380+
});
381+
382+
parseStream.on("entry", function (entry) {
383+
// Skip processing if we've already encountered an error
384+
if (extractionError) {
385+
entry.autodrain();
386+
return;
387+
}
388+
389+
var isDir = entry.type === "Directory";
390+
var fullpath = path.join(selectedPath, entry.path);
391+
var directory = isDir ? fullpath : path.dirname(fullpath);
392+
393+
try {
394+
if (!fs.existsSync(directory)) {
395+
fs.mkdirSync(directory, { recursive: true });
396+
}
397+
} catch (error) {
398+
extractionError = error as Error;
399+
window.showErrorMessage(`Failed to create directory "${directory}": ${error instanceof Error ? error.message : String(error)}`);
400+
entry.autodrain();
401+
return;
402+
}
403+
404+
if (!isDir) {
405+
const writeStream = fs.createWriteStream(fullpath);
406+
407+
// Handle write stream errors
408+
writeStream.on("error", (error) => {
409+
extractionError = error;
410+
window.showErrorMessage(`Failed to write file "${fullpath}": ${error.message}. This may be due to insufficient disk space or permission issues.`);
411+
entry.autodrain();
412+
});
413+
414+
// Handle entry stream errors
415+
entry.on("error", (error) => {
416+
extractionError = error;
417+
window.showErrorMessage(`Failed to extract entry "${entry.path}": ${error.message}`);
418+
writeStream.destroy();
419+
});
420+
421+
entry.pipe(writeStream);
422+
}
423+
});
424+
425+
parseStream.on("close", () => {
426+
if (extractionError) {
427+
console.error("Extraction failed:", extractionError);
428+
window.showErrorMessage(`Sample extraction failed: ${extractionError.message}`);
429+
return;
430+
}
431+
432+
console.log("Extraction complete!");
433+
window.showInformationMessage('Where would you like to open the project?',
434+
{ modal: true },
435+
'Current Window',
436+
'New Window'
437+
).then(selection => {
438+
if (selection === "Current Window") {
439+
// Dispose the current webview
440+
VisualizerWebview.currentPanel?.dispose();
441+
const folderUri = Uri.file(path.join(selectedPath, params.zipFileName));
442+
const workspaceFolders = workspace.workspaceFolders || [];
443+
if (!workspaceFolders.some(folder => folder.uri.fsPath === folderUri.fsPath)) {
444+
workspace.updateWorkspaceFolders(workspaceFolders.length, 0, { uri: folderUri });
445+
}
446+
} else if (selection === "New Window") {
447+
commands.executeCommand('vscode.openFolder', Uri.file(path.join(selectedPath, params.zipFileName)));
448+
}
449+
});
450+
});
451+
452+
zipReadStream.pipe(parseStream);
453+
window.showInformationMessage(
454+
successMsg,
455+
);
456+
}
457+
return isSuccess;
458+
}
307459
}

0 commit comments

Comments
 (0)