Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
FileOrDirRequest,
WorkspaceRootResponse,
ShowErrorMessageRequest,
WorkspaceTypeResponse
WorkspaceTypeResponse,
SampleDownloadRequest
} from "./interfaces";

export interface CommonRPCAPI {
Expand All @@ -51,4 +52,5 @@ export interface CommonRPCAPI {
showErrorMessage: (params: ShowErrorMessageRequest) => void;
getCurrentProjectTomlValues: () => Promise<Record<string, any>>;
getWorkspaceType: () => Promise<WorkspaceTypeResponse>;
downloadSelectedSampleFromGithub: (params: SampleDownloadRequest) => Promise<boolean>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ export interface PackageTomlValues {
export interface WorkspaceTypeResponse {
type: "SINGLE_PROJECT" | "MULTIPLE_PROJECTS" | "BALLERINA_WORKSPACE" | "VSCODE_WORKSPACE" | "UNKNOWN"
}

export interface SampleDownloadRequest {
zipFileName: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import {
FileOrDirRequest,
WorkspaceRootResponse,
ShowErrorMessageRequest,
WorkspaceTypeResponse
WorkspaceTypeResponse,
SampleDownloadRequest
} from "./interfaces";
import { RequestType, NotificationType } from "vscode-messenger-common";

Expand All @@ -53,3 +54,4 @@ export const getWorkspaceRoot: RequestType<void, WorkspaceRootResponse> = { meth
export const showErrorMessage: NotificationType<ShowErrorMessageRequest> = { method: `${_preFix}/showErrorMessage` };
export const getCurrentProjectTomlValues: RequestType<void, void> = { method: `${_preFix}/getCurrentProjectTomlValues` };
export const getWorkspaceType: RequestType<void, WorkspaceTypeResponse> = { method: `${_preFix}/getWorkspaceType` };
export const downloadSelectedSampleFromGithub: RequestType<SampleDownloadRequest, boolean> = { method: `${_preFix}/downloadSelectedSampleFromGithub` };
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export enum MACHINE_VIEW {
AIAgentDesigner = "AI Agent Designer",
AIChatAgentWizard = "AI Chat Agent Wizard",
ResolveMissingDependencies = "Resolve Missing Dependencies",
ServiceFunctionForm = "Service Function Form"
ServiceFunctionForm = "Service Function Form",
BISamplesView = "BI Samples View"
}

export interface MachineEvent {
Expand Down
1 change: 1 addition & 0 deletions workspaces/ballerina/ballerina-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@
"portfinder": "^1.0.32",
"source-map-support": "^0.5.21",
"toml": "^3.0.0",
"unzipper": "~0.12.3",
"uuid": "^11.1.0",
"vscode-debugadapter": "^1.51.0",
"vscode-debugprotocol": "^1.51.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,29 @@
import {
BallerinaDiagnosticsRequest,
CommandsRequest,
FileOrDirRequest,
GoToSourceRequest,
OpenExternalUrlRequest,
RunExternalCommandRequest,
ShowErrorMessageRequest,
WorkspaceFileRequest,
downloadSelectedSampleFromGithub,
executeCommand,
experimentalEnabled,
FileOrDirRequest,
getBallerinaDiagnostics,
getCurrentProjectTomlValues,
getTypeCompletions,
getWorkspaceFiles,
getWorkspaceRoot,
getWorkspaceType,
goToSource,
GoToSourceRequest,
isNPSupported,
openExternalUrl,
OpenExternalUrlRequest,
runBackgroundTerminalCommand,
RunExternalCommandRequest,
SampleDownloadRequest,
selectFileOrDirPath,
selectFileOrFolderPath,
showErrorMessage
showErrorMessage,
ShowErrorMessageRequest,
WorkspaceFileRequest
} from "@wso2/ballerina-core";
import { Messenger } from "vscode-messenger";
import { CommonRpcManager } from "./rpc-manager";
Expand All @@ -62,4 +64,5 @@ export function registerCommonRpcHandlers(messenger: Messenger) {
messenger.onNotification(showErrorMessage, (args: ShowErrorMessageRequest) => rpcManger.showErrorMessage(args));
messenger.onRequest(getCurrentProjectTomlValues, () => rpcManger.getCurrentProjectTomlValues());
messenger.onRequest(getWorkspaceType, () => rpcManger.getWorkspaceType());
messenger.onRequest(downloadSelectedSampleFromGithub, (args: SampleDownloadRequest) => rpcManger.downloadSelectedSampleFromGithub(args));
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,24 @@ import {
FileOrDirResponse,
GoToSourceRequest,
OpenExternalUrlRequest,
PackageTomlValues,
RunExternalCommandRequest,
RunExternalCommandResponse,
SampleDownloadRequest,
ShowErrorMessageRequest,
SyntaxTree,
PackageTomlValues,
TypeResponse,
WorkspaceFileRequest,
WorkspaceRootResponse,
WorkspacesFileResponse,
WorkspaceTypeResponse,
WorkspaceTypeResponse
} from "@wso2/ballerina-core";
import child_process from 'child_process';
import { Uri, commands, env, window, workspace, MarkdownString } from "vscode";
import path from "path";
import os from "os";
import fs from "fs";
import * as unzipper from 'unzipper';
import { commands, env, MarkdownString, ProgressLocation, Uri, window, workspace } from "vscode";
import { URI } from "vscode-uri";
import { extension } from "../../BalExtensionContext";
import { StateMachine } from "../../stateMachine";
Expand All @@ -60,9 +65,11 @@ import {
askFilePath,
askProjectPath,
BALLERINA_INTEGRATOR_ISSUES_URL,
getUpdatedSource
getUpdatedSource,
handleDownloadFile,
selectSampleDownloadPath
} from "./utils";
import path from "path";
import { VisualizerWebview } from "../../views/visualizer/webview";

export class CommonRpcManager implements CommonRPCAPI {
async getTypeCompletions(): Promise<TypeResponse> {
Expand Down Expand Up @@ -304,4 +311,92 @@ export class CommonRpcManager implements CommonRPCAPI {

return { type: "UNKNOWN" };
}


async downloadSelectedSampleFromGithub(params: SampleDownloadRequest): Promise<boolean> {
const repoUrl = 'https://raw.githubusercontent.com/wso2/integration-samples/refs/heads/main/ballerina-integrator/samples/';
const rawFileLink = repoUrl + params.zipFileName + '.zip';
const defaultDownloadsPath = path.join(os.homedir(), 'Downloads'); // Construct the default downloads path
const pathFromDialog = await selectSampleDownloadPath();
if (pathFromDialog === "") {
return false;
}
const selectedPath = pathFromDialog === "" ? defaultDownloadsPath : pathFromDialog;
const filePath = path.join(selectedPath, params.zipFileName + '.zip');
let isSuccess = false;

if (fs.existsSync(filePath)) {
// already downloaded
isSuccess = true;
} else {
await window.withProgress({
location: ProgressLocation.Notification,
title: 'Downloading file',
cancellable: true
}, async (progress, cancellationToken) => {

let cancelled: boolean = false;
cancellationToken.onCancellationRequested(async () => {
cancelled = true;
// Clean up partial download
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
});

try {
await handleDownloadFile(rawFileLink, filePath, progress);
isSuccess = true;
return;
} catch (error) {
window.showErrorMessage(`Error while downloading the file: ${error}`);
}
});
}

if (isSuccess) {
const successMsg = `The Integration sample file has been downloaded successfully to the following directory: ${filePath}.`;
const zipReadStream = fs.createReadStream(filePath);
if (fs.existsSync(path.join(selectedPath, params.zipFileName))) {
// already extracted
let uri = Uri.file(path.join(selectedPath, params.zipFileName));
commands.executeCommand("vscode.openFolder", uri, true);
return true;
}
zipReadStream.pipe(unzipper.Parse()).on("entry", function (entry) {
var isDir = entry.type === "Directory";
var fullpath = path.join(selectedPath, entry.path);
var directory = isDir ? fullpath : path.dirname(fullpath);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
if (!isDir) {
entry.pipe(fs.createWriteStream(fullpath));
}
}).on("close", () => {
console.log("Extraction complete!");
window.showInformationMessage('Where would you like to open the project?',
{ modal: true },
'Current Window',
'New Window'
).then(selection => {
if (selection === "Current Window") {
// Dispose the current webview
VisualizerWebview.currentPanel?.dispose();
const folderUri = Uri.file(path.join(selectedPath, params.zipFileName));
const workspaceFolders = workspace.workspaceFolders || [];
if (!workspaceFolders.some(folder => folder.uri.fsPath === folderUri.fsPath)) {
workspace.updateWorkspaceFolders(workspaceFolders.length, 0, { uri: folderUri });
}
} else if (selection === "New Window") {
commands.executeCommand('vscode.openFolder', Uri.file(path.join(selectedPath, params.zipFileName)));
}
});
});
window.showInformationMessage(
successMsg,
);
}
return isSuccess;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@

import * as os from 'os';
import { NodePosition } from "@wso2/syntax-tree";
import { Position, Range, Uri, window, workspace, WorkspaceEdit } from "vscode";
import { Position, Progress, Range, Uri, window, workspace, WorkspaceEdit } from "vscode";
import { TextEdit } from "@wso2/ballerina-core";
import axios from 'axios';
import fs from 'fs';

export const BALLERINA_INTEGRATOR_ISSUES_URL = "https://github.com/wso2/product-ballerina-integrator/issues";

interface ProgressMessage {
message: string;
increment?: number;
}

export function getUpdatedSource(
statement: string,
currentFileContent: string,
Expand Down Expand Up @@ -111,3 +118,75 @@ export async function applyBallerinaTomlEdit(tomlPath: Uri, textEdit: TextEdit)
}
});
}

export async function selectSampleDownloadPath(): Promise<string> {
const folderPath = await window.showOpenDialog({ title: 'Sample download directory', canSelectFolders: true, canSelectFiles: false, openLabel: 'Select Folder' });
if (folderPath && folderPath.length > 0) {
const newlySelectedFolder = folderPath[0].fsPath;
return newlySelectedFolder;
}
return "";
}

async function downloadFile(url: string, filePath: string, progressCallback?: (downloadProgress: any) => void) {
const writer = fs.createWriteStream(filePath);
let totalBytes = 0;
try {
const response = await axios.get(url, {
responseType: 'stream',
headers: {
"User-Agent": "Mozilla/5.0"
},
onDownloadProgress: (progressEvent) => {
totalBytes = progressEvent.total ?? 0;
if (totalBytes === 0) {
// Cannot calculate progress without total size
return;
}
const formatSize = (sizeInBytes: number) => {
const sizeInKB = sizeInBytes / 1024;
if (sizeInKB < 1024) {
return `${Math.floor(sizeInKB)} KB`;
} else {
return `${Math.floor(sizeInKB / 1024)} MB`;
}
};
const progress = {
percentage: Math.round((progressEvent.loaded * 100) / totalBytes),
downloadedAmount: formatSize(progressEvent.loaded),
downloadSize: formatSize(totalBytes)
};
if (progressCallback) {
progressCallback(progress);
}
}
});
response.data.pipe(writer);
await new Promise<void>((resolve, reject) => {
writer.on('finish', () => {
writer.close();
resolve();
});

writer.on('error', (error) => {
reject(error);
});
});
} catch (error) {
window.showErrorMessage(`Error while downloading the file: ${error}`);
throw error;
}
}

export async function handleDownloadFile(rawFileLink: string, defaultDownloadsPath: string, progress: Progress<ProgressMessage>) {
const handleProgress = (progressPercentage) => {
progress.report({ message: "Downloading file...", increment: progressPercentage });
};
try {
await downloadFile(rawFileLink, defaultDownloadsPath, handleProgress);
} catch (error) {
window.showErrorMessage(`Failed to download file: ${error}`);
}
progress.report({ message: "Download finished" });
}

Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,32 @@ import {
FileOrDirResponse,
GoToSourceRequest,
OpenExternalUrlRequest,
PackageTomlValues,
RunExternalCommandRequest,
RunExternalCommandResponse,
SampleDownloadRequest,
ShowErrorMessageRequest,
TypeResponse,
WorkspaceFileRequest,
WorkspaceRootResponse,
WorkspaceTypeResponse,
WorkspacesFileResponse,
downloadSelectedSampleFromGithub,
executeCommand,
experimentalEnabled,
getBallerinaDiagnostics,
getCurrentProjectTomlValues,
getTypeCompletions,
getWorkspaceFiles,
getWorkspaceRoot,
getWorkspaceType,
goToSource,
isNPSupported,
openExternalUrl,
runBackgroundTerminalCommand,
selectFileOrDirPath,
getCurrentProjectTomlValues,
PackageTomlValues,
selectFileOrFolderPath,
showErrorMessage,
WorkspaceTypeResponse,
getWorkspaceType
showErrorMessage
} from "@wso2/ballerina-core";
import { HOST_EXTENSION } from "vscode-messenger-common";
import { Messenger } from "vscode-messenger-webview";
Expand Down Expand Up @@ -121,4 +123,8 @@ export class CommonRpcClient implements CommonRPCAPI {
getWorkspaceType(): Promise<WorkspaceTypeResponse> {
return this._messenger.sendRequest(getWorkspaceType, HOST_EXTENSION);
}

downloadSelectedSampleFromGithub(params: SampleDownloadRequest): Promise<boolean> {
return this._messenger.sendRequest(downloadSelectedSampleFromGithub, HOST_EXTENSION, params);
}
}
Loading
Loading