From 40237c40771a3a8597f27635a28698e3bcd5dc3c Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 2 Dec 2025 14:23:11 +0530 Subject: [PATCH 001/102] Fix condition for processing focus inputs in processTypeFields function --- .../src/rpc-managers/data-mapper/utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 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 b3357ad2696..f9e45db90d1 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 @@ -732,7 +732,7 @@ function processTypeFields( let isFocused = false; let isSeq = !!model.groupById; - if (model.focusInputs) { + if (isSeq && model.focusInputs) { const focusMember = model.focusInputs[fieldId]; if (focusMember) { field = focusMember; @@ -751,8 +751,8 @@ function processTypeFields( displayName: field.displayName, typeName: field.typeName, kind: field.kind, - ...(isFocused && { isFocused }), - ...(isSeq && { isSeq }), + // ...(isFocused && { isFocused }), + // ...(isSeq && { isSeq }), ...(field.optional !== undefined && { optional: field.optional }), ...(field.typeInfo && { typeInfo: field.typeInfo }) }; From 44a799bdedf189455227e6e585d2ceeb9da3651e Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 2 Dec 2025 14:27:32 +0530 Subject: [PATCH 002/102] Fix processTypeFields to correctly include isFocused and isSeq properties --- .../ballerina-extension/src/rpc-managers/data-mapper/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 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 f9e45db90d1..eb84089cc1b 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 @@ -751,8 +751,8 @@ function processTypeFields( displayName: field.displayName, typeName: field.typeName, kind: field.kind, - // ...(isFocused && { isFocused }), - // ...(isSeq && { isSeq }), + ...(isFocused && { isFocused }), + ...(isSeq && { isSeq }), ...(field.optional !== undefined && { optional: field.optional }), ...(field.typeInfo && { typeInfo: field.typeInfo }) }; From 1f3bf4ba4bf344cd226421f0f0cd6a6d9f3c470f Mon Sep 17 00:00:00 2001 From: Vellummyilum Vinoth Date: Wed, 3 Dec 2025 13:24:03 +0530 Subject: [PATCH 003/102] Refactor AI Data Mapper repair workflow --- .../src/rpc-types/ai-panel/index.ts | 4 +- .../src/rpc-types/ai-panel/interfaces.ts | 20 +- .../src/rpc-types/ai-panel/rpc-type.ts | 4 +- .../src/features/ai/constants.ts | 5 + .../src/features/ai/dataMapping.ts | 118 +-- .../ai/service/datamapper/codeRepairPrompt.ts | 120 +-- .../ai/service/datamapper/datamapper.ts | 694 ++++++++---------- .../features/ai/service/datamapper/schema.ts | 16 +- .../features/ai/service/datamapper/types.ts | 17 +- .../src/rpc-managers/ai-panel/rpc-handler.ts | 3 - .../src/rpc-managers/ai-panel/rpc-manager.ts | 25 +- .../src/rpc-managers/ai-panel/utils.ts | 10 +- .../rpc-managers/bi-diagram/rpc-manager.ts | 7 +- .../src/rpc-clients/ai-panel/rpc-client.ts | 6 - 14 files changed, 458 insertions(+), 591 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts index 1c56541b121..7a52708a484 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts @@ -15,9 +15,8 @@ * specific language governing permissions and limitations * under the License. */ -import { DataMapperModelResponse } from "../../interfaces/extended-lang-client"; import { LoginMethod } from "../../state-machine-types"; -import { AddToProjectRequest, GetFromFileRequest, DeleteFromProjectRequest, ProjectSource, ProjectDiagnostics, PostProcessRequest, PostProcessResponse, FetchDataRequest, FetchDataResponse, TestGenerationRequest, TestGenerationResponse, TestGenerationMentions, AIChatSummary, DeveloperDocument, RequirementSpecification, LLMDiagnostics, GetModuleDirParams, AIPanelPrompt, AIMachineSnapshot, SubmitFeedbackRequest, RelevantLibrariesAndFunctionsRequest, GenerateOpenAPIRequest, GenerateCodeRequest, TestPlanGenerationRequest, TestGeneratorIntermediaryState, RepairParams, RelevantLibrariesAndFunctionsResponse, DocGenerationRequest, AddFilesToProjectRequest, MetadataWithAttachments, DatamapperModelContext, ProcessContextTypeCreationRequest, ProcessMappingParametersRequest } from "./interfaces"; +import { GetFromFileRequest, DeleteFromProjectRequest, ProjectSource, ProjectDiagnostics, PostProcessRequest, PostProcessResponse, FetchDataRequest, FetchDataResponse, TestGenerationRequest, TestGenerationResponse, TestGenerationMentions, AIChatSummary, DeveloperDocument, RequirementSpecification, LLMDiagnostics, AIPanelPrompt, AIMachineSnapshot, SubmitFeedbackRequest, RelevantLibrariesAndFunctionsRequest, GenerateOpenAPIRequest, GenerateCodeRequest, TestPlanGenerationRequest, TestGeneratorIntermediaryState, RepairParams, RelevantLibrariesAndFunctionsResponse, DocGenerationRequest, AddFilesToProjectRequest, MetadataWithAttachments, ProcessContextTypeCreationRequest, ProcessMappingParametersRequest } from "./interfaces"; export interface AIPanelAPI { // ================================== @@ -31,7 +30,6 @@ export interface AIPanelAPI { getDefaultPrompt: () => Promise; getAIMachineSnapshot: () => Promise; fetchData: (params: FetchDataRequest) => Promise; - addToProject: (params: AddToProjectRequest) => Promise; getFromFile: (params: GetFromFileRequest) => Promise; getFileExists: (params: GetFromFileRequest) => Promise; deleteFromProject: (params: DeleteFromProjectRequest) => void; diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts index 4abe471c4c3..aabcee88cdb 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts @@ -21,7 +21,7 @@ import { FunctionDefinition } from "@wso2/syntax-tree"; import { AIMachineContext, AIMachineStateValue } from "../../state-machine-types"; import { Command, TemplateId } from "../../interfaces/ai-panel"; import { AllDataMapperSourceRequest, DataMapperSourceResponse, ExtendedDataMapperMetadata } from "../../interfaces/extended-lang-client"; -import { ComponentInfo, DataMapperMetadata, Diagnostics, ImportStatements, Project } from "../.."; +import { ComponentInfo, DataMapperMetadata, Diagnostics, DMModel, ImportStatements } from "../.."; // ================================== // General Interfaces @@ -90,12 +90,6 @@ export interface DiagnosticEntry { code?: string; } -export interface AddToProjectRequest { - filePath: string; - content: string; - isTestCode: boolean; -} - export interface AddFilesToProjectRequest { fileChanges: FileChanges[]; } @@ -225,12 +219,20 @@ export interface RepairCodeParams { tempDir?: string; } +export interface RepairedMapping { + output: string; + expression: string; +} + export interface repairCodeRequest { - sourceFiles: SourceFile[]; - diagnostics: DiagnosticList; + dmModel: DMModel; imports: ImportInfo[]; } +export interface RepairCodeResponse { + repairedMappings: RepairedMapping[]; +} + // Test-generator related interfaces export enum TestGenerationTarget { Service = "service", diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts index ee93b828631..870f5bc095c 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts @@ -17,9 +17,8 @@ * * THIS FILE INCLUDES AUTO GENERATED CODE */ -import { DataMapperModelResponse } from "../../interfaces/extended-lang-client"; import { LoginMethod } from "../../state-machine-types"; -import { AddToProjectRequest, GetFromFileRequest, DeleteFromProjectRequest, ProjectSource, ProjectDiagnostics, PostProcessRequest, PostProcessResponse, FetchDataRequest, FetchDataResponse, TestGenerationRequest, TestGenerationResponse, TestGenerationMentions, AIChatSummary, DeveloperDocument, RequirementSpecification, LLMDiagnostics, GetModuleDirParams, AIPanelPrompt, AIMachineSnapshot, SubmitFeedbackRequest, RelevantLibrariesAndFunctionsRequest, GenerateOpenAPIRequest, GenerateCodeRequest, TestPlanGenerationRequest, TestGeneratorIntermediaryState, RepairParams, RelevantLibrariesAndFunctionsResponse, DocGenerationRequest, AddFilesToProjectRequest, MetadataWithAttachments, DatamapperModelContext, ProcessContextTypeCreationRequest, ProcessMappingParametersRequest } from "./interfaces"; +import { GetFromFileRequest, DeleteFromProjectRequest, ProjectSource, ProjectDiagnostics, PostProcessRequest, PostProcessResponse, FetchDataRequest, FetchDataResponse, TestGenerationRequest, TestGenerationResponse, TestGenerationMentions, AIChatSummary, DeveloperDocument, RequirementSpecification, LLMDiagnostics, AIPanelPrompt, AIMachineSnapshot, SubmitFeedbackRequest, RelevantLibrariesAndFunctionsRequest, GenerateOpenAPIRequest, GenerateCodeRequest, TestPlanGenerationRequest, TestGeneratorIntermediaryState, RepairParams, RelevantLibrariesAndFunctionsResponse, DocGenerationRequest, AddFilesToProjectRequest, MetadataWithAttachments, ProcessContextTypeCreationRequest, ProcessMappingParametersRequest } from "./interfaces"; import { RequestType, NotificationType } from "vscode-messenger-common"; const _preFix = "ai-panel"; @@ -31,7 +30,6 @@ export const getRefreshedAccessToken: RequestType = { method: `${_ export const getDefaultPrompt: RequestType = { method: `${_preFix}/getDefaultPrompt` }; export const getAIMachineSnapshot: RequestType = { method: `${_preFix}/getAIMachineSnapshot` }; export const fetchData: RequestType = { method: `${_preFix}/fetchData` }; -export const addToProject: RequestType = { method: `${_preFix}/addToProject` }; export const getFromFile: RequestType = { method: `${_preFix}/getFromFile` }; export const getFileExists: RequestType = { method: `${_preFix}/getFileExists` }; export const deleteFromProject: NotificationType = { method: `${_preFix}/deleteFromProject` }; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/constants.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/constants.ts index b6b2d3b27b5..6891aa98e77 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/constants.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/constants.ts @@ -46,6 +46,11 @@ export enum NullablePrimitiveType { BOOLEAN = "boolean?" } +// Error type +export enum ErrorType { + ERROR = "error" +} + // Array types for primitive data types export enum PrimitiveArrayType { // Basic array types diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts index 755b2db02b5..b9f8e46cd88 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/dataMapping.ts @@ -16,20 +16,20 @@ * under the License. */ -import { AllDataMapperSourceRequest, Attachment, CodeData, ComponentInfo, createFunctionSignature, CreateTempFileRequest, DataMapperMetadata, DatamapperModelContext, DataMapperModelResponse, DataMappingRecord, DiagnosticList, DMModel, EnumType, ExistingFunctionMatchResult, ExtendedDataMapperMetadata, ExtractMappingDetailsRequest, ExtractMappingDetailsResponse, GenerateTypesFromRecordRequest, GenerateTypesFromRecordResponse, getSource, ImportInfo, ImportStatements, InlineMappingsSourceResult, IORoot, IOTypeField, LinePosition, Mapping, MappingParameters, MetadataWithAttachments, ModuleSummary, PackageSummary, ProjectComponentsResponse, ProjectSource, RecordType, RepairCodeParams, repairCodeRequest, SourceFile, SyntaxTree, TempDirectoryPath } from "@wso2/ballerina-core"; +import { AllDataMapperSourceRequest, Attachment, CodeData, ComponentInfo, createFunctionSignature, CreateTempFileRequest, DataMapperMetadata, DatamapperModelContext, DataMapperModelResponse, DataMappingRecord, DiagnosticList, DMModel, EnumType, ExistingFunctionMatchResult, ExtendedDataMapperMetadata, ExtractMappingDetailsRequest, ExtractMappingDetailsResponse, GenerateTypesFromRecordRequest, GenerateTypesFromRecordResponse, getSource, ImportInfo, ImportStatements, InlineMappingsSourceResult, IORoot, IOTypeField, keywords, LinePosition, Mapping, MappingParameters, MetadataWithAttachments, ModuleSummary, PackageSummary, ProjectComponentsResponse, RecordType, RepairCodeParams, SourceFile, SyntaxTree, TempDirectoryPath } from "@wso2/ballerina-core"; import { camelCase } from "lodash"; import path from "path"; import * as fs from 'fs'; import * as os from 'os'; import { Uri } from "vscode"; -import { extractRecordTypeDefinitionsFromFile, generateMappingExpressionsFromModel, repairSourceFilesWithAI } from "../../rpc-managers/ai-panel/utils"; +import { extractRecordTypeDefinitionsFromFile, generateMappingExpressionsFromModel } from "../../rpc-managers/ai-panel/utils"; import { writeBallerinaFileDidOpenTemp } from "../../utils/modification"; import { ExtendedLangClient, NOT_SUPPORTED } from "../../core"; import { DefaultableParam, FunctionDefinition, IncludedRecordParam, ModulePart, RequiredParam, RestParam, STKindChecker, STNode } from "@wso2/syntax-tree"; import { addMissingRequiredFields, attemptRepairProject, checkProjectDiagnostics } from "../../../src/rpc-managers/ai-panel/repair-utils"; -import { NullablePrimitiveType, PrimitiveArrayType, PrimitiveType } from "./constants"; +import { ErrorType, NullablePrimitiveType, PrimitiveArrayType, PrimitiveType } from "./constants"; import { INVALID_RECORD_REFERENCE } from "../../../src/views/ai-panel/errorCodes"; -import { CodeRepairResult, PackageInfo, TypesGenerationResult } from "./service/datamapper/types"; +import { PackageInfo, TypesGenerationResult } from "./service/datamapper/types"; import { URI } from "vscode-uri"; import { getAllDataMapperSource } from "./service/datamapper/datamapper"; import { StateMachine } from "../../stateMachine"; @@ -67,8 +67,12 @@ const isPrimitiveArrayType = (type: string): boolean => { return false; }; +const isErrorType = (type: string): boolean => { + return Object.values(ErrorType).includes(type as ErrorType); +}; + const isAnyPrimitiveType = (type: string): boolean => { - return isPrimitiveType(type) || isNullablePrimitiveType(type) || isPrimitiveArrayType(type); + return isPrimitiveType(type) || isNullablePrimitiveType(type) || isPrimitiveArrayType(type) || isErrorType(type); }; // ================================================================================================ @@ -220,32 +224,6 @@ export async function createTempBallerinaDir(): Promise { return tempDir; } -export async function repairCodeWithLLM(codeRepairRequest: repairCodeRequest): Promise { - if (!codeRepairRequest) { - throw new Error("Code repair request is required"); - } - - if (!codeRepairRequest.sourceFiles || codeRepairRequest.sourceFiles.length === 0) { - throw new Error("Source files are required for code repair"); - } - - const repairedSourceFiles = await repairSourceFilesWithAI(codeRepairRequest); - - for (const repairedFile of repairedSourceFiles) { - try { - writeBallerinaFileDidOpenTemp( - repairedFile.filePath, - repairedFile.content - ); - } catch (error) { - console.error(`Error processing file ${repairedFile.filePath}:`, error); - } - } - - const projectSourceResponse = { sourceFiles: repairedSourceFiles, projectName: "", packagePath: "", isActive: true }; - return projectSourceResponse; -} - export function createDataMappingFunctionSource( inputParams: DataMappingRecord[], outputParam: DataMappingRecord, @@ -297,7 +275,9 @@ function getDefaultParamName(type: string, isArray: boolean): string { case PrimitiveType.BOOLEAN: return isArray ? "flagArr" : "flag"; default: - return camelCase(processedType); + const camelCaseName = camelCase(processedType); + // Check if the camelCase name is a reserved keyword + return keywords.includes(camelCaseName) ? `'${camelCaseName}` : camelCaseName; } } @@ -514,7 +494,7 @@ export async function generateMappings( // ================================================================================================ // DMModel Optimization - Functions for processing and optimizing data mapper models // ================================================================================================ -function ensureUnionRefs(model: DMModel): DMModel { +export function ensureUnionRefs(model: DMModel): DMModel { const processedModel = JSON.parse(JSON.stringify(model)); const unionRefs = new Map(); @@ -1316,45 +1296,6 @@ function prepareSourceFilesForRepair( return sourceFiles; } -// Repair code and get updated content -export async function repairCodeAndGetUpdatedContent( - params: RepairCodeParams, - langClient: ExtendedLangClient, - projectRoot: string -): Promise { - - // Read main file content - let finalContent = fs.readFileSync(params.tempFileMetadata.codeData.lineRange.fileName, 'utf8'); - - // Read custom functions content (only if path is provided) - let customFunctionsContent = params.customFunctionsFilePath - ? await getCustomFunctionsContent(params.customFunctionsFilePath) - : ''; - - // Check and repair diagnostics - const diagnostics = await checkAndRepairDiagnostics( - params, - langClient, - projectRoot - ); - - // Repair with LLM if needed - if (diagnostics.diagnosticsList && diagnostics.diagnosticsList.length > 0) { - const result = await repairWithLLM( - params.tempFileMetadata, - finalContent, - params.customFunctionsFilePath, - customFunctionsContent, - diagnostics, - params.imports - ); - finalContent = result.finalContent; - customFunctionsContent = result.customFunctionsContent; - } - - return { finalContent, customFunctionsContent }; -} - // Get custom functions content if file exists export async function getCustomFunctionsContent( customFunctionsFilePath: string | undefined, @@ -1382,34 +1323,6 @@ async function checkAndRepairDiagnostics( return await repairAndCheckDiagnostics(langClient, projectRoot, diagnosticsParams); } -// Repair code using LLM -async function repairWithLLM( - tempFileMetadata: ExtendedDataMapperMetadata, - mainContent: string, - customFunctionsFilePath: string | undefined, - customFunctionsContent: string, - diagnostics: DiagnosticList, - imports: ImportInfo[] -): Promise<{ finalContent: string; customFunctionsContent: string }> { - const sourceFiles = prepareSourceFilesForRepair( - tempFileMetadata.codeData.lineRange.fileName, - mainContent, - customFunctionsFilePath, - customFunctionsContent - ); - - await repairCodeWithLLM({sourceFiles, diagnostics, imports}); - - // Get updated content after repair - const finalContent = fs.readFileSync(tempFileMetadata.codeData.lineRange.fileName, 'utf8'); - const updatedCustomFunctionsContent = await getCustomFunctionsContent(customFunctionsFilePath); - - return { - finalContent, - customFunctionsContent: updatedCustomFunctionsContent - }; -} - // ================================================================================================ // processMappingParameters - Functions for processing mapping parameters // ================================================================================================ @@ -1433,6 +1346,11 @@ export function buildRecordMap( const recFilePath = filepath + rec.filePath; recordMap.set(rec.name, { type: rec.name, isArray: false, filePath: recFilePath }); }); + + mod.types.forEach((type: ComponentInfo) => { + const typeFilePath = filepath + type.filePath; + recordMap.set(type.name, { type: type.name, isArray: false, filePath: typeFilePath }); + }); } } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/codeRepairPrompt.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/codeRepairPrompt.ts index 2f820687694..d11ee856d55 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/codeRepairPrompt.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/codeRepairPrompt.ts @@ -15,66 +15,90 @@ // under the License. /** - * Generates the main data mapping prompt for AI + * Generates the code repair prompt for AI using DM Model */ -export function getBallerinaCodeRepairPrompt(sourceFiles: string, diagnostics: string, imports: string): string { - return `You are an expert Ballerina programmer tasked with fixing compiler errors in Ballerina source code. - -# Context -You have been provided with: -1. Source files with Ballerina code that contains errors -2. Diagnostic information (compiler errors and warnings) -3. Import statements available in the project - -# Your Task -Analyze the provided code and diagnostics, then generate corrected Ballerina source files that: -- Fix all compiler errors identified in the diagnostics -- Maintain the original code structure and logic as much as possible -- Use correct Ballerina syntax and idioms -- Ensure all function signatures, type definitions, and record field accesses are valid -- Verify that all imported modules are used correctly -- Follow Ballerina best practices and conventions +export function getBallerinaCodeRepairPrompt(dmModel: string, imports: string): string { + return `You are an expert Ballerina programmer tasked with repairing mapping expressions that have compiler errors. -# Input Data +## Context + +You have been provided with a Data Mapper Model (DMModel) that contains mapping definitions with diagnostics (compiler errors and warnings). Your task is to fix the expressions in these mappings. + +The DMModel structure includes: +- **inputs**: Array of input record structures with their fields and types +- **output**: Output record structure with fields and types +- **subMappings**: Nested mapping definitions +- **mappings**: Array of mapping objects, each containing: + - \`output\`: Target field path (e.g., "outputRecord.fieldName") + - \`expression\`: Ballerina expression to map the value + - \`diagnostics\`: Array of compiler errors/warnings for this mapping (if any) +- **refs**: Referenced type definitions -## Source Files: -${sourceFiles} +# Input Data -## Diagnostics (Errors and Warnings): -${diagnostics} +## Data Mapper Model (DMModel with diagnostics): +${dmModel} ## Available Imports: ${imports} -# Instructions -1. Carefully examine each diagnostic error and identify the root cause -2. Check function signatures, return types, and parameter types against Ballerina documentation -3. Verify record field access patterns are correct (use dot notation for required fields, optional chaining for optional fields) -4. Ensure type compatibility in assignments and function calls -5. Fix any syntax errors or misused language constructs -6. Validate that all imported modules and their functions are used correctly -7. Return the complete corrected source files with the same file paths - -# Output Format -Return a JSON object with the following structure: +## Your Task + +Analyze each mapping that has diagnostics and repair the expression to fix all compiler errors. Focus on: + +1. **Type Compatibility**: Ensure the expression produces the correct type for the output field +2. **Field Access**: Use correct syntax for accessing record fields + - Required fields: \`record.field\` + - Optional fields: \`record?.field\` or \`record["field"]\` +3. **Null Safety**: Handle optional/nilable types appropriately +4. **Function Calls**: Verify imported functions are called correctly +5. **Type Conversions**: Add necessary type casts or conversions +6. **Syntax Errors**: Fix any Ballerina syntax issues + +## Output Format + +Return a JSON object with the repaired mappings: + +{ + "repairedMappings": [ + { + "output": "path.to.output.field", + "expression": "corrected_ballerina_expression" + } + ] +} + +## Requirements + +- Only include mappings that had diagnostics and were repaired +- Provide the complete corrected expression, not partial code +- Ensure expressions are valid Ballerina syntax +- Maintain the original mapping intent and logic +- Do NOT add comments or explanations in the expressions +- The repaired expression must resolve all diagnostic errors for that mapping + +## Example + +**Input Mapping with Diagnostic:** +{ + "output": "person.age", + "expression": "inputData.years", + "diagnostics": [ + { + "message": "incompatible types: expected 'int', found 'string'" + } + ] +} + +##Repaired Output: { - "repairedFiles": [ + "repairedMappings": [ { - "filePath": "path/to/file.bal", - "content": "// Complete corrected Ballerina code here" + "output": "person.age", + "expression": "int:fromString(inputData.years) ?: 0" } ] } -# Important Notes -- Include ALL source files in your response, even if some don't have errors -- Provide the COMPLETE file content, not just the changed portions -- Ensure the code compiles without errors -- Maintain code readability and formatting -- Preserve all existing comments from the original code -- Do NOT add any new comments or explanatory notes -- Only fix the errors without adding documentation or explanations -- Do not change the core logic or business requirements of the code - -Generate the repaired source files now.`; +Generate the repaired mappings now.`; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts index 64a1a6262a0..6cadac8def5 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/datamapper.ts @@ -17,20 +17,21 @@ import { CoreMessage, ModelMessage, generateObject } from "ai"; import { getAnthropicClient, ANTHROPIC_SONNET_4 } from "../connection"; import { - CodeRepairResult, DatamapperResponse, DataModelStructure, MappingFields, - RepairedFiles, + RepairedMapping, + RepairedMappings, + DMModelDiagnosticsResult } from "./types"; -import { GeneratedMappingSchema, RepairedSourceFilesSchema } from "./schema"; -import { AIPanelAbortController } from "../../../../../src/rpc-managers/ai-panel/utils"; -import { DataMapperModelResponse, DMModel, Mapping, repairCodeRequest, SourceFile, DiagnosticList, ImportInfo, ProcessMappingParametersRequest, Command, MetadataWithAttachments, InlineMappingsSourceResult, ProcessContextTypeCreationRequest, ProjectImports, ImportStatements, TemplateId, GetModuleDirParams, TextEdit, DataMapperSourceResponse, DataMapperSourceRequest, AllDataMapperSourceRequest, DataMapperModelRequest, DeleteMappingRequest } from "@wso2/ballerina-core"; +import { GeneratedMappingSchema, RepairedMappingsSchema } from "./schema"; +import { AIPanelAbortController, repairSourceFilesWithAI } from "../../../../../src/rpc-managers/ai-panel/utils"; +import { DataMapperModelResponse, DMModel, Mapping, repairCodeRequest, DiagnosticList, ImportInfo, ProcessMappingParametersRequest, Command, MetadataWithAttachments, InlineMappingsSourceResult, ProcessContextTypeCreationRequest, ProjectImports, ImportStatements, TemplateId, GetModuleDirParams, TextEdit, DataMapperSourceResponse, DataMapperSourceRequest, AllDataMapperSourceRequest, DataMapperModelRequest, DeleteMappingRequest, CodeData } from "@wso2/ballerina-core"; import { getDataMappingPrompt } from "./dataMappingPrompt"; import { getBallerinaCodeRepairPrompt } from "./codeRepairPrompt"; import { CopilotEventHandler, createWebviewEventHandler } from "../event"; import { getErrorMessage } from "../utils"; -import { buildMappingFileArray, buildRecordMap, collectExistingFunctions, collectModuleInfo, createTempBallerinaDir, createTempFileAndGenerateMetadata, getFunctionDefinitionFromSyntaxTree, getUniqueFunctionFilePaths, prepareMappingContext, generateInlineMappingsSource, generateTypesFromContext, repairCodeAndGetUpdatedContent, extractImports, generateDataMapperModel, determineCustomFunctionsPath, generateMappings, getCustomFunctionsContent, repairAndCheckDiagnostics } from "../../dataMapping"; +import { buildMappingFileArray, buildRecordMap, collectExistingFunctions, collectModuleInfo, createTempBallerinaDir, createTempFileAndGenerateMetadata, getFunctionDefinitionFromSyntaxTree, getUniqueFunctionFilePaths, prepareMappingContext, generateInlineMappingsSource, generateTypesFromContext, extractImports, generateDataMapperModel, determineCustomFunctionsPath, generateMappings, repairAndCheckDiagnostics, ensureUnionRefs, normalizeRefs } from "../../dataMapping"; import { addCheckExpressionErrors } from "../../../../../src/rpc-managers/ai-panel/repair-utils"; import { BiDiagramRpcManager, getBallerinaFiles } from "../../../../../src/rpc-managers/bi-diagram/rpc-manager"; import { updateSourceCode } from "../../../../../src/utils/source-utils"; @@ -147,24 +148,18 @@ async function generateAIMappings( } } -// Uses Claude AI to repair Ballerina source files based on diagnostics and import information +// Uses Claude AI to repair code based on DM model with diagnostics and import information async function repairBallerinaCode( - filesToRepair: SourceFile[], - compilationDiagnostics: DiagnosticList, + dmModel: DMModel, availableImports: ImportInfo[] -): Promise { - if (!filesToRepair || filesToRepair.length === 0) { - throw new Error("Source files to repair are required and cannot be empty"); - } - - if (!compilationDiagnostics) { - throw new Error("Compilation diagnostics are required for code repair"); +): Promise { + if (!dmModel) { + throw new Error("DM model is required for code repair"); } // Build repair prompt const codeRepairPrompt = getBallerinaCodeRepairPrompt( - JSON.stringify(filesToRepair), - JSON.stringify(compilationDiagnostics), + JSON.stringify(dmModel), JSON.stringify(availableImports || []) ); @@ -178,14 +173,14 @@ async function repairBallerinaCode( maxOutputTokens: 8192, temperature: 0, messages: chatMessages, - schema: RepairedSourceFilesSchema, + schema: RepairedMappingsSchema, abortSignal: AIPanelAbortController.getInstance().signal, }); - return object.repairedFiles as SourceFile[]; + return object.repairedMappings as RepairedMapping[]; } catch (error) { console.error("Failed to parse response:", error); - throw new Error(`Failed to parse repaired files response: ${error}`); + throw new Error(`Failed to parse repaired mappings response: ${error}`); } } @@ -207,8 +202,8 @@ export async function generateAutoMappings(dataMapperModelResponse?: DataMapperM } } -// Generates repaired Ballerina code by fixing diagnostics with retry logic -export async function generateRepairCode(codeRepairRequest?: repairCodeRequest): Promise { +// Generates repaired mappings by fixing diagnostics with retry logic +export async function generateRepairCode(codeRepairRequest?: repairCodeRequest): Promise { if (!codeRepairRequest) { throw new Error("Code repair request is required for generating repair code"); } @@ -223,17 +218,15 @@ export async function generateRepairCode(codeRepairRequest?: repairCodeRequest): } try { - // Generate AI-powered repaired source files using Claude - const aiRepairedFiles = await repairBallerinaCode(codeRepairRequest.sourceFiles, codeRepairRequest.diagnostics, codeRepairRequest.imports); - - if (!aiRepairedFiles || aiRepairedFiles.length === 0) { - const error = new Error("No repaired files were generated. Unable to fix the provided source code."); - lastError = error; - attemptCount += 1; - continue; + // Generate AI-powered repaired mappings using Claude with DM model + const aiRepairedMappings = await repairBallerinaCode(codeRepairRequest.dmModel, codeRepairRequest.imports); + + if (!aiRepairedMappings || aiRepairedMappings.length === 0) { + console.warn("No mappings were repaired. The code may not have fixable errors."); + return { repairedMappings: [] }; } - return { repairedFiles: aiRepairedFiles }; + return { repairedMappings: aiRepairedMappings }; } catch (error) { console.error(`Error occurred while generating repaired code: ${error}`); @@ -246,6 +239,133 @@ export async function generateRepairCode(codeRepairRequest?: repairCodeRequest): throw lastError!; } +// Gets DM model for a function +async function getDMModel( + langClient: any, + mainFilePath: string, + functionName: string +): Promise { + // Get function definition to retrieve accurate position + const funcDefinitionNode = await getFunctionDefinitionFromSyntaxTree( + langClient, + mainFilePath, + functionName + ); + + // Build metadata with current function position + const dataMapperMetadata: DataMapperModelRequest = { + filePath: mainFilePath, + codedata: { + lineRange: { + fileName: mainFilePath, + startLine: { + line: funcDefinitionNode.position.startLine, + offset: funcDefinitionNode.position.startColumn, + }, + endLine: { + line: funcDefinitionNode.position.endLine, + offset: funcDefinitionNode.position.endColumn, + }, + }, + }, + targetField: functionName, + position: { + line: funcDefinitionNode.position.startLine, + offset: funcDefinitionNode.position.startColumn + } + }; + + // Get DM model with mapping-level diagnostics + const dataMapperModel = await langClient.getDataMapperMappings(dataMapperMetadata) as DataMapperModelResponse; + const dmModel = dataMapperModel.mappingsModel as DMModel; + + return { dataMapperMetadata, dmModel }; +} + +// Repairs mappings using LLM based on DM model diagnostics +async function repairMappingsWithLLM( + langClient: any, + dmModelResult: DMModelDiagnosticsResult, + imports: ImportInfo[] +): Promise { + const { dataMapperMetadata, dmModel } = dmModelResult; + + // Call LLM repair with targeted diagnostics and DM model context + try { + let mappingsModel = ensureUnionRefs(dmModel); + mappingsModel = normalizeRefs(mappingsModel); + + const repairResult = await repairSourceFilesWithAI({ + dmModel: mappingsModel, + imports + }); + + // Apply repaired mappings to the DM model + if (repairResult.repairedMappings && repairResult.repairedMappings.length > 0) { + // Apply each repaired mapping individually using the language server + for (const repairedMapping of repairResult.repairedMappings) { + const targetMapping = dmModel.mappings.find(m => m.output === repairedMapping.output); + if (targetMapping) { + // Update the mapping with the repaired expression + targetMapping.expression = repairedMapping.expression; + targetMapping.diagnostics = []; + + // Generate source for this individual mapping + const singleMappingRequest: DataMapperSourceRequest = { + filePath: dataMapperMetadata.filePath, + codedata: dataMapperMetadata.codedata, + varName: dataMapperMetadata.targetField, + targetField: dataMapperMetadata.targetField, + mapping: targetMapping + }; + + try { + const mappingSourceResponse = await langClient.getDataMapperSource(singleMappingRequest); + if (mappingSourceResponse.textEdits && Object.keys(mappingSourceResponse.textEdits).length > 0) { + await updateSourceCode({ textEdits: mappingSourceResponse.textEdits, skipPayloadCheck: true }); + await new Promise((resolve) => setTimeout(resolve, 50)); + } + } catch (error) { + console.warn(`Failed to apply repaired mapping for ${repairedMapping.output}:`, error); + } + } + } + } + } catch (error) { + console.warn('LLM repair failed, continuing with other repairs:', error); + } +} + +// Repairs check expression errors (BCE3032) in DM model +async function repairCheckErrors( + langClient: any, + projectRoot: string, + mainFilePath: string, + allMappingsRequest: AllDataMapperSourceRequest, + tempDirectory: string, + isSameFile: boolean +): Promise { + // Apply programmatic fixes (imports, required fields, etc.) + const filePaths = [mainFilePath]; + if (allMappingsRequest.customFunctionsFilePath && !isSameFile) { + filePaths.push(allMappingsRequest.customFunctionsFilePath); + } + + let diags = await repairAndCheckDiagnostics(langClient, projectRoot, { + tempDir: tempDirectory, + filePaths + }); + + // Handle check expression errors (BCE3032) + const hasCheckError = diags.diagnosticsList.some(diagEntry => + diagEntry.diagnostics.some(d => d.code === "BCE3032") + ); + + if (hasCheckError) { + await addCheckExpressionErrors(diags.diagnosticsList, langClient); + } +} + // ============================================================================= // MAPPING CODE GENERATION WITH EVENT HANDLERS // ============================================================================= @@ -377,63 +497,50 @@ export async function generateMappingCodeCore(mappingRequest: ProcessMappingPara // Check if mappings file and custom functions file are the same const mainFilePath = tempFileMetadata.codeData.lineRange.fileName; - const isSameFile = customFunctionsTargetPath && + const isSameFile = customFunctionsTargetPath && path.resolve(mainFilePath) === path.resolve(path.join(tempDirectory, customFunctionsFileName)); - let codeRepairResult: CodeRepairResult; - const customContent = await getCustomFunctionsContent(allMappingsRequest.customFunctionsFilePath); eventHandler({ type: "content_block", content: "\nRepairing generated code..." }); - if (isSameFile) { - const mainContent = fs.readFileSync(mainFilePath, 'utf8'); + // Get DM model with diagnostics + const dmModelResult = await getDMModel( + langClient, + mainFilePath, + targetFunctionName + ); - if (customContent) { - // Merge: main content + custom functions - const mergedContent = `${mainContent}\n\n${customContent}`; - fs.writeFileSync(mainFilePath, mergedContent, 'utf8'); - } - - codeRepairResult = await repairCodeAndGetUpdatedContent({ - tempFileMetadata, - customFunctionsFilePath: undefined, - imports: uniqueImportStatements, - tempDir: tempDirectory - }, langClient, projectRoot); - - codeRepairResult.customFunctionsContent = ''; - } else { - // Files are different, repair them separately - codeRepairResult = await repairCodeAndGetUpdatedContent({ - tempFileMetadata, - customFunctionsFilePath: allMappingsRequest.customFunctionsFilePath, - imports: uniqueImportStatements, - tempDir: tempDirectory - }, langClient, projectRoot); - } + // Repair mappings using LLM based on DM model diagnostics + await repairMappingsWithLLM( + langClient, + dmModelResult, + uniqueImportStatements + ); - // Handle check expression errors and repair diagnostics - const filePaths = await handleCheckExpressionErrorsAndRepair( + // Repair check expression errors (BCE3032) + await repairCheckErrors( langClient, projectRoot, - tempFileMetadata, + mainFilePath, allMappingsRequest, tempDirectory, - isSameFile, - codeRepairResult + isSameFile ); // Remove compilation error mappings - const { updatedMainContent, updatedCustomContent } = await removeCompilationErrorMappingFields( + await removeCompilationErrorMappingFields( langClient, - projectRoot, mainFilePath, targetFunctionName, allMappingsRequest, - tempDirectory, - filePaths, - isSameFile ); + // Read updated content after removing compilation errors + const finalContent = fs.readFileSync(mainFilePath, 'utf8'); + let customFunctionsContent = ''; + if (allMappingsRequest.customFunctionsFilePath && !isSameFile) { + customFunctionsContent = fs.readFileSync(allMappingsRequest.customFunctionsFilePath, 'utf8'); + } + let generatedFunctionDefinition = await getFunctionDefinitionFromSyntaxTree( langClient, tempFileMetadata.codeData.lineRange.fileName, @@ -453,9 +560,9 @@ export async function generateMappingCodeCore(mappingRequest: ProcessMappingPara const generatedSourceFiles = buildMappingFileArray( targetFilePath, - updatedMainContent, + finalContent, customFunctionsTargetPath, - updatedCustomContent, + customFunctionsContent, ); // Build assistant response @@ -481,13 +588,13 @@ export async function generateMappingCodeCore(mappingRequest: ProcessMappingPara } if (isSameFile) { - const mergedContent = `${generatedFunctionDefinition.source}\n${customContent}`; - assistantResponse += `\n\`\`\`ballerina\n${mergedContent}\n\`\`\`\n`; + // For same file, custom content is already merged in the main content + assistantResponse += `\n\`\`\`ballerina\n${generatedFunctionDefinition.source}\n\`\`\`\n`; } else { assistantResponse += `\n\`\`\`ballerina\n${generatedFunctionDefinition.source}\n\`\`\`\n`; - if (updatedCustomContent) { - assistantResponse += `\n\`\`\`ballerina\n${updatedCustomContent}\n\`\`\`\n`; + if (customFunctionsContent) { + assistantResponse += `\n\`\`\`ballerina\n${customFunctionsContent}\n\`\`\`\n`; } } @@ -667,6 +774,65 @@ export function combineTextEdits(sortedTextEdits: TextEdit[]): TextEdit { }; } +// Gets DM model with diagnostics for inline variable +async function getInlineDMModelWithDiagnostics( + langClient: any, + mainFilePath: string, + variableName: string, + codedata: CodeData +): Promise { + // Build metadata for inline variable + const dataMapperMetadata = { + filePath: mainFilePath, + codedata: codedata, + targetField: variableName, + position: codedata.lineRange.startLine + }; + + // Get DM model with mapping-level diagnostics + const dataMapperModel = await langClient.getDataMapperMappings(dataMapperMetadata) as DataMapperModelResponse; + const dmModel = dataMapperModel.mappingsModel as DMModel; + + return { dataMapperMetadata, dmModel }; +} + +// Removes mappings with compilation errors +async function removeMappingsWithErrors( + langClient: any, + mainFilePath: string, + dmModelResult: DMModelDiagnosticsResult, + varName: string +): Promise { + const { dataMapperMetadata, dmModel } = dmModelResult; + + // Check if any mappings have diagnostics and delete them + if (dmModel && dmModel.mappings && dmModel.mappings.length > 0) { + // Extract mappings with diagnostics + const mappingsWithDiagnostics = dmModel.mappings?.filter((mapping: Mapping) => + mapping.diagnostics && mapping.diagnostics.length > 0 + ) || []; + + // Delete each mapping with diagnostics using the deleteMapping API + for (const mapping of mappingsWithDiagnostics) { + const deleteRequest: DeleteMappingRequest = { + filePath: mainFilePath, + codedata: dataMapperMetadata.codedata, + mapping: mapping, + varName: varName, + targetField: dataMapperMetadata.targetField, + }; + + const deleteResponse = await langClient.deleteMapping(deleteRequest); + + // Apply the text edits from the delete operation directly to temp files + if (Object.keys(deleteResponse.textEdits).length > 0) { + await applyTextEditsToTempFile(deleteResponse.textEdits, mainFilePath); + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } + } +} + // ============================================================================= // INLINE MAPPING CODE GENERATION WITH EVENT HANDLERS // ============================================================================= @@ -740,35 +906,34 @@ export async function generateInlineMappingCodeCore(inlineMappingRequest: Metada const isSameFile = customFunctionsTargetPath && path.resolve(mainFilePath) === path.resolve(path.join(inlineMappingsResult.tempDir, customFunctionsFileName)); - let codeRepairResult: CodeRepairResult; - const customContent = await getCustomFunctionsContent(inlineMappingsResult.allMappingsRequest.customFunctionsFilePath); eventHandler({ type: "content_block", content: "\nRepairing generated code..." }); - if (isSameFile) { - const mainContent = fs.readFileSync(mainFilePath, 'utf8'); + const variableName = inlineMappingRequest.metadata.name || inlineMappingsResult.tempFileMetadata.name; - if (customContent) { - // Merge: main content + custom functions - const mergedContent = `${mainContent}\n\n${customContent}`; - fs.writeFileSync(mainFilePath, mergedContent, 'utf8'); - } - - codeRepairResult = await repairCodeAndGetUpdatedContent({ - tempFileMetadata: inlineMappingsResult.tempFileMetadata, - customFunctionsFilePath: undefined, - imports: uniqueImportStatements, - tempDir: inlineMappingsResult.tempDir - }, langClient, projectRoot); - - codeRepairResult.customFunctionsContent = ''; - } else { - // Files are different, repair them separately - codeRepairResult = await repairCodeAndGetUpdatedContent({ - tempFileMetadata: inlineMappingsResult.tempFileMetadata, - customFunctionsFilePath: inlineMappingsResult.allMappingsRequest.customFunctionsFilePath, - tempDir: inlineMappingsResult.tempDir - }, langClient, projectRoot); - } + // Get DM model with diagnostics + const dmModelResult = await getInlineDMModelWithDiagnostics( + langClient, + mainFilePath, + variableName, + inlineMappingsResult.allMappingsRequest.codedata + ); + + // Repair inline mappings using LLM based on DM model diagnostics + await repairMappingsWithLLM( + langClient, + dmModelResult, + uniqueImportStatements + ); + + // Repair check expression errors (BCE3032) + await repairCheckErrors( + langClient, + projectRoot, + mainFilePath, + inlineMappingsResult.allMappingsRequest, + inlineMappingsResult.tempDir, + isSameFile + ); // For workspace projects, compute relative file path from workspace root let targetFilePath = path.relative(projectRoot, context.documentUri); @@ -779,33 +944,18 @@ export async function generateInlineMappingCodeCore(inlineMappingRequest: Metada targetFilePath = path.relative(workspacePath, context.documentUri); } - const variableName = inlineMappingRequest.metadata.name || inlineMappingsResult.tempFileMetadata.name; - - // Handle check expression errors and repair diagnostics for inline mappings - const { inlineFilePaths, updatedCodeToDisplay } = await handleInlineCheckExpressionErrorsAndRepair( - langClient, - projectRoot, - inlineMappingsResult, - isSameFile, - codeRepairResult, - variableName - ); - - if (updatedCodeToDisplay) { - codeToDisplay = updatedCodeToDisplay; - } - - // Remove compilation error mappings for inline mappings - const { updatedMainContent, updatedCustomContent } = await removeInlineCompilationErrorMappingFields( + // Remove compilation error mappings for inline mappings FIRST + await removeInlineCompilationErrorMappingFields( langClient, - projectRoot, mainFilePath, variableName, inlineMappingsResult, - inlineFilePaths, - isSameFile ); + // Read updated content after removing compilation errors + const finalContent = fs.readFileSync(mainFilePath, 'utf8'); + + // Extract code to display if (variableName) { const extractedVariableDefinition = await extractVariableDefinitionSource( mainFilePath, @@ -816,12 +966,16 @@ export async function generateInlineMappingCodeCore(inlineMappingRequest: Metada codeToDisplay = extractedVariableDefinition; } } + let customFunctionsContent = ''; + if (inlineMappingsResult.allMappingsRequest.customFunctionsFilePath && !isSameFile) { + customFunctionsContent = fs.readFileSync(inlineMappingsResult.allMappingsRequest.customFunctionsFilePath, 'utf8'); + } const generatedSourceFiles = buildMappingFileArray( context.documentUri, - updatedMainContent, + finalContent, customFunctionsTargetPath, - updatedCustomContent, + customFunctionsContent, ); // Build assistant response @@ -839,13 +993,13 @@ export async function generateInlineMappingCodeCore(inlineMappingRequest: Metada } if (isSameFile) { - const mergedCodeDisplay = customContent ? `${codeToDisplay}\n${customContent}` : codeToDisplay; - assistantResponse += `\n\`\`\`ballerina\n${mergedCodeDisplay}\n\`\`\`\n`; + // For same file, custom content is already merged in the code to display + assistantResponse += `\n\`\`\`ballerina\n${codeToDisplay}\n\`\`\`\n`; } else { assistantResponse += `\n\`\`\`ballerina\n${codeToDisplay}\n\`\`\`\n`; - if (updatedCustomContent) { - assistantResponse += `\n\`\`\`ballerina\n${updatedCustomContent}\n\`\`\`\n`; + if (customFunctionsContent) { + assistantResponse += `\n\`\`\`ballerina\n${customFunctionsContent}\n\`\`\`\n`; } } @@ -854,6 +1008,30 @@ export async function generateInlineMappingCodeCore(inlineMappingRequest: Metada eventHandler({ type: "stop", command: Command.DataMap }); } +// Removes mapping fields with compilation errors for inline mappings +async function removeInlineCompilationErrorMappingFields( + langClient: any, + mainFilePath: string, + variableName: string, + inlineMappingsResult: InlineMappingsSourceResult +): Promise { + // Get DM model with diagnostics for inline variable + const dmModelResult = await getInlineDMModelWithDiagnostics( + langClient, + mainFilePath, + variableName, + inlineMappingsResult.allMappingsRequest.codedata + ); + + // Use the to remove mappings with errors + await removeMappingsWithErrors( + langClient, + mainFilePath, + dmModelResult, + inlineMappingsResult.allMappingsRequest.varName + ); +} + // Main public function that uses the default event handler for inline mapping generation export async function generateInlineMappingCode(inlineMappingRequest: MetadataWithAttachments): Promise { const eventHandler = createWebviewEventHandler(Command.DataMap); @@ -908,7 +1086,7 @@ export async function generateContextTypesCore(typeCreationRequest: ProcessConte const sourceAttachmentNames = typeCreationRequest.attachments.map(a => a.name).join(", "); const fileText = typeCreationRequest.attachments.length === 1 ? "file" : "files"; assistantResponse = `Types generated from the ${sourceAttachmentNames} ${fileText} shown below.\n`; - assistantResponse += `\n\`\`\`ballerina\n${typesCode}\n\`\`\`\n`; + assistantResponse += `\n\`\`\`ballerina\n${typesCode}\n\`\`\`\n`; // Send assistant response through event handler eventHandler({ type: "content_block", content: assistantResponse }); @@ -953,261 +1131,27 @@ export async function openChatWindowWithCommand(): Promise { }); } -// Removes mapping fields with compilation errors for inline mappings and reads updated content -async function removeInlineCompilationErrorMappingFields( - langClient: any, - projectRoot: string, - mainFilePath: string, - variableName: string, - inlineMappingsResult: InlineMappingsSourceResult, - inlineFilePaths: string[], - isSameFile: boolean -): Promise<{ updatedMainContent: string; updatedCustomContent: string }> { - // For inline mappings, we use the variable's location from the codedata - const updatedDataMapperMetadata: DataMapperModelRequest = { - filePath: mainFilePath, - codedata: inlineMappingsResult.allMappingsRequest.codedata, - targetField: variableName, - position: inlineMappingsResult.allMappingsRequest.codedata.lineRange.startLine - }; - - // Get DM model with mappings to check for mapping-level diagnostics - const dataMapperModel = await langClient.getDataMapperMappings(updatedDataMapperMetadata) as DataMapperModelResponse; - const dmModel = dataMapperModel.mappingsModel as DMModel; - - // Check if any mappings have diagnostics - if (dmModel && dmModel.mappings && dmModel.mappings.length > 0) { - const mappingsWithDiagnostics = dmModel.mappings.filter((mapping: Mapping) => - mapping.diagnostics && mapping.diagnostics.length > 0 - ); - - if (mappingsWithDiagnostics.length > 0) { - // Delete each mapping with diagnostics using the deleteMapping API - for (const mapping of mappingsWithDiagnostics) { - const deleteRequest: DeleteMappingRequest = { - filePath: mainFilePath, - codedata: updatedDataMapperMetadata.codedata, - mapping: mapping, - varName: inlineMappingsResult.allMappingsRequest.varName, - targetField: variableName, - }; - - const deleteResponse = await langClient.deleteMapping(deleteRequest); - - // Apply the text edits from the delete operation directly to temp files - if (Object.keys(deleteResponse.textEdits).length > 0) { - await applyTextEditsToTempFile(deleteResponse.textEdits, mainFilePath); - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - - await repairAndCheckDiagnostics(langClient, projectRoot, { - tempDir: inlineMappingsResult.tempDir, - filePaths: inlineFilePaths - }); - } - } - - // Read updated content after diagnostics handling - const updatedMainContent = fs.readFileSync(mainFilePath, 'utf8'); - let updatedCustomContent = ''; - if (inlineMappingsResult.allMappingsRequest.customFunctionsFilePath && !isSameFile) { - updatedCustomContent = fs.readFileSync(inlineMappingsResult.allMappingsRequest.customFunctionsFilePath, 'utf8'); - } - - return { updatedMainContent, updatedCustomContent }; -} - -// Handles check expression errors (BCE3032) and repairs diagnostics for inline mapping files -async function handleInlineCheckExpressionErrorsAndRepair( - langClient: any, - projectRoot: string, - inlineMappingsResult: InlineMappingsSourceResult, - isSameFile: boolean, - codeRepairResult: CodeRepairResult, - variableName: string -): Promise<{ inlineFilePaths: string[]; updatedCodeToDisplay?: string }> { - // Build file paths array for both main file and custom functions file - const inlineFilePaths = [inlineMappingsResult.tempFileMetadata.codeData.lineRange.fileName]; - if (inlineMappingsResult.allMappingsRequest.customFunctionsFilePath && !isSameFile) { - inlineFilePaths.push(inlineMappingsResult.allMappingsRequest.customFunctionsFilePath); - } - - // Repair and check diagnostics for all files - let diags = await repairAndCheckDiagnostics(langClient, projectRoot, { - tempDir: inlineMappingsResult.tempDir, - filePaths: inlineFilePaths - }); - - // Check for inline mappings with 'check' expressions (BCE3032 error) - const hasCheckError = diags.diagnosticsList.some(diagEntry => - diagEntry.diagnostics.some(d => d.code === "BCE3032") - ); - - let updatedCodeToDisplay: string; - - if (hasCheckError) { - const isModified = await addCheckExpressionErrors(diags.diagnosticsList, langClient); - if (isModified) { - // Re-read the files after modifications - const tempFilePath = inlineMappingsResult.tempFileMetadata.codeData.lineRange.fileName; - codeRepairResult.finalContent = fs.readFileSync(tempFilePath, 'utf8'); - - // Update the code to display if we're working with a variable - if (variableName) { - const extractedVariableDefinition = await extractVariableDefinitionSource( - tempFilePath, - inlineMappingsResult.tempFileMetadata.codeData, - variableName - ); - if (extractedVariableDefinition) { - updatedCodeToDisplay = extractedVariableDefinition; - } - } - - if (inlineMappingsResult.allMappingsRequest.customFunctionsFilePath && !isSameFile) { - codeRepairResult.customFunctionsContent = fs.readFileSync( - inlineMappingsResult.allMappingsRequest.customFunctionsFilePath, - 'utf8' - ); - } - } - } - - return { inlineFilePaths, updatedCodeToDisplay }; -} - -// Handles check expression errors (BCE3032) and repairs diagnostics for mapping files -async function handleCheckExpressionErrorsAndRepair( - langClient: any, - projectRoot: string, - tempFileMetadata: any, - allMappingsRequest: AllDataMapperSourceRequest, - tempDirectory: string, - isSameFile: boolean, - codeRepairResult: CodeRepairResult -): Promise { - // Build file paths array for both main file and custom functions file - const filePaths = [tempFileMetadata.codeData.lineRange.fileName]; - if (allMappingsRequest.customFunctionsFilePath && !isSameFile) { - filePaths.push(allMappingsRequest.customFunctionsFilePath); - } - - // Repair and check diagnostics for all files - let diags = await repairAndCheckDiagnostics(langClient, projectRoot, { - tempDir: tempDirectory, - filePaths - }); - - // Check for mappings with 'check' expressions (BCE3032 error) - const hasCheckError = diags.diagnosticsList.some((diagEntry) => - diagEntry.diagnostics.some(d => d.code === "BCE3032") - ); - - if (hasCheckError) { - const isModified = await addCheckExpressionErrors(diags.diagnosticsList, langClient); - if (isModified) { - // Re-read the files after modifications - const mainFilePath = tempFileMetadata.codeData.lineRange.fileName; - codeRepairResult.finalContent = fs.readFileSync(mainFilePath, 'utf8'); - - if (allMappingsRequest.customFunctionsFilePath && !isSameFile) { - codeRepairResult.customFunctionsContent = fs.readFileSync( - allMappingsRequest.customFunctionsFilePath, - 'utf8' - ); - } - } - } - - return filePaths; -} - -// Removes mapping fields with compilation errors to avoid syntax errors in generated code and reads updated content +// Removes mapping fields with compilation errors to avoid syntax errors in generated code async function removeCompilationErrorMappingFields( langClient: any, - projectRoot: string, mainFilePath: string, targetFunctionName: string, allMappingsRequest: AllDataMapperSourceRequest, - tempDirectory: string, - filePaths: string[], - isSameFile: boolean -): Promise<{ updatedMainContent: string; updatedCustomContent: string }> { - // Get function definition from syntax tree - const funcDefinitionNode = await getFunctionDefinitionFromSyntaxTree( +): Promise { + // Get DM model with diagnostics + const dmModelResult = await getDMModel( langClient, mainFilePath, targetFunctionName ); - const updatedDataMapperMetadata: DataMapperModelRequest = { - filePath: mainFilePath, - codedata: { - lineRange: { - fileName: mainFilePath, - startLine: { - line: funcDefinitionNode.position.startLine, - offset: funcDefinitionNode.position.startColumn, - }, - endLine: { - line: funcDefinitionNode.position.endLine, - offset: funcDefinitionNode.position.endColumn, - }, - }, - }, - targetField: targetFunctionName, - position: { - line: funcDefinitionNode.position.startLine, - offset: funcDefinitionNode.position.startColumn - } - }; - - // Get DM model with mappings to check for mapping-level diagnostics - const dataMapperModel = await langClient.getDataMapperMappings(updatedDataMapperMetadata) as DataMapperModelResponse; - const dmModel = dataMapperModel.mappingsModel as DMModel; - - // Check if any mappings have diagnostics - if (dmModel && dmModel.mappings && dmModel.mappings.length > 0) { - const mappingsWithDiagnostics = dmModel.mappings.filter((mapping: Mapping) => - mapping.diagnostics && mapping.diagnostics.length > 0 - ); - - if (mappingsWithDiagnostics.length > 0) { - // Delete each mapping with diagnostics using the deleteMapping API - for (const mapping of mappingsWithDiagnostics) { - const deleteRequest: DeleteMappingRequest = { - filePath: mainFilePath, - codedata: updatedDataMapperMetadata.codedata, - mapping: mapping, - varName: allMappingsRequest.varName, - targetField: targetFunctionName, - }; - - const deleteResponse = await langClient.deleteMapping(deleteRequest); - - // Apply the text edits from the delete operation directly to temp files - if (Object.keys(deleteResponse.textEdits).length > 0) { - await applyTextEditsToTempFile(deleteResponse.textEdits, mainFilePath); - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - - await repairAndCheckDiagnostics(langClient, projectRoot, { - tempDir: tempDirectory, - filePaths: filePaths - }); - } - } - - // Read updated content after diagnostics handling - const updatedMainContent = fs.readFileSync(mainFilePath, 'utf8'); - let updatedCustomContent = ''; - if (allMappingsRequest.customFunctionsFilePath && !isSameFile) { - updatedCustomContent = fs.readFileSync(allMappingsRequest.customFunctionsFilePath, 'utf8'); - } - - return { updatedMainContent, updatedCustomContent }; + // Use function to remove mappings with compilation errors + await removeMappingsWithErrors( + langClient, + mainFilePath, + dmModelResult, + allMappingsRequest.varName + ); } // Applies text edits to a temporary file without using VS Code workspace APIs diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/schema.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/schema.ts index cc31e8123a9..ec44048a148 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/schema.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/schema.ts @@ -32,16 +32,16 @@ const GeneratedMappingSchema = z.object({ generatedMappings: DataMappingSchema, }); -// Schema for a single source file -const SourceFileSchema = z.object({ - filePath: z.string().min(1), - content: z.string(), +// Schema for a single repaired mapping +const RepairedMappingSchema = z.object({ + output: z.string(), + expression: z.string() }); -// Schema for the array of repaired source files -const RepairedSourceFilesSchema = z.object({ - repairedFiles: z.array(SourceFileSchema), +// Schema for the array of repaired mappings +const RepairedMappingsSchema = z.object({ + repairedMappings: z.array(RepairedMappingSchema), }); // Export the schema for reuse -export { GeneratedMappingSchema, RepairedSourceFilesSchema }; +export { GeneratedMappingSchema, RepairedMappingsSchema }; diff --git a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts index cf477d48e9a..fe43c7c6718 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/ai/service/datamapper/types.ts @@ -14,7 +14,7 @@ // specific language governing permissions and limitations // under the License. -import { DataMappingRecord, EnumType, IORoot, Mapping, RecordType, SourceFile } from "@wso2/ballerina-core"; +import { DataMapperModelRequest, DataMappingRecord, DMModel, EnumType, IORoot, Mapping, RecordType } from "@wso2/ballerina-core"; // ============================================================================= // DATA MAPPING REQUEST/RESPONSE @@ -49,13 +49,18 @@ export interface DatamapperResponse { // DATAMAPPER CODE REPAIR // ============================================================================= -export interface RepairedFiles { - repairedFiles: SourceFile[]; +export interface RepairedMapping { + output: string; + expression: string; } -export interface CodeRepairResult { - finalContent: string; - customFunctionsContent: string; +export interface RepairedMappings { + repairedMappings: RepairedMapping[]; +} + +export interface DMModelDiagnosticsResult { + dataMapperMetadata: DataMapperModelRequest; + dmModel: DMModel; } // ============================================================================= diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts index 139e5aeb5ea..16dabff26cc 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts @@ -23,8 +23,6 @@ import { addChatSummary, addFilesToProject, AddFilesToProjectRequest, - addToProject, - AddToProjectRequest, AIChatSummary, applyDoOnFailBlocks, checkSyntaxError, @@ -110,7 +108,6 @@ export function registerAiPanelRpcHandlers(messenger: Messenger) { messenger.onRequest(getDefaultPrompt, () => rpcManger.getDefaultPrompt()); messenger.onRequest(getAIMachineSnapshot, () => rpcManger.getAIMachineSnapshot()); messenger.onRequest(fetchData, (args: FetchDataRequest) => rpcManger.fetchData(args)); - messenger.onRequest(addToProject, (args: AddToProjectRequest) => rpcManger.addToProject(args)); messenger.onRequest(getFromFile, (args: GetFromFileRequest) => rpcManger.getFromFile(args)); messenger.onRequest(getFileExists, (args: GetFromFileRequest) => rpcManger.getFileExists(args)); messenger.onNotification(deleteFromProject, (args: DeleteFromProjectRequest) => rpcManger.deleteFromProject(args)); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts index 6ae0af553fa..c70de1972d6 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts @@ -23,7 +23,6 @@ import { AIPanelAPI, AIPanelPrompt, AddFilesToProjectRequest, - AddToProjectRequest, BIModuleNodesRequest, BISourceCodeResponse, DeleteFromProjectRequest, @@ -90,6 +89,7 @@ import { getAccessToken, getLoginMethod, getRefreshedAccessToken, loginGithubCop import { writeBallerinaFileDidOpen, writeBallerinaFileDidOpenTemp } from "../../utils/modification"; import { updateSourceCode } from "../../utils/source-utils"; import { refreshDataMapper } from "../data-mapper/utils"; +import { buildProjectsStructure } from "../../utils/project-artifacts"; import { DEVELOPMENT_DOCUMENT, NATURAL_PROGRAMMING_DIR_NAME, REQUIREMENT_DOC_PREFIX, @@ -184,29 +184,6 @@ export class AiPanelRpcManager implements AIPanelAPI { }; } - async addToProject(req: AddToProjectRequest): Promise { - const projectPath = StateMachine.context().projectPath; - // Check if workspaceFolderPath is a Ballerina project - // Assuming a Ballerina project must contain a 'Ballerina.toml' file - const ballerinaProjectFile = path.join(projectPath, 'Ballerina.toml'); - if (!fs.existsSync(ballerinaProjectFile)) { - throw new Error("Not a Ballerina project."); - } - - let balFilePath = path.join(projectPath, req.filePath); - - const directory = path.dirname(balFilePath); - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }); - } - - await writeBallerinaFileDidOpen(balFilePath, req.content); - updateView(); - const datamapperMetadata = StateMachine.context().dataMapperMetadata; - await refreshDataMapper(balFilePath, datamapperMetadata.codeData, datamapperMetadata.name); - return true; - } - async getFromFile(req: GetFromFileRequest): Promise { let projectPath = StateMachine.context().projectPath; const workspacePath = StateMachine.context().workspacePath; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts index 2d55200a43c..2b67b5bf780 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/utils.ts @@ -16,14 +16,14 @@ * under the License. */ -import { Attachment, AttachmentStatus, DiagnosticEntry, DataMapperModelResponse, Mapping, FileChanges, DMModel, SourceFile, repairCodeRequest} from "@wso2/ballerina-core"; +import { Attachment, AttachmentStatus, DiagnosticEntry, DataMapperModelResponse, Mapping, FileChanges, DMModel, SourceFile, repairCodeRequest, RepairedMapping} from "@wso2/ballerina-core"; import { Position, Range, Uri, workspace, WorkspaceEdit } from 'vscode'; import path from "path"; import * as fs from 'fs'; import { AIChatError } from "./utils/errors"; import { processDataMapperInput } from "../../../src/features/ai/service/datamapper/context_api"; -import { DataMapperRequest, DataMapperResponse, FileData } from "../../../src/features/ai/service/datamapper/types"; +import { DataMapperRequest, DataMapperResponse, FileData, RepairedMappings } from "../../../src/features/ai/service/datamapper/types"; import { getAskResponse } from "../../../src/features/ai/service/ask/ask"; import { MappingFileRecord} from "./types"; import { generateAutoMappings, generateRepairCode } from "../../../src/features/ai/service/datamapper/datamapper"; @@ -201,11 +201,11 @@ export async function enrichModelWithMappingInstructions(mappingInstructionFiles }; } -// Processes a repair request and returns the repaired source files using AI -export async function repairSourceFilesWithAI(codeRepairRequest: repairCodeRequest): Promise { +// Processes a repair request and returns the repaired mappings using AI +export async function repairSourceFilesWithAI(codeRepairRequest: repairCodeRequest): Promise<{ repairedMappings: RepairedMapping[] }> { try { const repairResponse = await generateRepairCode(codeRepairRequest); - return repairResponse.repairedFiles; + return { repairedMappings: repairResponse.repairedMappings }; } catch (error) { console.error(error); throw error; diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index d243e87bdb5..299d8c5d692 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -1785,7 +1785,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { async getRecordNames(): Promise { const projectComponents = await this.getProjectComponents(); - // Extracting all record names + // Extracting all record names and type names const recordNames: string[] = []; if (projectComponents?.components?.packages) { @@ -1796,6 +1796,11 @@ export class BiDiagramRpcManager implements BIDiagramAPI { recordNames.push(record.name); } } + if (module.types) { + for (const type of module.types) { + recordNames.push(type.name); + } + } } } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts index 733e57fbd04..8bb17458bfa 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts @@ -23,7 +23,6 @@ import { AIPanelAPI, AIPanelPrompt, AddFilesToProjectRequest, - AddToProjectRequest, DeleteFromProjectRequest, DeveloperDocument, DocGenerationRequest, @@ -55,7 +54,6 @@ import { abortTestGeneration, addChatSummary, addFilesToProject, - addToProject, applyDoOnFailBlocks, checkSyntaxError, clearInitialPrompt, @@ -149,10 +147,6 @@ export class AiPanelRpcClient implements AIPanelAPI { return this._messenger.sendRequest(fetchData, HOST_EXTENSION, params); } - addToProject(params: AddToProjectRequest): Promise { - return this._messenger.sendRequest(addToProject, HOST_EXTENSION, params); - } - getFromFile(params: GetFromFileRequest): Promise { return this._messenger.sendRequest(getFromFile, HOST_EXTENSION, params); } From 56d7f7f06a75600f0d3fea14f68a2b77e301fc2f Mon Sep 17 00:00:00 2001 From: madushajg Date: Wed, 3 Dec 2025 16:24:55 +0530 Subject: [PATCH 004/102] Update project explorer behavior to prevent focus on revealed items --- .../src/project-explorer/project-explorer-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/bi/bi-extension/src/project-explorer/project-explorer-provider.ts b/workspaces/bi/bi-extension/src/project-explorer/project-explorer-provider.ts index 388514c5ee7..000b94c6903 100644 --- a/workspaces/bi/bi-extension/src/project-explorer/project-explorer-provider.ts +++ b/workspaces/bi/bi-extension/src/project-explorer/project-explorer-provider.ts @@ -146,7 +146,7 @@ export class ProjectExplorerEntryProvider implements vscode.TreeDataProvider Date: Tue, 2 Dec 2025 23:45:46 +0530 Subject: [PATCH 005/102] Fix package vulnerability --- common/config/rush/pnpm-lock.yaml | 1021 +++++++++-------- package.json | 3 +- .../ballerina-extension/package.json | 9 +- .../ballerina-low-code-diagram/package.json | 10 +- 4 files changed, 534 insertions(+), 509 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index da29ba0eb0d..047f012df30 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -448,10 +448,13 @@ importers: dependencies: '@ai-sdk/amazon-bedrock': specifier: ^3.0.25 - version: 3.0.61(zod@4.1.11) + version: 3.0.65(zod@4.1.11) '@ai-sdk/anthropic': specifier: ^2.0.20 - version: 2.0.49(zod@4.1.11) + version: 2.0.53(zod@4.1.11) + '@iarna/toml': + specifier: ^2.2.5 + version: 2.2.5 '@types/lodash': specifier: ^4.14.200 version: 4.17.17 @@ -481,7 +484,7 @@ importers: version: link:../../wso2-platform/wso2-platform-core ai: specifier: ^5.0.56 - version: 5.0.102(zod@4.1.11) + version: 5.0.106(zod@4.1.11) cors-anywhere: specifier: ^0.4.4 version: 0.4.4 @@ -613,8 +616,8 @@ importers: specifier: ^4.6.2 version: 4.6.2 express: - specifier: ^4.18.2 - version: 4.21.2 + specifier: ^4.22.1 + version: 4.22.1 istanbul: specifier: ^0.4.5 version: 0.4.5 @@ -824,8 +827,8 @@ importers: specifier: ^7.1.2 version: 7.1.2(webpack@5.103.0) express: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^4.22.1 + version: 4.22.1 file-loader: specifier: ^6.2.0 version: 6.2.0(webpack@5.103.0) @@ -879,10 +882,10 @@ importers: version: 4.0.0(webpack@5.103.0) stylelint: specifier: ^16.19.1 - version: 16.26.0(typescript@5.8.3) + version: 16.26.1(typescript@5.8.3) stylelint-config-standard: specifier: ^38.0.0 - version: 38.0.0(stylelint@16.26.0(typescript@5.8.3)) + version: 38.0.0(stylelint@16.26.1(typescript@5.8.3)) svg-url-loader: specifier: ^8.0.0 version: 8.0.0(webpack@5.103.0) @@ -1550,7 +1553,7 @@ importers: version: 3.17.5 zustand: specifier: ^5.0.4 - version: 5.0.8(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)) + version: 5.0.9(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)) devDependencies: '@types/blueimp-md5': specifier: ^2.18.2 @@ -2658,10 +2661,10 @@ importers: version: 5.0.0 yaml: specifier: ^2.6.0 - version: 2.8.1 + version: 2.8.2 zustand: specifier: ^5.0.5 - version: 5.0.8(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) + version: 5.0.9(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@biomejs/biome': specifier: ^1.8.3 @@ -2864,7 +2867,7 @@ importers: version: 4.0.0(webpack@5.103.0) tailwindcss: specifier: ^3.4.3 - version: 3.4.18(yaml@2.8.1) + version: 3.4.18(yaml@2.8.2) ts-loader: specifier: ^9.5.2 version: 9.5.4(typescript@5.8.3)(webpack@5.103.0) @@ -3281,7 +3284,7 @@ importers: devDependencies: '@eslint/eslintrc': specifier: ~3.3.1 - version: 3.3.1 + version: 3.3.3 '@eslint/js': specifier: ~9.27.0 version: 9.27.0 @@ -3377,7 +3380,7 @@ importers: version: 3.17.5 zustand: specifier: ^5.0.5 - version: 5.0.8(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)) + version: 5.0.9(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)) devDependencies: '@types/blueimp-md5': specifier: ^2.18.2 @@ -3669,7 +3672,7 @@ importers: dependencies: '@ai-sdk/anthropic': specifier: ^2.0.35 - version: 2.0.49(zod@3.25.76) + version: 2.0.53(zod@3.25.76) '@apidevtools/json-schema-ref-parser': specifier: 12.0.2 version: 12.0.2 @@ -3729,7 +3732,7 @@ importers: version: 0.5.16 ai: specifier: ^5.0.76 - version: 5.0.102(zod@3.25.76) + version: 5.0.106(zod@3.25.76) axios: specifier: ~1.12.0 version: 1.12.2 @@ -3898,7 +3901,7 @@ importers: version: 4.10.0(webpack@5.103.0) yaml: specifier: ~2.8.0 - version: 2.8.1 + version: 2.8.2 ../../workspaces/mi/mi-rpc-client: dependencies: @@ -4164,7 +4167,7 @@ importers: version: 5.2.2(webpack-cli@5.1.4)(webpack@5.103.0) yaml: specifier: ~2.8.0 - version: 2.8.1 + version: 2.8.2 ../../workspaces/mi/syntax-tree: dependencies: @@ -4235,7 +4238,7 @@ importers: dependencies: '@aws-sdk/client-s3': specifier: ^3.817.0 - version: 3.940.0 + version: 3.943.0 '@vscode-logging/logger': specifier: ^2.0.0 version: 2.0.0 @@ -4289,13 +4292,13 @@ importers: version: 5.0.0 yaml: specifier: ^2.8.0 - version: 2.8.1 + version: 2.8.2 zod: specifier: ^3.22.4 version: 3.25.76 zustand: specifier: ^5.0.5 - version: 5.0.8(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) + version: 5.0.9(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@biomejs/biome': specifier: ^1.9.4 @@ -4517,26 +4520,26 @@ packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@ai-sdk/amazon-bedrock@3.0.61': - resolution: {integrity: sha512-sgMNLtII+vvHbe8S8nVxVAf3I60PcSKRvBvB6CvwdaO3yc5CVCHEulfcasxTR9jThV60aUZ2Q5BzheSwIyo9hg==} + '@ai-sdk/amazon-bedrock@3.0.65': + resolution: {integrity: sha512-E5KJv9OvLJitwPo6GnTgYdssTjEbwVW08TXqaQE2C6hfpg6XdwMXc7BJvQ97eXogGETAyFSS0irDYsbA90rB+g==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/anthropic@2.0.49': - resolution: {integrity: sha512-XedtHVHX6UOlR/aa8bDmlsDc/e+kjC+l6qBeqnZPF05np6Xs7YR8tfH7yARq0LDq3m+ysw7Qoy9M5KRL+1C8qA==} + '@ai-sdk/anthropic@2.0.53': + resolution: {integrity: sha512-ih7NV+OFSNWZCF+tYYD7ovvvM+gv7TRKQblpVohg2ipIwC9Y0TirzocJVREzZa/v9luxUwFbsPji++DUDWWxsg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@2.0.15': - resolution: {integrity: sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw==} + '@ai-sdk/gateway@2.0.18': + resolution: {integrity: sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@3.0.17': - resolution: {integrity: sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==} + '@ai-sdk/provider-utils@3.0.18': + resolution: {integrity: sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -4588,48 +4591,48 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.940.0': - resolution: {integrity: sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==} + '@aws-sdk/client-s3@3.943.0': + resolution: {integrity: sha512-UOX8/1mmNaRmEkxoIVP2+gxd5joPJqz+fygRqlIXON1cETLGoctinMwQs7qU8g8hghm76TU2G6ZV6sLH8cySMw==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.940.0': - resolution: {integrity: sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==} + '@aws-sdk/client-sso@3.943.0': + resolution: {integrity: sha512-kOTO2B8Ks2qX73CyKY8PAajtf5n39aMe2spoiOF5EkgSzGV7hZ/HONRDyADlyxwfsX39Q2F2SpPUaXzon32IGw==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.940.0': - resolution: {integrity: sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==} + '@aws-sdk/core@3.943.0': + resolution: {integrity: sha512-8CBy2hI9ABF7RBVQuY1bgf/ue+WPmM/hl0adrXFlhnhkaQP0tFY5zhiy1Y+n7V+5f3/ORoHBmCCQmcHDDYJqJQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.940.0': - resolution: {integrity: sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==} + '@aws-sdk/credential-provider-env@3.943.0': + resolution: {integrity: sha512-WnS5w9fK9CTuoZRVSIHLOMcI63oODg9qd1vXMYb7QGLGlfwUm4aG3hdu7i9XvYrpkQfE3dzwWLtXF4ZBuL1Tew==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.940.0': - resolution: {integrity: sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==} + '@aws-sdk/credential-provider-http@3.943.0': + resolution: {integrity: sha512-SA8bUcYDEACdhnhLpZNnWusBpdmj4Vl67Vxp3Zke7SvoWSYbuxa+tiDiC+c92Z4Yq6xNOuLPW912ZPb9/NsSkA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.940.0': - resolution: {integrity: sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==} + '@aws-sdk/credential-provider-ini@3.943.0': + resolution: {integrity: sha512-BcLDb8l4oVW+NkuqXMlO7TnM6lBOWW318ylf4FRED/ply5eaGxkQYqdGvHSqGSN5Rb3vr5Ek0xpzSjeYD7C8Kw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-login@3.940.0': - resolution: {integrity: sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==} + '@aws-sdk/credential-provider-login@3.943.0': + resolution: {integrity: sha512-9iCOVkiRW+evxiJE94RqosCwRrzptAVPhRhGWv4osfYDhjNAvUMyrnZl3T1bjqCoKNcETRKEZIU3dqYHnUkcwQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.940.0': - resolution: {integrity: sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==} + '@aws-sdk/credential-provider-node@3.943.0': + resolution: {integrity: sha512-14eddaH/gjCWoLSAELVrFOQNyswUYwWphIt+PdsJ/FqVfP4ay2HsiZVEIYbQtmrKHaoLJhiZKwBQRjcqJDZG0w==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.940.0': - resolution: {integrity: sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==} + '@aws-sdk/credential-provider-process@3.943.0': + resolution: {integrity: sha512-GIY/vUkthL33AdjOJ8r9vOosKf/3X+X7LIiACzGxvZZrtoOiRq0LADppdiKIB48vTL63VvW+eRIOFAxE6UDekw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.940.0': - resolution: {integrity: sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==} + '@aws-sdk/credential-provider-sso@3.943.0': + resolution: {integrity: sha512-1c5G11syUrru3D9OO6Uk+ul5e2lX1adb+7zQNyluNaLPXP6Dina6Sy6DFGRLu7tM8+M7luYmbS3w63rpYpaL+A==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.940.0': - resolution: {integrity: sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==} + '@aws-sdk/credential-provider-web-identity@3.943.0': + resolution: {integrity: sha512-VtyGKHxICSb4kKGuaqotxso8JVM8RjCS3UYdIMOxUt9TaFE/CZIfZKtjTr+IJ7M0P7t36wuSUb/jRLyNmGzUUA==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.936.0': @@ -4640,8 +4643,8 @@ packages: resolution: {integrity: sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.940.0': - resolution: {integrity: sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==} + '@aws-sdk/middleware-flexible-checksums@3.943.0': + resolution: {integrity: sha512-J2oYbAQXTFEezs5m2Vij6H3w71K1hZfCtb85AsR/2Ovp/FjABMnK+Es1g1edRx6KuMTc9HkL/iGU4e+ek+qCZw==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-host-header@3.936.0': @@ -4660,32 +4663,32 @@ packages: resolution: {integrity: sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.940.0': - resolution: {integrity: sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==} + '@aws-sdk/middleware-sdk-s3@3.943.0': + resolution: {integrity: sha512-kd2mALfthU+RS9NsPS+qvznFcPnVgVx9mgmStWCPn5Qc5BTnx4UAtm+HPA+XZs+zxOopp+zmAfE4qxDHRVONBA==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-ssec@3.936.0': resolution: {integrity: sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.940.0': - resolution: {integrity: sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==} + '@aws-sdk/middleware-user-agent@3.943.0': + resolution: {integrity: sha512-956n4kVEwFNXndXfhSAN5wO+KRgqiWEEY+ECwLvxmmO8uQ0NWOa8l6l65nTtyuiWzMX81c9BvlyNR5EgUeeUvA==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.940.0': - resolution: {integrity: sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==} + '@aws-sdk/nested-clients@3.943.0': + resolution: {integrity: sha512-anFtB0p2FPuyUnbOULwGmKYqYKSq1M73c9uZ08jR/NCq6Trjq9cuF5TFTeHwjJyPRb4wMf2Qk859oiVfFqnQiw==} engines: {node: '>=18.0.0'} '@aws-sdk/region-config-resolver@3.936.0': resolution: {integrity: sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.940.0': - resolution: {integrity: sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==} + '@aws-sdk/signature-v4-multi-region@3.943.0': + resolution: {integrity: sha512-KKvmxNQ/FZbM6ml6nKd8ltDulsUojsXnMJNgf1VHTcJEbADC/6mVWOq0+e9D0WP1qixUBEuMjlS2HqD5KoqwEg==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.940.0': - resolution: {integrity: sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==} + '@aws-sdk/token-providers@3.943.0': + resolution: {integrity: sha512-cRKyIzwfkS+XztXIFPoWORuaxlIswP+a83BJzelX4S1gUZ7FcXB4+lj9Jxjn8SbQhR4TPU3Owbpu+S7pd6IRbQ==} engines: {node: '>=18.0.0'} '@aws-sdk/types@3.936.0': @@ -4707,8 +4710,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.936.0': resolution: {integrity: sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==} - '@aws-sdk/util-user-agent-node@3.940.0': - resolution: {integrity: sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==} + '@aws-sdk/util-user-agent-node@3.943.0': + resolution: {integrity: sha512-gn+ILprVRrgAgTIBk2TDsJLRClzIOdStQFeFTcN0qpL8Z4GBCqMFhw7O7X+MM55Stt5s4jAauQ/VvoqmCADnQg==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -5593,11 +5596,11 @@ packages: cpu: [x64] os: [win32] - '@cacheable/memory@2.0.5': - resolution: {integrity: sha512-fkiAxCvssEyJZ5fxX4tcdZFRmW9JehSTGvvqmXn6rTzG5cH6V/3C4ad8yb01vOjp2xBydHkHrgpW0qeGtzt6VQ==} + '@cacheable/memory@2.0.6': + resolution: {integrity: sha512-7e8SScMocHxcAb8YhtkbMhGG+EKLRIficb1F5sjvhSYsWTZGxvg4KIDp8kgxnV2PUJ3ddPe6J9QESjKvBWRDkg==} - '@cacheable/utils@2.3.1': - resolution: {integrity: sha512-38NJXjIr4W1Sghun8ju+uYWD8h2c61B4dKwfnQHVDFpAJ9oS28RpfqZQJ6Dgd3RceGkILDY9YT+72HJR3LoeSQ==} + '@cacheable/utils@2.3.2': + resolution: {integrity: sha512-8kGE2P+HjfY8FglaOiW+y8qxcaQAfAhVML+i66XJR3YX5FtyDqn6Txctr3K2FrbxLKixRRYYBWMbuGciOhYNDg==} '@cnakazawa/watch@1.0.4': resolution: {integrity: sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==} @@ -5730,6 +5733,10 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.20': + resolution: {integrity: sha512-8BHsjXfSciZxjmHQOuVdW2b8WLUPts9a+mfL13/PzEviufUEW2xnvQuOlKs9dRBHgRqJ53SF/DUoK9+MZk72oQ==} + engines: {node: '>=18'} + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} @@ -6034,8 +6041,8 @@ packages: resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@8.57.1': @@ -6574,8 +6581,8 @@ packages: peerDependencies: yjs: '>=13.5.22' - '@lezer/common@1.3.0': - resolution: {integrity: sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==} + '@lezer/common@1.4.0': + resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} '@lezer/cpp@1.1.3': resolution: {integrity: sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==} @@ -6601,8 +6608,8 @@ packages: '@lezer/json@1.0.3': resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - '@lezer/lr@1.4.3': - resolution: {integrity: sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA==} + '@lezer/lr@1.4.4': + resolution: {integrity: sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==} '@lezer/markdown@1.6.0': resolution: {integrity: sha512-AXb98u3M6BEzTnreBnGtQaF7xFTiMA92Dsy5tqEjpacbjRxDSFdN4bKJo9uvU4cEEOS7D2B9MT7kvDgOEIzJSw==} @@ -6727,8 +6734,8 @@ packages: engines: {node: '>=22.7.5'} hasBin: true - '@modelcontextprotocol/sdk@1.23.0': - resolution: {integrity: sha512-MCGd4K9aZKvuSqdoBkdMvZNcYXCkZRYVs/Gh92mdV5IHbctX9H9uIvd4X93+9g8tBbXv08sxc/QHXTzf8y65bA==} + '@modelcontextprotocol/sdk@1.24.0': + resolution: {integrity: sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -8213,8 +8220,8 @@ packages: resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} engines: {node: '>=18.0.0'} - '@smithy/core@3.18.5': - resolution: {integrity: sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==} + '@smithy/core@3.18.6': + resolution: {integrity: sha512-8Q/ugWqfDUEU1Exw71+DoOzlONJ2Cn9QA8VeeDzLLjzO/qruh9UKFzbszy4jXcIYgGofxYiT0t1TT6+CT/GupQ==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@4.2.5': @@ -8277,12 +8284,12 @@ packages: resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.3.12': - resolution: {integrity: sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==} + '@smithy/middleware-endpoint@4.3.13': + resolution: {integrity: sha512-X4za1qCdyx1hEVVXuAWlZuK6wzLDv1uw1OY9VtaYy1lULl661+frY7FeuHdYdl7qAARUxH2yvNExU2/SmRFfcg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.12': - resolution: {integrity: sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==} + '@smithy/middleware-retry@4.4.13': + resolution: {integrity: sha512-RzIDF9OrSviXX7MQeKOm8r/372KTyY8Jmp6HNKOOYlrguHADuM3ED/f4aCyNhZZFLG55lv5beBin7nL0Nzy1Dw==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@4.2.6': @@ -8329,8 +8336,8 @@ packages: resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.9.8': - resolution: {integrity: sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==} + '@smithy/smithy-client@4.9.9': + resolution: {integrity: sha512-SUnZJMMo5yCmgjopJbiNeo1vlr8KvdnEfIHV9rlD77QuOGdRotIVBcOrBuMr+sI9zrnhtDtLP054bZVbpZpiQA==} engines: {node: '>=18.0.0'} '@smithy/types@4.9.0': @@ -8365,12 +8372,12 @@ packages: resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.3.11': - resolution: {integrity: sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==} + '@smithy/util-defaults-mode-browser@4.3.12': + resolution: {integrity: sha512-TKc6FnOxFULKxLgTNHYjcFqdOYzXVPFFVm5JhI30F3RdhT7nYOtOsjgaOwfDRmA/3U66O9KaBQ3UHoXwayRhAg==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.2.14': - resolution: {integrity: sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==} + '@smithy/util-defaults-mode-node@4.2.15': + resolution: {integrity: sha512-94NqfQVo+vGc5gsQ9SROZqOvBkGNMQu6pjXbnn8aQvBUhc31kx49gxlkBEqgmaZQHUUfdRUin5gK/HlHKmbAwg==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@3.2.5': @@ -10327,8 +10334,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.48.0': - resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} + '@typescript-eslint/project-service@8.48.1': + resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -10353,8 +10360,8 @@ packages: resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.48.0': - resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} + '@typescript-eslint/scope-manager@8.48.1': + resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.33.1': @@ -10363,8 +10370,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/tsconfig-utils@8.48.0': - resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} + '@typescript-eslint/tsconfig-utils@8.48.1': + resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -10433,8 +10440,8 @@ packages: resolution: {integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.48.0': - resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} + '@typescript-eslint/types@8.48.1': + resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@2.34.0': @@ -10485,8 +10492,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/typescript-estree@8.48.0': - resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} + '@typescript-eslint/typescript-estree@8.48.1': + resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -10523,8 +10530,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.48.0': - resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} + '@typescript-eslint/utils@8.48.1': + resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -10550,8 +10557,8 @@ packages: resolution: {integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.48.0': - resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} + '@typescript-eslint/visitor-keys@8.48.1': + resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typespec/ts-http-runtime@0.3.2': @@ -11067,8 +11074,8 @@ packages: resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} engines: {node: '>=12'} - ai@5.0.102: - resolution: {integrity: sha512-snRK3nS5DESOjjpq7S74g8YszWVMzjagfHqlJWZsbtl9PyOS+2XUd8dt2wWg/jdaq/jh0aU66W1mx5qFjUQyEg==} + ai@5.0.106: + resolution: {integrity: sha512-M5obwavxSJJ3tGlAFqI6eltYNJB0D20X6gIBCFx/KVorb/X1fxVVfiZZpZb+Gslu4340droSOjT0aKQFCarNVg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -11937,8 +11944,8 @@ packages: bare-abort-controller: optional: true - bare-fs@4.5.1: - resolution: {integrity: sha512-zGUCsm3yv/ePt2PHNbVxjjn0nNB1MkIaR4wOCxJ2ig5pCf5cCVAYJXVhQg/3OhhJV6DB1ts7Hv0oUaElc2TPQg==} + bare-fs@4.5.2: + resolution: {integrity: sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -11973,8 +11980,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.31: - resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==} + baseline-browser-mapping@2.8.32: + resolution: {integrity: sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==} hasBin: true basic-auth@2.0.1: @@ -12039,8 +12046,8 @@ packages: blueimp-md5@2.19.0: resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} body-parser@2.2.1: @@ -12056,8 +12063,8 @@ packages: boundary@2.0.0: resolution: {integrity: sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==} - bowser@2.13.0: - resolution: {integrity: sha512-yHAbSRuT6LTeKi6k2aS40csueHqgAsFEgmrOsfRyFpJnFv5O2hl9FYmWEUZ97gZ/dG17U4IQQcTx4YAFYPuWRQ==} + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} boxen@1.3.0: resolution: {integrity: sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==} @@ -12218,8 +12225,8 @@ packages: resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} engines: {node: '>=18'} - cacheable@2.2.0: - resolution: {integrity: sha512-LEJxRqfeomiiRd2t0uON6hxAtgOoWDfY3fugebbz+J3vDLO+SkdfFChQcOHTZhj9SYa9iwE9MGYNX72dKiOE4w==} + cacheable@2.3.0: + resolution: {integrity: sha512-HHiAvOBmlcR2f3SQ7kdlYD8+AUJG+wlFZ/Ze8tl1Vzvz0MdOh8IYA/EFU4ve8t1/sZ0j4MGi7ST5MoTwHessQA==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -12838,17 +12845,13 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -13628,8 +13631,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.260: - resolution: {integrity: sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==} + electron-to-chromium@1.5.263: + resolution: {integrity: sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==} email-addresses@5.0.0: resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} @@ -13714,8 +13717,8 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.20.0: - resolution: {integrity: sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==} + envinfo@7.21.0: + resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} engines: {node: '>=4'} hasBin: true @@ -14181,8 +14184,8 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.6.0: - resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} exenv-es6@1.1.1: @@ -14228,12 +14231,12 @@ packages: peerDependencies: express: '>= 4.11' - express@4.21.2: - resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} ext@1.7.0: @@ -14457,13 +14460,13 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} find-cache-dir@1.0.0: resolution: {integrity: sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==} @@ -15137,8 +15140,8 @@ packages: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} - hashery@1.2.0: - resolution: {integrity: sha512-43XJKpwle72Ik5Zpam7MuzRWyNdwwdf6XHlh8wCj2PggvWf+v/Dm5B0dxGZOmddidgeO6Ofu9As/o231Ti/9PA==} + hashery@1.3.0: + resolution: {integrity: sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==} engines: {node: '>=20'} hasown@2.0.2: @@ -15623,8 +15626,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} is-absolute-url@2.1.0: @@ -16699,6 +16702,9 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jpjs@1.2.1: resolution: {integrity: sha512-GxJWybWU4NV0RNKi6EIqk6IRPOTqd/h+U7sbtyuD7yUISUzV78LdHnq2xkevJsTlz/EImux4sWj+wfMiwKLkiw==} @@ -17210,8 +17216,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.2.4: + resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} lru-cache@4.1.5: @@ -17446,8 +17452,8 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - memfs@4.51.0: - resolution: {integrity: sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A==} + memfs@4.51.1: + resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} memoizee@0.4.17: resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} @@ -19205,8 +19211,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} postcss-svgo@2.1.6: @@ -19482,10 +19488,6 @@ packages: resolution: {integrity: sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==} engines: {node: '>=20'} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - qs@6.14.0: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} @@ -19541,8 +19543,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} raw-body@3.0.2: @@ -19841,8 +19843,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -20618,6 +20620,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@0.19.1: + resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} + engines: {node: '>= 0.8.0'} + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -21293,8 +21299,8 @@ packages: peerDependencies: stylelint: ^16.18.0 - stylelint@16.26.0: - resolution: {integrity: sha512-Y/3AVBefrkqqapVYH3LBF5TSDZ1kw+0XpdKN2KchfuhMK6lQ85S4XOG4lIZLcrcS4PWBmvcY6eS2kCQFz0jukQ==} + stylelint@16.26.1: + resolution: {integrity: sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==} engines: {node: '>=18.12.0'} hasBin: true @@ -23199,8 +23205,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} hasBin: true @@ -23291,8 +23297,8 @@ packages: zod@4.1.11: resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} - zustand@5.0.8: - resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + zustand@5.0.9: + resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -23319,50 +23325,50 @@ snapshots: '@adobe/css-tools@4.4.4': {} - '@ai-sdk/amazon-bedrock@3.0.61(zod@4.1.11)': + '@ai-sdk/amazon-bedrock@3.0.65(zod@4.1.11)': dependencies: - '@ai-sdk/anthropic': 2.0.49(zod@4.1.11) + '@ai-sdk/anthropic': 2.0.53(zod@4.1.11) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.11) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.11) '@smithy/eventstream-codec': 4.2.5 '@smithy/util-utf8': 4.2.0 aws4fetch: 1.0.20 zod: 4.1.11 - '@ai-sdk/anthropic@2.0.49(zod@3.25.76)': + '@ai-sdk/anthropic@2.0.53(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.18(zod@3.25.76) zod: 3.25.76 - '@ai-sdk/anthropic@2.0.49(zod@4.1.11)': + '@ai-sdk/anthropic@2.0.53(zod@4.1.11)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.11) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.11) zod: 4.1.11 - '@ai-sdk/gateway@2.0.15(zod@3.25.76)': + '@ai-sdk/gateway@2.0.18(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.18(zod@3.25.76) '@vercel/oidc': 3.0.5 zod: 3.25.76 - '@ai-sdk/gateway@2.0.15(zod@4.1.11)': + '@ai-sdk/gateway@2.0.18(zod@4.1.11)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.11) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.11) '@vercel/oidc': 3.0.5 zod: 4.1.11 - '@ai-sdk/provider-utils@3.0.17(zod@3.25.76)': + '@ai-sdk/provider-utils@3.0.18(zod@3.25.76)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 eventsource-parser: 3.0.6 zod: 3.25.76 - '@ai-sdk/provider-utils@3.0.17(zod@4.1.11)': + '@ai-sdk/provider-utils@3.0.18(zod@4.1.11)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 @@ -23443,31 +23449,31 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.940.0': + '@aws-sdk/client-s3@3.943.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.940.0 - '@aws-sdk/credential-provider-node': 3.940.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/credential-provider-node': 3.943.0 '@aws-sdk/middleware-bucket-endpoint': 3.936.0 '@aws-sdk/middleware-expect-continue': 3.936.0 - '@aws-sdk/middleware-flexible-checksums': 3.940.0 + '@aws-sdk/middleware-flexible-checksums': 3.943.0 '@aws-sdk/middleware-host-header': 3.936.0 '@aws-sdk/middleware-location-constraint': 3.936.0 '@aws-sdk/middleware-logger': 3.936.0 '@aws-sdk/middleware-recursion-detection': 3.936.0 - '@aws-sdk/middleware-sdk-s3': 3.940.0 + '@aws-sdk/middleware-sdk-s3': 3.943.0 '@aws-sdk/middleware-ssec': 3.936.0 - '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/middleware-user-agent': 3.943.0 '@aws-sdk/region-config-resolver': 3.936.0 - '@aws-sdk/signature-v4-multi-region': 3.940.0 + '@aws-sdk/signature-v4-multi-region': 3.943.0 '@aws-sdk/types': 3.936.0 '@aws-sdk/util-endpoints': 3.936.0 '@aws-sdk/util-user-agent-browser': 3.936.0 - '@aws-sdk/util-user-agent-node': 3.940.0 + '@aws-sdk/util-user-agent-node': 3.943.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/eventstream-serde-browser': 4.2.5 '@smithy/eventstream-serde-config-resolver': 4.3.5 '@smithy/eventstream-serde-node': 4.2.5 @@ -23478,21 +23484,21 @@ snapshots: '@smithy/invalid-dependency': 4.2.5 '@smithy/md5-js': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.12 - '@smithy/middleware-retry': 4.4.12 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 '@smithy/middleware-serde': 4.2.6 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.11 - '@smithy/util-defaults-mode-node': 4.2.14 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -23503,41 +23509,41 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.940.0': + '@aws-sdk/client-sso@3.943.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/middleware-host-header': 3.936.0 '@aws-sdk/middleware-logger': 3.936.0 '@aws-sdk/middleware-recursion-detection': 3.936.0 - '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/middleware-user-agent': 3.943.0 '@aws-sdk/region-config-resolver': 3.936.0 '@aws-sdk/types': 3.936.0 '@aws-sdk/util-endpoints': 3.936.0 '@aws-sdk/util-user-agent-browser': 3.936.0 - '@aws-sdk/util-user-agent-node': 3.940.0 + '@aws-sdk/util-user-agent-node': 3.943.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/fetch-http-handler': 5.3.6 '@smithy/hash-node': 4.2.5 '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.12 - '@smithy/middleware-retry': 4.4.12 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 '@smithy/middleware-serde': 4.2.6 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.11 - '@smithy/util-defaults-mode-node': 4.2.14 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -23546,53 +23552,53 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.940.0': + '@aws-sdk/core@3.943.0': dependencies: '@aws-sdk/types': 3.936.0 '@aws-sdk/xml-builder': 3.930.0 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/node-config-provider': 4.3.5 '@smithy/property-provider': 4.2.5 '@smithy/protocol-http': 5.3.5 '@smithy/signature-v4': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/util-base64': 4.3.0 '@smithy/util-middleware': 4.2.5 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.940.0': + '@aws-sdk/credential-provider-env@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.940.0': + '@aws-sdk/credential-provider-http@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/fetch-http-handler': 5.3.6 '@smithy/node-http-handler': 4.4.5 '@smithy/property-provider': 4.2.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/util-stream': 4.5.6 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.940.0': + '@aws-sdk/credential-provider-ini@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 - '@aws-sdk/credential-provider-env': 3.940.0 - '@aws-sdk/credential-provider-http': 3.940.0 - '@aws-sdk/credential-provider-login': 3.940.0 - '@aws-sdk/credential-provider-process': 3.940.0 - '@aws-sdk/credential-provider-sso': 3.940.0 - '@aws-sdk/credential-provider-web-identity': 3.940.0 - '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/credential-provider-env': 3.943.0 + '@aws-sdk/credential-provider-http': 3.943.0 + '@aws-sdk/credential-provider-login': 3.943.0 + '@aws-sdk/credential-provider-process': 3.943.0 + '@aws-sdk/credential-provider-sso': 3.943.0 + '@aws-sdk/credential-provider-web-identity': 3.943.0 + '@aws-sdk/nested-clients': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/credential-provider-imds': 4.2.5 '@smithy/property-provider': 4.2.5 @@ -23602,10 +23608,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.940.0': + '@aws-sdk/credential-provider-login@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 - '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/nested-clients': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/protocol-http': 5.3.5 @@ -23615,14 +23621,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.940.0': + '@aws-sdk/credential-provider-node@3.943.0': dependencies: - '@aws-sdk/credential-provider-env': 3.940.0 - '@aws-sdk/credential-provider-http': 3.940.0 - '@aws-sdk/credential-provider-ini': 3.940.0 - '@aws-sdk/credential-provider-process': 3.940.0 - '@aws-sdk/credential-provider-sso': 3.940.0 - '@aws-sdk/credential-provider-web-identity': 3.940.0 + '@aws-sdk/credential-provider-env': 3.943.0 + '@aws-sdk/credential-provider-http': 3.943.0 + '@aws-sdk/credential-provider-ini': 3.943.0 + '@aws-sdk/credential-provider-process': 3.943.0 + '@aws-sdk/credential-provider-sso': 3.943.0 + '@aws-sdk/credential-provider-web-identity': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/credential-provider-imds': 4.2.5 '@smithy/property-provider': 4.2.5 @@ -23632,20 +23638,20 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.940.0': + '@aws-sdk/credential-provider-process@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/shared-ini-file-loader': 4.4.0 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.940.0': + '@aws-sdk/credential-provider-sso@3.943.0': dependencies: - '@aws-sdk/client-sso': 3.940.0 - '@aws-sdk/core': 3.940.0 - '@aws-sdk/token-providers': 3.940.0 + '@aws-sdk/client-sso': 3.943.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/token-providers': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/shared-ini-file-loader': 4.4.0 @@ -23654,10 +23660,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.940.0': + '@aws-sdk/credential-provider-web-identity@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 - '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/nested-clients': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/shared-ini-file-loader': 4.4.0 @@ -23683,12 +23689,12 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.940.0': + '@aws-sdk/middleware-flexible-checksums@3.943.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/is-array-buffer': 4.2.0 '@smithy/node-config-provider': 4.3.5 @@ -23726,16 +23732,16 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.940.0': + '@aws-sdk/middleware-sdk-s3@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@aws-sdk/util-arn-parser': 3.893.0 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/node-config-provider': 4.3.5 '@smithy/protocol-http': 5.3.5 '@smithy/signature-v4': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/util-config-provider': 4.2.0 '@smithy/util-middleware': 4.2.5 @@ -23749,51 +23755,51 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.940.0': + '@aws-sdk/middleware-user-agent@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/types': 3.936.0 '@aws-sdk/util-endpoints': 3.936.0 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/protocol-http': 5.3.5 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.940.0': + '@aws-sdk/nested-clients@3.943.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.940.0 + '@aws-sdk/core': 3.943.0 '@aws-sdk/middleware-host-header': 3.936.0 '@aws-sdk/middleware-logger': 3.936.0 '@aws-sdk/middleware-recursion-detection': 3.936.0 - '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/middleware-user-agent': 3.943.0 '@aws-sdk/region-config-resolver': 3.936.0 '@aws-sdk/types': 3.936.0 '@aws-sdk/util-endpoints': 3.936.0 '@aws-sdk/util-user-agent-browser': 3.936.0 - '@aws-sdk/util-user-agent-node': 3.940.0 + '@aws-sdk/util-user-agent-node': 3.943.0 '@smithy/config-resolver': 4.4.3 - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/fetch-http-handler': 5.3.6 '@smithy/hash-node': 4.2.5 '@smithy/invalid-dependency': 4.2.5 '@smithy/middleware-content-length': 4.2.5 - '@smithy/middleware-endpoint': 4.3.12 - '@smithy/middleware-retry': 4.4.12 + '@smithy/middleware-endpoint': 4.3.13 + '@smithy/middleware-retry': 4.4.13 '@smithy/middleware-serde': 4.2.6 '@smithy/middleware-stack': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/node-http-handler': 4.4.5 '@smithy/protocol-http': 5.3.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/url-parser': 4.2.5 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.11 - '@smithy/util-defaults-mode-node': 4.2.14 + '@smithy/util-defaults-mode-browser': 4.3.12 + '@smithy/util-defaults-mode-node': 4.2.15 '@smithy/util-endpoints': 3.2.5 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -23810,19 +23816,19 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.940.0': + '@aws-sdk/signature-v4-multi-region@3.943.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.940.0 + '@aws-sdk/middleware-sdk-s3': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/protocol-http': 5.3.5 '@smithy/signature-v4': 5.3.5 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.940.0': + '@aws-sdk/token-providers@3.943.0': dependencies: - '@aws-sdk/core': 3.940.0 - '@aws-sdk/nested-clients': 3.940.0 + '@aws-sdk/core': 3.943.0 + '@aws-sdk/nested-clients': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/property-provider': 4.2.5 '@smithy/shared-ini-file-loader': 4.4.0 @@ -23856,12 +23862,12 @@ snapshots: dependencies: '@aws-sdk/types': 3.936.0 '@smithy/types': 4.9.0 - bowser: 2.13.0 + bowser: 2.13.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.940.0': + '@aws-sdk/util-user-agent-node@3.943.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.940.0 + '@aws-sdk/middleware-user-agent': 3.943.0 '@aws-sdk/types': 3.936.0 '@smithy/node-config-provider': 4.3.5 '@smithy/types': 4.9.0 @@ -25692,16 +25698,16 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true - '@cacheable/memory@2.0.5': + '@cacheable/memory@2.0.6': dependencies: - '@cacheable/utils': 2.3.1 + '@cacheable/utils': 2.3.2 '@keyv/bigmap': 1.3.0(keyv@5.5.4) hookified: 1.13.0 keyv: 5.5.4 - '@cacheable/utils@2.3.1': + '@cacheable/utils@2.3.2': dependencies: - hashery: 1.2.0 + hashery: 1.3.0 keyv: 5.5.4 '@cnakazawa/watch@1.0.4': @@ -25714,23 +25720,23 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@codemirror/commands@6.10.0': dependencies: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@codemirror/lang-angular@0.1.4': dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-cpp@6.0.3': dependencies: @@ -25742,7 +25748,7 @@ snapshots: '@codemirror/autocomplete': 6.19.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@codemirror/lang-go@6.0.1': @@ -25750,7 +25756,7 @@ snapshots: '@codemirror/autocomplete': 6.19.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/go': 1.0.1 '@codemirror/lang-html@6.4.11': @@ -25761,7 +25767,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/css': 1.3.0 '@lezer/html': 1.3.12 @@ -25777,16 +25783,16 @@ snapshots: '@codemirror/lint': 6.8.5 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/javascript': 1.5.4 '@codemirror/lang-jinja@6.0.0': dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-json@6.0.2': dependencies: @@ -25797,9 +25803,9 @@ snapshots: dependencies: '@codemirror/lang-css': 6.3.1 '@codemirror/language': 6.11.3 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-liquid@6.3.0': dependencies: @@ -25808,9 +25814,9 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-markdown@6.5.0': dependencies: @@ -25819,7 +25825,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/markdown': 1.6.0 '@codemirror/lang-php@6.0.2': @@ -25827,7 +25833,7 @@ snapshots: '@codemirror/lang-html': 6.4.11 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/php': 1.0.5 '@codemirror/lang-python@6.2.1': @@ -25835,7 +25841,7 @@ snapshots: '@codemirror/autocomplete': 6.19.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/python': 1.1.18 '@codemirror/lang-rust@6.0.2': @@ -25848,7 +25854,7 @@ snapshots: '@codemirror/lang-css': 6.3.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/sass': 1.1.0 '@codemirror/lang-sql@6.10.0': @@ -25856,25 +25862,25 @@ snapshots: '@codemirror/autocomplete': 6.19.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-vue@0.1.3': dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/lang-javascript': 6.2.4 '@codemirror/language': 6.11.3 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-wast@6.0.2': dependencies: '@codemirror/language': 6.11.3 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@codemirror/lang-xml@6.1.0': dependencies: @@ -25882,7 +25888,7 @@ snapshots: '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/xml': 1.0.6 '@codemirror/lang-yaml@6.1.2': @@ -25890,9 +25896,9 @@ snapshots: '@codemirror/autocomplete': 6.19.1 '@codemirror/language': 6.11.3 '@codemirror/state': 6.5.2 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/yaml': 1.0.3 '@codemirror/language-data@6.5.2': @@ -25925,9 +25931,9 @@ snapshots: dependencies: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.8 - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 style-mod: 4.1.3 '@codemirror/legacy-modes@6.5.2': @@ -26023,6 +26029,8 @@ snapshots: dependencies: '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.20': {} + '@csstools/css-tokenizer@3.0.4': {} '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -26030,9 +26038,9 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': dependencies: - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 '@dabh/diagnostics@2.0.8': dependencies: @@ -26383,7 +26391,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 debug: 4.4.3(supports-color@8.1.1) @@ -27235,7 +27243,7 @@ snapshots: '@keyv/bigmap@1.3.0(keyv@5.5.4)': dependencies: - hashery: 1.2.0 + hashery: 1.3.0 hookified: 1.13.0 keyv: 5.5.4 @@ -27387,98 +27395,98 @@ snapshots: '@lexical/offset': 0.17.1 lexical: 0.17.1 - '@lezer/common@1.3.0': {} + '@lezer/common@1.4.0': {} '@lezer/cpp@1.1.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/css@1.3.0': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/go@1.0.1': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/highlight@1.2.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/html@1.3.12': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/java@1.1.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/javascript@1.5.4': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/json@1.0.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 - '@lezer/lr@1.4.3': + '@lezer/lr@1.4.4': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/markdown@1.6.0': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 '@lezer/php@1.0.5': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/python@1.1.18': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/rust@1.0.2': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/sass@1.1.0': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/xml@1.0.6': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@lezer/yaml@1.0.3': dependencies: - '@lezer/common': 1.3.0 + '@lezer/common': 1.4.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.3 + '@lezer/lr': 1.4.4 '@marijn/find-cluster-break@1.0.2': {} @@ -27691,7 +27699,7 @@ snapshots: '@modelcontextprotocol/inspector-cli@0.17.2': dependencies: - '@modelcontextprotocol/sdk': 1.23.0 + '@modelcontextprotocol/sdk': 1.24.0 commander: 13.1.0 spawn-rx: 5.1.2 transitivePeerDependencies: @@ -27700,7 +27708,7 @@ snapshots: '@modelcontextprotocol/inspector-client@0.17.2(@types/react-dom@18.2.0)(@types/react@18.2.0)': dependencies: - '@modelcontextprotocol/sdk': 1.23.0 + '@modelcontextprotocol/sdk': 1.24.0 '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.2.0)(@types/react@18.2.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.2.0)(@types/react@18.2.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-icons': 1.3.2(react@18.3.1) @@ -27733,9 +27741,9 @@ snapshots: '@modelcontextprotocol/inspector-server@0.17.2': dependencies: - '@modelcontextprotocol/sdk': 1.23.0 + '@modelcontextprotocol/sdk': 1.24.0 cors: 2.8.5 - express: 5.1.0 + express: 5.2.1 shell-quote: 1.8.3 spawn-rx: 5.1.2 ws: 8.18.3 @@ -27751,7 +27759,7 @@ snapshots: '@modelcontextprotocol/inspector-cli': 0.17.2 '@modelcontextprotocol/inspector-client': 0.17.2(@types/react-dom@18.2.0)(@types/react@18.2.0) '@modelcontextprotocol/inspector-server': 0.17.2 - '@modelcontextprotocol/sdk': 1.23.0 + '@modelcontextprotocol/sdk': 1.24.0 concurrently: 9.2.1 node-fetch: 3.3.2 open: 10.2.0 @@ -27771,7 +27779,7 @@ snapshots: - typescript - utf-8-validate - '@modelcontextprotocol/sdk@1.23.0': + '@modelcontextprotocol/sdk@1.24.0': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -27780,8 +27788,9 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 - express: 5.1.0 - express-rate-limit: 7.5.1(express@5.1.0) + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 pkce-challenge: 5.0.1 raw-body: 3.0.2 zod: 3.25.76 @@ -28523,7 +28532,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.2.0) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.2.0) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -28545,7 +28554,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.3.1) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -28798,7 +28807,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.2.0) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.2.0) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -28821,7 +28830,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.2.0) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.2.0) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -28844,7 +28853,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.3.1) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -29183,7 +29192,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.2.0) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.2.0) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -29212,7 +29221,7 @@ snapshots: aria-hidden: 1.2.6 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-remove-scroll: 2.7.1(@types/react@18.2.0)(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.2.0)(react@18.3.1) optionalDependencies: '@types/react': 18.2.0 '@types/react-dom': 18.2.0 @@ -30165,7 +30174,7 @@ snapshots: '@smithy/util-middleware': 4.2.5 tslib: 2.8.1 - '@smithy/core@3.18.5': + '@smithy/core@3.18.6': dependencies: '@smithy/middleware-serde': 4.2.6 '@smithy/protocol-http': 5.3.5 @@ -30269,9 +30278,9 @@ snapshots: '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.3.12': + '@smithy/middleware-endpoint@4.3.13': dependencies: - '@smithy/core': 3.18.5 + '@smithy/core': 3.18.6 '@smithy/middleware-serde': 4.2.6 '@smithy/node-config-provider': 4.3.5 '@smithy/shared-ini-file-loader': 4.4.0 @@ -30280,12 +30289,12 @@ snapshots: '@smithy/util-middleware': 4.2.5 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.12': + '@smithy/middleware-retry@4.4.13': dependencies: '@smithy/node-config-provider': 4.3.5 '@smithy/protocol-http': 5.3.5 '@smithy/service-error-classification': 4.2.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 '@smithy/util-middleware': 4.2.5 '@smithy/util-retry': 4.2.5 @@ -30359,10 +30368,10 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@4.9.8': + '@smithy/smithy-client@4.9.9': dependencies: - '@smithy/core': 3.18.5 - '@smithy/middleware-endpoint': 4.3.12 + '@smithy/core': 3.18.6 + '@smithy/middleware-endpoint': 4.3.13 '@smithy/middleware-stack': 4.2.5 '@smithy/protocol-http': 5.3.5 '@smithy/types': 4.9.0 @@ -30407,20 +30416,20 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.3.11': + '@smithy/util-defaults-mode-browser@4.3.12': dependencies: '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.2.14': + '@smithy/util-defaults-mode-node@4.2.15': dependencies: '@smithy/config-resolver': 4.4.3 '@smithy/credential-provider-imds': 4.2.5 '@smithy/node-config-provider': 4.3.5 '@smithy/property-provider': 4.2.5 - '@smithy/smithy-client': 4.9.8 + '@smithy/smithy-client': 4.9.9 '@smithy/types': 4.9.0 tslib: 2.8.1 @@ -31615,7 +31624,7 @@ snapshots: ejs: 3.1.10 esbuild: 0.25.12 esbuild-plugin-alias: 0.2.1 - express: 4.21.2 + express: 4.22.1 find-cache-dir: 3.3.2 fs-extra: 11.3.2 process: 0.11.10 @@ -32138,7 +32147,7 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 css-loader: 6.11.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@5.103.0) fs-extra: 11.3.2 html-webpack-plugin: 5.6.5(webpack@5.103.0) @@ -32199,7 +32208,7 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 constants-browserify: 1.0.0 css-loader: 6.11.0(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) - express: 4.21.2 + express: 4.22.1 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) fs-extra: 11.3.2 html-webpack-plugin: 5.6.5(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) @@ -32367,9 +32376,9 @@ snapshots: commander: 6.2.1 cross-spawn: 7.0.6 detect-indent: 6.1.0 - envinfo: 7.20.0 + envinfo: 7.21.0 execa: 5.1.1 - express: 4.21.2 + express: 4.22.1 find-up: 5.0.0 fs-extra: 11.3.2 get-npm-tarball-url: 2.1.0 @@ -32715,7 +32724,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.27.0(jiti@2.6.1))(typescript@5.8.3)(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) @@ -32780,7 +32789,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.27.0(jiti@2.6.1))(typescript@5.8.3)(webpack@5.103.0) @@ -32845,7 +32854,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.26.0(jiti@2.6.1))(typescript@5.8.3)(webpack@5.103.0) @@ -32910,7 +32919,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.27.0(jiti@2.6.1))(typescript@5.8.3)(webpack@5.103.0) @@ -32975,7 +32984,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.27.0(jiti@2.6.1))(typescript@5.8.3)(webpack@5.103.0) @@ -33040,7 +33049,7 @@ snapshots: babel-plugin-polyfill-corejs3: 0.1.7(@babel/core@7.27.7) chalk: 4.1.2 core-js: 3.47.0 - express: 4.21.2 + express: 4.22.1 file-system-cache: 1.1.0 find-up: 5.0.0 fork-ts-checker-webpack-plugin: 6.5.3(eslint@9.27.0(jiti@2.6.1))(typescript@4.9.5)(webpack@5.103.0) @@ -33169,7 +33178,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33235,7 +33244,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33301,7 +33310,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33365,7 +33374,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33429,7 +33438,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33493,7 +33502,7 @@ snapshots: core-js: 3.47.0 cpy: 8.1.2 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 9.1.0 global: 4.4.0 globby: 11.1.0 @@ -33556,7 +33565,7 @@ snapshots: cli-table3: 0.6.5 compression: 1.8.1 detect-port: 1.6.1 - express: 4.21.2 + express: 4.22.1 fs-extra: 11.3.2 globby: 11.1.0 lodash: 4.17.21 @@ -33966,7 +33975,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34017,7 +34026,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34068,7 +34077,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34119,7 +34128,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34170,7 +34179,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34221,7 +34230,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 3.6.0(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 file-loader: 6.2.0(webpack@5.103.0) find-up: 5.0.0 fs-extra: 9.1.0 @@ -34271,7 +34280,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 5.2.7(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 find-up: 5.0.0 fs-extra: 9.1.0 html-webpack-plugin: 5.6.5(webpack@5.103.0) @@ -34320,7 +34329,7 @@ snapshots: chalk: 4.1.2 core-js: 3.47.0 css-loader: 5.2.7(webpack@5.103.0) - express: 4.21.2 + express: 4.22.1 find-up: 5.0.0 fs-extra: 9.1.0 html-webpack-plugin: 5.6.5(webpack@5.103.0) @@ -37180,10 +37189,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.48.0(typescript@5.8.3)': + '@typescript-eslint/project-service@8.48.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.8.3) - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.8.3) + '@typescript-eslint/types': 8.48.1 debug: 4.4.3(supports-color@8.1.1) typescript: 5.8.3 transitivePeerDependencies: @@ -37214,16 +37223,16 @@ snapshots: '@typescript-eslint/types': 8.33.1 '@typescript-eslint/visitor-keys': 8.33.1 - '@typescript-eslint/scope-manager@8.48.0': + '@typescript-eslint/scope-manager@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 '@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 @@ -37306,7 +37315,7 @@ snapshots: '@typescript-eslint/types@8.33.1': {} - '@typescript-eslint/types@8.48.0': {} + '@typescript-eslint/types@8.48.1': {} '@typescript-eslint/typescript-estree@2.34.0(typescript@3.9.10)': dependencies: @@ -37396,12 +37405,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.48.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.48.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.48.0(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.8.3) - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/visitor-keys': 8.48.0 + '@typescript-eslint/project-service': 8.48.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.8.3) + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/visitor-keys': 8.48.1 debug: 4.4.3(supports-color@8.1.1) minimatch: 9.0.5 semver: 7.7.3 @@ -37484,12 +37493,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.48.0(eslint@8.57.1)(typescript@5.8.3)': + '@typescript-eslint/utils@8.48.1(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.48.0 - '@typescript-eslint/types': 8.48.0 - '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.48.1 + '@typescript-eslint/types': 8.48.1 + '@typescript-eslint/typescript-estree': 8.48.1(typescript@5.8.3) eslint: 8.57.1 typescript: 5.8.3 transitivePeerDependencies: @@ -37520,9 +37529,9 @@ snapshots: '@typescript-eslint/types': 8.33.1 eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.48.0': + '@typescript-eslint/visitor-keys@8.48.1': dependencies: - '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/types': 8.48.1 eslint-visitor-keys: 4.2.1 '@typespec/ts-http-runtime@0.3.2': @@ -37948,7 +37957,7 @@ snapshots: '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)': dependencies: - envinfo: 7.20.0 + envinfo: 7.21.0 webpack-cli: 4.10.0(webpack-dev-server@5.2.2)(webpack@5.103.0) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.103.0)': @@ -38114,19 +38123,19 @@ snapshots: clean-stack: 4.2.0 indent-string: 5.0.0 - ai@5.0.102(zod@3.25.76): + ai@5.0.106(zod@3.25.76): dependencies: - '@ai-sdk/gateway': 2.0.15(zod@3.25.76) + '@ai-sdk/gateway': 2.0.18(zod@3.25.76) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.18(zod@3.25.76) '@opentelemetry/api': 1.9.0 zod: 3.25.76 - ai@5.0.102(zod@4.1.11): + ai@5.0.106(zod@4.1.11): dependencies: - '@ai-sdk/gateway': 2.0.15(zod@4.1.11) + '@ai-sdk/gateway': 2.0.18(zod@4.1.11) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.17(zod@4.1.11) + '@ai-sdk/provider-utils': 3.0.18(zod@4.1.11) '@opentelemetry/api': 1.9.0 zod: 4.1.11 @@ -39378,7 +39387,7 @@ snapshots: bare-events@2.8.2: {} - bare-fs@4.5.1: + bare-fs@4.5.2: dependencies: bare-events: 2.8.2 bare-path: 3.0.0 @@ -39417,7 +39426,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.31: {} + baseline-browser-mapping@2.8.32: {} basic-auth@2.0.1: dependencies: @@ -39476,18 +39485,18 @@ snapshots: blueimp-md5@2.19.0: {} - body-parser@1.20.3: + body-parser@1.20.4: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 + qs: 6.14.0 + raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -39514,7 +39523,7 @@ snapshots: boundary@2.0.0: {} - bowser@2.13.0: {} + bowser@2.13.1: {} boxen@1.3.0: dependencies: @@ -39571,18 +39580,18 @@ snapshots: browserslist@1.7.7: dependencies: caniuse-db: 1.0.30001757 - electron-to-chromium: 1.5.260 + electron-to-chromium: 1.5.263 browserslist@2.11.3: dependencies: caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.260 + electron-to-chromium: 1.5.263 browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.31 + baseline-browser-mapping: 2.8.32 caniuse-lite: 1.0.30001757 - electron-to-chromium: 1.5.260 + electron-to-chromium: 1.5.263 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -39751,10 +39760,10 @@ snapshots: normalize-url: 8.1.0 responselike: 3.0.0 - cacheable@2.2.0: + cacheable@2.3.0: dependencies: - '@cacheable/memory': 2.0.5 - '@cacheable/utils': 2.3.1 + '@cacheable/memory': 2.0.6 + '@cacheable/utils': 2.3.2 hookified: 1.13.0 keyv: 5.5.4 qified: 0.5.2 @@ -40093,7 +40102,7 @@ snapshots: clipboardy@5.0.1: dependencies: - execa: 9.6.0 + execa: 9.6.1 is-wayland: 0.1.0 is-wsl: 3.1.0 is64bit: 2.0.0 @@ -40380,12 +40389,10 @@ snapshots: convert-source-map@2.0.0: {} - cookie-signature@1.0.6: {} + cookie-signature@1.0.7: {} cookie-signature@1.2.2: {} - cookie@0.7.1: {} - cookie@0.7.2: {} copy-concurrently@1.0.5: @@ -41360,7 +41367,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.260: {} + electron-to-chromium@1.5.263: {} email-addresses@5.0.0: {} @@ -41435,7 +41442,7 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.20.0: {} + envinfo@7.21.0: {} environment@1.1.0: {} @@ -41842,7 +41849,7 @@ snapshots: eslint-plugin-storybook@9.1.16(eslint@8.57.1)(storybook@9.1.16(@testing-library/dom@10.4.1)(prettier@3.5.3))(typescript@5.8.3): dependencies: - '@typescript-eslint/utils': 8.48.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 8.48.1(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 storybook: 9.1.16(@testing-library/dom@10.4.1)(prettier@3.5.3) transitivePeerDependencies: @@ -41989,13 +41996,13 @@ snapshots: '@eslint/config-array': 0.20.1 '@eslint/config-helpers': 0.2.3 '@eslint/core': 0.13.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.26.0 '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@modelcontextprotocol/sdk': 1.23.0 + '@modelcontextprotocol/sdk': 1.24.0 '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -42034,7 +42041,7 @@ snapshots: '@eslint/config-array': 0.20.1 '@eslint/config-helpers': 0.2.3 '@eslint/core': 0.14.0 - '@eslint/eslintrc': 3.3.1 + '@eslint/eslintrc': 3.3.3 '@eslint/js': 9.27.0 '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.7 @@ -42247,7 +42254,7 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.6.0: + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.6 @@ -42311,45 +42318,45 @@ snapshots: exponential-backoff@3.1.3: {} - express-rate-limit@7.5.1(express@5.1.0): + express-rate-limit@7.5.1(express@5.2.1): dependencies: - express: 5.1.0 + express: 5.2.1 - express@4.21.2: + express@4.22.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.3 + body-parser: 1.20.4 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.0.6 + cookie: 0.7.2 + cookie-signature: 1.0.7 debug: 2.6.9 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.1 + finalhandler: 1.3.2 fresh: 0.5.2 - http-errors: 2.0.0 + http-errors: 2.0.1 merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.13.0 + qs: 6.14.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.0 + send: 0.19.1 serve-static: 1.16.2 setprototypeof: 1.2.0 - statuses: 2.0.1 + statuses: 2.0.2 type-is: 1.6.18 utils-merge: 1.0.1 vary: 1.1.2 - express@5.1.0: + express@5.2.1: dependencies: accepts: 2.0.0 body-parser: 2.2.1 @@ -42358,10 +42365,11 @@ snapshots: cookie: 0.7.2 cookie-signature: 1.2.2 debug: 4.4.3(supports-color@8.1.1) + depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.0 + finalhandler: 2.1.1 fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -42647,17 +42655,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.1: + finalhandler@1.3.2: dependencies: debug: 2.6.9 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.1 + statuses: 2.0.2 unpipe: 1.0.0 - finalhandler@2.1.0: + finalhandler@2.1.1: dependencies: debug: 4.4.3(supports-color@8.1.1) encodeurl: 2.0.0 @@ -42754,7 +42762,7 @@ snapshots: flat-cache@6.1.19: dependencies: - cacheable: 2.2.0 + cacheable: 2.3.0 flatted: 3.3.3 hookified: 1.13.0 @@ -43595,7 +43603,7 @@ snapshots: has@1.0.4: {} - hashery@1.2.0: + hashery@1.3.0: dependencies: hookified: 1.13.0 @@ -44270,7 +44278,7 @@ snapshots: ipaddr.js@1.9.1: {} - ipaddr.js@2.2.0: {} + ipaddr.js@2.3.0: {} is-absolute-url@2.1.0: {} @@ -46095,6 +46103,8 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + jose@6.1.3: {} + jpjs@1.2.1: {} js-base64@2.6.4: {} @@ -46524,7 +46534,7 @@ snapshots: nano-spawn: 2.0.0 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.8.1 + yaml: 2.8.2 listenercount@1.0.1: {} @@ -46738,7 +46748,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.2: {} + lru-cache@11.2.4: {} lru-cache@4.1.5: dependencies: @@ -47148,7 +47158,7 @@ snapshots: dependencies: fs-monkey: 1.1.0 - memfs@4.51.0: + memfs@4.51.1: dependencies: '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) @@ -48652,7 +48662,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.2 + lru-cache: 11.2.4 minipass: 7.1.2 path-to-regexp@0.1.12: {} @@ -48946,13 +48956,13 @@ snapshots: postcss: 8.5.6 ts-node: 10.9.2(@swc/core@1.15.3(@swc/helpers@0.5.17))(@types/node@22.15.35)(typescript@5.8.3) - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.6 - yaml: 2.8.1 + yaml: 2.8.2 postcss-load-options@1.2.0: dependencies: @@ -49113,7 +49123,7 @@ snapshots: dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 postcss-modules-scope@1.1.0: @@ -49129,7 +49139,7 @@ snapshots: postcss-modules-scope@3.2.1(postcss@8.5.6): dependencies: postcss: 8.5.6 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-modules-values@1.3.0: dependencies: @@ -49274,7 +49284,7 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.0: + postcss-selector-parser@7.1.1: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -49597,10 +49607,6 @@ snapshots: dependencies: hookified: 1.13.0 - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - qs@6.14.0: dependencies: side-channel: 1.1.0 @@ -49645,10 +49651,10 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.5.2: + raw-body@2.5.3: dependencies: bytes: 3.1.2 - http-errors: 2.0.0 + http-errors: 2.0.1 iconv-lite: 0.4.24 unpipe: 1.0.0 @@ -50141,7 +50147,7 @@ snapshots: optionalDependencies: '@types/react': 18.2.0 - react-remove-scroll@2.7.1(@types/react@18.2.0)(react@18.2.0): + react-remove-scroll@2.7.2(@types/react@18.2.0)(react@18.2.0): dependencies: react: 18.2.0 react-remove-scroll-bar: 2.3.8(@types/react@18.2.0)(react@18.2.0) @@ -50152,7 +50158,7 @@ snapshots: optionalDependencies: '@types/react': 18.2.0 - react-remove-scroll@2.7.1(@types/react@18.2.0)(react@18.3.1): + react-remove-scroll@2.7.2(@types/react@18.2.0)(react@18.3.1): dependencies: react: 18.3.1 react-remove-scroll-bar: 2.3.8(@types/react@18.2.0)(react@18.3.1) @@ -51274,6 +51280,22 @@ snapshots: range-parser: 1.2.1 statuses: 2.0.1 + send@0.19.1: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + send@1.2.0: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -52074,21 +52096,22 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 6.1.2 - stylelint-config-recommended@16.0.0(stylelint@16.26.0(typescript@5.8.3)): + stylelint-config-recommended@16.0.0(stylelint@16.26.1(typescript@5.8.3)): dependencies: - stylelint: 16.26.0(typescript@5.8.3) + stylelint: 16.26.1(typescript@5.8.3) - stylelint-config-standard@38.0.0(stylelint@16.26.0(typescript@5.8.3)): + stylelint-config-standard@38.0.0(stylelint@16.26.1(typescript@5.8.3)): dependencies: - stylelint: 16.26.0(typescript@5.8.3) - stylelint-config-recommended: 16.0.0(stylelint@16.26.0(typescript@5.8.3)) + stylelint: 16.26.1(typescript@5.8.3) + stylelint-config-recommended: 16.0.0(stylelint@16.26.1(typescript@5.8.3)) - stylelint@16.26.0(typescript@5.8.3): + stylelint@16.26.1(typescript@5.8.3): dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-syntax-patches-for-csstree': 1.0.20 '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 colord: 2.9.3 @@ -52115,7 +52138,7 @@ snapshots: postcss: 8.5.6 postcss-resolve-nested-selector: 0.1.6 postcss-safe-parser: 7.0.1(postcss@8.5.6) - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 @@ -52431,7 +52454,7 @@ snapshots: tailwind-merge@2.6.0: {} - tailwindcss@3.4.18(yaml@2.8.1): + tailwindcss@3.4.18(yaml@2.8.2): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -52450,7 +52473,7 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.1) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.11 @@ -52472,7 +52495,7 @@ snapshots: pump: 3.0.3 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.5.1 + bare-fs: 4.5.2 bare-path: 3.0.0 transitivePeerDependencies: - bare-abort-controller @@ -54186,7 +54209,7 @@ snapshots: colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 - envinfo: 7.20.0 + envinfo: 7.21.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 @@ -54205,7 +54228,7 @@ snapshots: colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 - envinfo: 7.20.0 + envinfo: 7.21.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 @@ -54222,7 +54245,7 @@ snapshots: colorette: 2.0.20 commander: 12.1.0 cross-spawn: 7.0.6 - envinfo: 7.20.0 + envinfo: 7.21.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 @@ -54241,7 +54264,7 @@ snapshots: colorette: 2.0.20 commander: 12.1.0 cross-spawn: 7.0.6 - envinfo: 7.20.0 + envinfo: 7.21.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 @@ -54310,7 +54333,7 @@ snapshots: webpack-dev-middleware@7.4.5(webpack@5.103.0(@swc/core@1.15.3(@swc/helpers@0.5.17))): dependencies: colorette: 2.0.20 - memfs: 4.51.0 + memfs: 4.51.1 mime-types: 3.0.2 on-finished: 2.4.1 range-parser: 1.2.1 @@ -54322,7 +54345,7 @@ snapshots: webpack-dev-middleware@7.4.5(webpack@5.103.0): dependencies: colorette: 2.0.20 - memfs: 4.51.0 + memfs: 4.51.1 mime-types: 3.0.2 on-finished: 2.4.1 range-parser: 1.2.1 @@ -54346,10 +54369,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -54386,10 +54409,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -54425,10 +54448,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -54464,10 +54487,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -54503,10 +54526,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.21.2 + express: 4.22.1 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.2.0 + ipaddr.js: 2.3.0 launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 @@ -55123,7 +55146,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.8.1: {} + yaml@2.8.2: {} yargs-parser@18.1.3: dependencies: @@ -55258,13 +55281,13 @@ snapshots: zod@4.1.11: {} - zustand@5.0.8(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)): + zustand@5.0.9(@types/react@18.2.0)(react@18.2.0)(use-sync-external-store@1.6.0(react@18.2.0)): optionalDependencies: '@types/react': 18.2.0 react: 18.2.0 use-sync-external-store: 1.6.0(react@18.2.0) - zustand@5.0.8(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): + zustand@5.0.9(@types/react@18.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): optionalDependencies: '@types/react': 18.2.0 react: 19.1.0 diff --git a/package.json b/package.json index cab9d8f485f..70c0fe585c0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@eslint/plugin-kit": "^0.3.4", "on-headers": "^1.1.0", "form-data": "^4.0.4", - "tmp": "^0.2.4" + "tmp": "^0.2.4", + "express": "^4.22.1" } }, "scripts": { diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index a51f86a5e59..c4822db51cf 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -1190,14 +1190,15 @@ "dependencies": { "@ai-sdk/amazon-bedrock": "^3.0.25", "@ai-sdk/anthropic": "^2.0.20", + "@iarna/toml": "^2.2.5", "@types/lodash": "^4.14.200", "@vscode/test-electron": "^2.5.2", "@vscode/vsce": "^3.7.0", "@wso2/ballerina-core": "workspace:*", "@wso2/ballerina-visualizer": "workspace:*", - "@wso2/trace-visualizer": "workspace:*", "@wso2/font-wso2-vscode": "workspace:*", "@wso2/syntax-tree": "workspace:*", + "@wso2/trace-visualizer": "workspace:*", "@wso2/wso2-platform-core": "workspace:*", "ai": "^5.0.56", "cors-anywhere": "^0.4.4", @@ -1212,6 +1213,7 @@ "node-fetch": "^3.3.2", "node-schedule": "^2.1.1", "portfinder": "^1.0.32", + "protobufjs": "^7.2.5", "source-map-support": "^0.5.21", "toml": "^3.0.0", "unzipper": "~0.12.3", @@ -1228,8 +1230,7 @@ "vscode-uri": "^3.0.8", "xml-js": "^1.6.11", "xstate": "^4.38.3", - "zod": "^4.1.8", - "protobufjs": "^7.2.5" + "zod": "^4.1.8" }, "devDependencies": { "@sentry/webpack-plugin": "^1.20.1", @@ -1245,7 +1246,7 @@ "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "decache": "^4.6.2", - "express": "^4.18.2", + "express": "^4.22.1", "istanbul": "^0.4.5", "js-yaml": "^4.1.1", "jwt-decode": "^3.1.2", diff --git a/workspaces/ballerina/ballerina-low-code-diagram/package.json b/workspaces/ballerina/ballerina-low-code-diagram/package.json index 624635718c1..89be7a03d29 100644 --- a/workspaces/ballerina/ballerina-low-code-diagram/package.json +++ b/workspaces/ballerina/ballerina-low-code-diagram/package.json @@ -21,9 +21,9 @@ "storybook:setup": "node tools/setup-storybook.js" }, "dependencies": { + "@date-io/date-fns": "^3.2.1", "@wso2/ballerina-core": "workspace:*", "@wso2/syntax-tree": "workspace:*", - "@date-io/date-fns": "^3.2.1", "classnames": "^2.5.1", "clipboard-copy": "^4.0.1", "clsx": "^2.1.1", @@ -38,14 +38,14 @@ "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", + "monaco-editor": "0.52.2", "react": "18.2.0", "react-dom": "18.2.0", "react-intl": "^7.1.11", "react-lottie": "^1.2.10", "react-zoom-pan-pinch": "^3.7.0", "uuid": "^11.1.0", - "vscode-languageserver-protocol": "^3.17.5", - "monaco-editor": "0.52.2" + "vscode-languageserver-protocol": "^3.17.5" }, "devDependencies": { "@babel/core": "^7.27.1", @@ -73,6 +73,7 @@ "copy-webpack-plugin": "^13.0.0", "copyfiles": "^2.4.1", "css-loader": "^7.1.2", + "express": "^4.22.1", "file-loader": "^6.2.0", "fork-ts-checker-webpack-plugin": "^9.1.0", "glob": "^11.1.0", @@ -101,8 +102,7 @@ "typescript": "5.8.3", "webpack": "^5.99.8", "webpack-cli": "^6.0.1", - "webpack-dev-server": "^5.2.1", - "express": "^4.21.2" + "webpack-dev-server": "^5.2.1" }, "repository": { "type": "git", From bb361a06a0aa2ff191de45c6505ac33491416de2 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Wed, 3 Dec 2025 17:11:11 +0530 Subject: [PATCH 006/102] Update changelog for version 1.5.4 --- workspaces/ballerina/ballerina-extension/CHANGELOG.md | 7 +++++++ workspaces/bi/bi-extension/CHANGELOG.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/workspaces/ballerina/ballerina-extension/CHANGELOG.md b/workspaces/ballerina/ballerina-extension/CHANGELOG.md index 37c8c539578..9852ab7d7e9 100644 --- a/workspaces/ballerina/ballerina-extension/CHANGELOG.md +++ b/workspaces/ballerina/ballerina-extension/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to the **Ballerina** extension will be documented in this fi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [5.6.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-03 + +### Fixed + +- **Data Mapper** — Fixed the issue with focusing into inner array queries. +- **Security** — Updated dependencies to address security vulnerabilities (`CVE-2024-51999`). + ## [5.6.3](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.2...ballerina-integrator-1.5.3) - 2025-12-01 ### Changed diff --git a/workspaces/bi/bi-extension/CHANGELOG.md b/workspaces/bi/bi-extension/CHANGELOG.md index 415db8bb09b..c6c8a353b9f 100644 --- a/workspaces/bi/bi-extension/CHANGELOG.md +++ b/workspaces/bi/bi-extension/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to the **WSO2 Integrator: BI** extension will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [1.5.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-01 + +### Fixed + +- **Data Mapper** — Fixed the issue with focusing into inner array queries. +- **Security** — Updated dependencies to address security vulnerabilities (`CVE-2024-51999`). + ## [1.5.3](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.2...ballerina-integrator-1.5.3) - 2025-12-01 ### Changed From 1101348850d1c2290c7323006ecd29a0439d9b67 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Fri, 5 Dec 2025 12:37:23 +0530 Subject: [PATCH 007/102] Fix condition to stop focus into query header port --- .../src/components/Diagram/LinkState/CreateLinkState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/Diagram/LinkState/CreateLinkState.ts b/workspaces/ballerina/data-mapper/src/components/Diagram/LinkState/CreateLinkState.ts index 70f8eff1d75..870a6762b4f 100644 --- a/workspaces/ballerina/data-mapper/src/components/Diagram/LinkState/CreateLinkState.ts +++ b/workspaces/ballerina/data-mapper/src/components/Diagram/LinkState/CreateLinkState.ts @@ -135,7 +135,7 @@ export class CreateLinkState extends State { context: (element.getNode() as DataMapperNodeModel).context })); this.link = link; - } else if (!isValueConfig) { + } else if (!isValueConfig && !isQueryHeaderPort(element)) { element.fireEvent({}, "expressionBarFocused"); this.clearState(); this.eject(); From 213baf60f6705f82ca56ee69269f8f7cfa9f5a4b Mon Sep 17 00:00:00 2001 From: ChamodA Date: Fri, 5 Dec 2025 18:26:25 +0530 Subject: [PATCH 008/102] Fix: Await expect assertion for initial array widget output in test scenarios --- .../test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts index f6923e801a3..91318c0dc03 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/data-mapper/DataMapperUtils.ts @@ -404,7 +404,7 @@ export namespace TestScenarios { await dm.waitForProgressEnd(); const locArrInit = dmWebView.getByTestId('array-widget-field-objectOutput.output.oArr1D.IN'); await locArrInit.waitFor(); - expect(locArrInit).toHaveText('[]'); + await expect(locArrInit).toHaveText('[]'); await dm.selectConfigMenuItem('objectOutput.output.oArr1D', 'Add Element'); From 275d66133651363b086e970879817e0c6d77655c Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Fri, 5 Dec 2025 19:08:07 +0530 Subject: [PATCH 009/102] Update changelog dates to match release date --- workspaces/ballerina/ballerina-extension/CHANGELOG.md | 2 +- workspaces/bi/bi-extension/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/CHANGELOG.md b/workspaces/ballerina/ballerina-extension/CHANGELOG.md index 9852ab7d7e9..ad3d4bd2e3b 100644 --- a/workspaces/ballerina/ballerina-extension/CHANGELOG.md +++ b/workspaces/ballerina/ballerina-extension/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the **Ballerina** extension will be documented in this fi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/). -## [5.6.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-03 +## [5.6.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-05 ### Fixed diff --git a/workspaces/bi/bi-extension/CHANGELOG.md b/workspaces/bi/bi-extension/CHANGELOG.md index c6c8a353b9f..19a4c58d509 100644 --- a/workspaces/bi/bi-extension/CHANGELOG.md +++ b/workspaces/bi/bi-extension/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the **WSO2 Integrator: BI** extension will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/). -## [1.5.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-01 +## [1.5.4](https://github.com/wso2/vscode-extensions/compare/ballerina-integrator-1.5.3...ballerina-integrator-1.5.4) - 2025-12-05 ### Fixed From 51e2e2b31fefd6b1290b020254a439c71080415e Mon Sep 17 00:00:00 2001 From: gigara Date: Fri, 5 Dec 2025 20:13:47 +0530 Subject: [PATCH 010/102] Fix jws vulnerability --- common/config/rush/pnpm-config.json | 3 ++- common/config/rush/pnpm-lock.yaml | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/common/config/rush/pnpm-config.json b/common/config/rush/pnpm-config.json index 15ab1176ab2..6017c38f0ce 100644 --- a/common/config/rush/pnpm-config.json +++ b/common/config/rush/pnpm-config.json @@ -89,7 +89,8 @@ * Ppnpmdocumentation: https://pnpm.io/package_json#pnpmoverrides */ "globalOverrides": { - "tar-fs": "3.1.1" + "tar-fs": "3.1.1", + "jws": "3.2.3" // "example1": "^1.0.0", // "example2": "npm:@company/example2@^1.0.0" }, diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 047f012df30..90c78c1f5a9 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -6,6 +6,7 @@ settings: overrides: tar-fs: 3.1.1 + jws: 3.2.3 pnpmfileChecksum: sha256-XTeZQwJtKk4dimqf7175GhJCXrnq3Yh7+kwb86Bwcdo= @@ -16877,8 +16878,8 @@ packages: jwa@1.4.2: resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@3.2.3: + resolution: {integrity: sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==} jwt-decode@4.0.0: resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} @@ -37948,7 +37949,7 @@ snapshots: '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.103.0)': dependencies: webpack: 5.103.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack@5.103.0) + webpack-cli: 5.1.4(webpack-dev-server@5.2.2)(webpack@5.103.0) '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.103.0)': dependencies: @@ -37963,7 +37964,7 @@ snapshots: '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.103.0)': dependencies: webpack: 5.103.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack@5.103.0) + webpack-cli: 5.1.4(webpack-dev-server@5.2.2)(webpack@5.103.0) '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.103.0)': dependencies: @@ -46372,7 +46373,7 @@ snapshots: jsonwebtoken@9.0.2: dependencies: - jws: 3.2.2 + jws: 3.2.3 lodash.includes: 4.3.0 lodash.isboolean: 3.0.3 lodash.isinteger: 4.0.4 @@ -46412,7 +46413,7 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jws@3.2.2: + jws@3.2.3: dependencies: jwa: 1.4.2 safe-buffer: 5.2.1 @@ -54800,7 +54801,7 @@ snapshots: watchpack: 2.4.4 webpack-sources: 3.3.3 optionalDependencies: - webpack-cli: 5.1.4(webpack@5.103.0) + webpack-cli: 5.1.4(webpack-dev-server@5.2.2)(webpack@5.103.0) transitivePeerDependencies: - '@swc/core' - esbuild From a11668b7800a507d836807983c142471a159dff8 Mon Sep 17 00:00:00 2001 From: choreo-cicd Date: Fri, 5 Dec 2025 17:48:27 +0000 Subject: [PATCH 011/102] Update version to ballerina-integrator-1.5.4 --- workspaces/ballerina/ballerina-extension/package.json | 2 +- workspaces/bi/bi-extension/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index c4822db51cf..96d1956daac 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -2,7 +2,7 @@ "name": "ballerina", "displayName": "Ballerina", "description": "Ballerina Language support, debugging, graphical visualization, AI-based data-mapping and many more.", - "version": "5.6.3", + "version": "5.6.4", "publisher": "wso2", "icon": "resources/images/ballerina.png", "homepage": "https://wso2.com/ballerina/vscode/docs", diff --git a/workspaces/bi/bi-extension/package.json b/workspaces/bi/bi-extension/package.json index 7391ab5e194..06bf3b48c1b 100644 --- a/workspaces/bi/bi-extension/package.json +++ b/workspaces/bi/bi-extension/package.json @@ -2,7 +2,7 @@ "name": "ballerina-integrator", "displayName": "WSO2 Integrator: BI", "description": "An extension which gives a development environment for designing, developing, debugging, and testing integration solutions.", - "version": "1.5.3", + "version": "1.5.4", "publisher": "wso2", "icon": "resources/images/wso2-ballerina-integrator-logo.png", "repository": { From 5016f958f7af0754bc14eb7360f33fe7c12501e1 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sun, 7 Dec 2025 20:36:48 +0530 Subject: [PATCH 012/102] Rename DataMapperErrorBoundary to DataMapperErrorBoundaryLegacy --- .../src/components/DataMapper/DataMapperEditor.tsx | 6 +++--- .../{ErrorBoundary => ErrorBoundaryLegacy}/Error/index.tsx | 0 .../{ErrorBoundary => ErrorBoundaryLegacy}/Error/style.ts | 0 .../{ErrorBoundary => ErrorBoundaryLegacy}/index.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename workspaces/ballerina/data-mapper/src/components/DataMapper/{ErrorBoundary => ErrorBoundaryLegacy}/Error/index.tsx (100%) rename workspaces/ballerina/data-mapper/src/components/DataMapper/{ErrorBoundary => ErrorBoundaryLegacy}/Error/style.ts (100%) rename workspaces/ballerina/data-mapper/src/components/DataMapper/{ErrorBoundary => ErrorBoundaryLegacy}/index.tsx (96%) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 74a8ad3cba2..673b4d306c3 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -27,7 +27,7 @@ import DataMapperDiagram from "../Diagram/Diagram"; import { DataMapperHeader } from "./Header/DataMapperHeader"; import { DataMapperNodeModel } from "../Diagram/Node/commons/DataMapperNode"; import { IONodeInitVisitor } from "../../visitors/IONodeInitVisitor"; -import { DataMapperErrorBoundary } from "./ErrorBoundary"; +import { DataMapperErrorBoundaryLegacy } from "./ErrorBoundary"; import { traverseNode } from "../../utils/model-utils"; import { View } from "./Views/DataMapperView"; import { @@ -339,7 +339,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { } return ( - +
{model && ( - + ) } diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx similarity index 100% rename from workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx rename to workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts similarity index 100% rename from workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts rename to workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx similarity index 96% rename from workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx rename to workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx index b12962da2df..069f191048e 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx @@ -55,4 +55,4 @@ export class DataMapperErrorBoundaryC extends React.Component Date: Sun, 7 Dec 2025 21:03:37 +0530 Subject: [PATCH 013/102] Add react-error-boundary yo data-mapper --- common/config/rush/pnpm-lock.yaml | 8 ++++++++ workspaces/ballerina/data-mapper/package.json | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 3bd72e43999..85f20b78066 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -1542,6 +1542,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-error-boundary: + specifier: ~6.0.0 + version: 6.0.0(react@18.2.0) resize-observer-polyfill: specifier: ^1.5.1 version: 1.5.1 @@ -49869,6 +49872,11 @@ snapshots: '@babel/runtime': 7.28.4 react: 18.2.0 + react-error-boundary@6.0.0(react@18.2.0): + dependencies: + '@babel/runtime': 7.28.4 + react: 18.2.0 + react-error-boundary@6.0.0(react@19.1.0): dependencies: '@babel/runtime': 7.28.4 diff --git a/workspaces/ballerina/data-mapper/package.json b/workspaces/ballerina/data-mapper/package.json index 7b544924f33..1a0688c15a4 100644 --- a/workspaces/ballerina/data-mapper/package.json +++ b/workspaces/ballerina/data-mapper/package.json @@ -37,7 +37,8 @@ "zustand": "^5.0.4", "blueimp-md5": "^2.19.0", "mousetrap": "^1.6.5", - "@types/mousetrap": "~1.6.15" + "@types/mousetrap": "~1.6.15", + "react-error-boundary": "~6.0.0" }, "devDependencies": { "@types/lodash.debounce": "^4.0.6", @@ -70,4 +71,4 @@ "publishConfig": { "registry": "https://npm.pkg.github.com/" } -} \ No newline at end of file +} From 5890a8217db9e6d321c07187bd90ddf237ac2f73 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 8 Dec 2025 09:33:07 +0530 Subject: [PATCH 014/102] Refactor error handling in DataMapper: replace ErrorBoundary with DataMapperErrorBoundary and add ErrorScreen component --- .../src/views/DataMapper/index.tsx | 6 +- .../DataMapper/DataMapperEditor.tsx | 2 +- .../DataMapper/ErrorBoundary/Error/index.tsx | 79 +++++++++++++++++++ .../DataMapper/ErrorBoundary/Error/style.ts | 69 ++++++++++++++++ .../DataMapper/ErrorBoundary/index.tsx | 43 ++++++++++ .../ballerina/data-mapper/src/index.tsx | 1 + 6 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx create mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts create mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx index 216c0cb23a7..92247b7959e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx @@ -19,7 +19,7 @@ import React from "react"; import { CodeData, LinePosition } from "@wso2/ballerina-core"; -import { ErrorBoundary } from "@wso2/ui-toolkit"; +import { DataMapperErrorBoundary } from "@wso2/ballerina-data-mapper"; import { TopNavigationBar } from "../../components/TopNavigationBar"; import { DataMapperView } from "./DataMapperView"; @@ -39,9 +39,9 @@ export function DataMapper(props: DataMapperProps) { return ( <> - + - + ); }; diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 673b4d306c3..95bf8407da4 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -27,7 +27,7 @@ import DataMapperDiagram from "../Diagram/Diagram"; import { DataMapperHeader } from "./Header/DataMapperHeader"; import { DataMapperNodeModel } from "../Diagram/Node/commons/DataMapperNode"; import { IONodeInitVisitor } from "../../visitors/IONodeInitVisitor"; -import { DataMapperErrorBoundaryLegacy } from "./ErrorBoundary"; +import { DataMapperErrorBoundaryLegacy } from "./ErrorBoundaryLegacy"; import { traverseNode } from "../../utils/model-utils"; import { View } from "./Views/DataMapperView"; import { diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx new file mode 100644 index 00000000000..c0fcfeb80bc --- /dev/null +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import * as React from "react"; +import { useErrorBoundary } from "react-error-boundary"; + +import { useStyles } from "./style"; +import { Button, Codicon, Icon, Typography } from "@wso2/ui-toolkit"; +import { ISSUES_URL } from "../../../Diagram/utils/constants"; + +interface ErrorScreenProps { + onClose?: () => void; + error?: Error; + resetErrorBoundary?: () => void; +} + +export default function ErrorScreen(props: ErrorScreenProps) { + const classes = useStyles(); + const { resetBoundary } = useErrorBoundary(); + + const handleReset = () => { + resetBoundary(); + }; + + return ( + <> + {props.onClose && ( +
+ +
+ )} +
+
+ + + + + +
+ + A problem occurred while rendering the Data Mapper. + +
+ +
+ + Please raise an issue with the sample code in our issue tracker + +
+ + ); +} diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts new file mode 100644 index 00000000000..1052db14ed2 --- /dev/null +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { css } from "@emotion/css"; + +export const useStyles = () => ({ + root: css({ + position: 'relative', + flexGrow: 1, + margin: '25vh auto', + width: 'fit-content' + }), + errorContainer: css({ + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column" + }), + errorTitle: css({ + color: "var(--vscode-badge-background)", + textAlign: "center" + }), + errorMsg: css({ + paddingTop: "16px", + color: "var(--vscode-checkbox-border)", + textAlign: "center" + }), + closeButtonContainer: css({ + position: 'absolute', + top: '16px', + right: '16px' + }), + errorImg: css({ + display: 'flex', + justifyContent: 'center', + width: '100%' + }), + iconContainer: css({ + display: 'flex', + gap: '8px', + justifyContent: 'center', + marginTop: '16px' + }), + gridContainer: css({ + height: "100%" + }), + link: css({ + color: "var(--vscode-editor-selectionBackground)", + textDecoration: "underline", + "&:hover, &:focus, &:active": { + color: "var(--vscode-editor-selectionBackground)", + textDecoration: "underline", + } + }) +}); diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx new file mode 100644 index 00000000000..aeca500e51a --- /dev/null +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import * as React from "react"; +import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary"; + +import ErrorScreen from "./Error"; + +export interface DataMapperErrorBoundaryProps { + children: React.ReactNode; + onClose?: () => void; +} + +export function DataMapperErrorBoundary(props: DataMapperErrorBoundaryProps) { + const { children, onClose } = props; + + const handleError = (error: Error, errorInfo: React.ErrorInfo) => { + console.error("Error caught by DataMapperErrorBoundary:", error, errorInfo); + }; + + return ( + } + onError={handleError} + > + {children} + + ); +} diff --git a/workspaces/ballerina/data-mapper/src/index.tsx b/workspaces/ballerina/data-mapper/src/index.tsx index 9cae88a622e..3369df80709 100644 --- a/workspaces/ballerina/data-mapper/src/index.tsx +++ b/workspaces/ballerina/data-mapper/src/index.tsx @@ -30,6 +30,7 @@ import { CompletionItem, ErrorBoundary } from "@wso2/ui-toolkit"; import { DataMapperEditor } from "./components/DataMapper/DataMapperEditor"; import { ExpressionProvider } from "./context/ExpressionContext"; import { ISSUES_URL } from "./components/Diagram/utils/constants"; +export { DataMapperErrorBoundary } from "./components/DataMapper/ErrorBoundary"; const queryClient = new QueryClient({ defaultOptions: { From a4d7e9c78c409b22797a000cef638f4fc81f873e Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 8 Dec 2025 13:22:55 +0530 Subject: [PATCH 015/102] Remove internal error boundaries in data mapper --- .../src/views/DataMapper/DataMapperView.tsx | 2 +- .../src/views/DataMapper/index.tsx | 2 +- .../DataMapper/DataMapperEditor.tsx | 93 +++++++++---------- .../DataMapper/ErrorBoundary/Error/index.tsx | 2 +- .../DataMapper/Header/DataMapperHeader.tsx | 2 +- .../ballerina/data-mapper/src/index.tsx | 14 ++- 6 files changed, 55 insertions(+), 60 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index 3fd223d4053..c653b714a6d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -617,7 +617,7 @@ export function DataMapperView(props: DataMapperProps) { } else if (isFileUpdateError) { throw new Error("Error while updating file content"); } - }, [isError]); + }, [isError, isFileUpdateError]); const retrieveCompeletions = useCallback( debounce(async (outputId: string, viewId: string, value: string, cursorPosition?: number) => { diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx index 92247b7959e..32db892a08a 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx @@ -39,7 +39,7 @@ export function DataMapper(props: DataMapperProps) { return ( <> - + diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 95bf8407da4..287fc68b684 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -284,7 +284,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { ]); } catch (error) { console.error("Error generating nodes:", error); - setHasInternalError(true); + throw new Error("Error generating nodes: " + error); } }; @@ -339,53 +339,50 @@ export function DataMapperEditor(props: DataMapperEditorProps) { } return ( - -
- {model && ( - + {model && ( + + )} + {errorKind && } + {nodes.length > 0 && !errorKind && ( + <> + - )} - {errorKind && } - {nodes.length > 0 && !errorKind && ( - <> - - {isSMConfigPanelOpen && ( - - )} - {isQueryClausesPanelOpen && ( - - )} - - )} - -
-
- ) + )} + {isQueryClausesPanelOpen && ( + + )} + + )} +
+ ); } diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index c0fcfeb80bc..080711a7ddf 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -63,7 +63,7 @@ export default function ErrorScreen(props: ErrorScreenProps) { - A problem occurred while rendering the Data Mapper. + This mapping cannot be visualized.
-
- )} -
-
- - - - - -
- - This mapping cannot be visualized. - -
- -
- - Please raise an issue with the sample code in our issue tracker - -
- - ); + goToSource: () => void; } export default function ErrorScreen(props: ErrorScreenProps) { const classes = useStyles(); + const { resetBoundary } = useErrorBoundary(); + const { onClose, goToSource } = props; return ( <> @@ -90,6 +42,16 @@ export default function ErrorScreen(props: ErrorScreenProps) {
+
+ +
+
+ +

This mapping cannot be visualized.

diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx index aeca500e51a..4d69568f163 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx @@ -23,10 +23,11 @@ import ErrorScreen from "./Error"; export interface DataMapperErrorBoundaryProps { children: React.ReactNode; onClose?: () => void; + goToSource: () => void; } export function DataMapperErrorBoundary(props: DataMapperErrorBoundaryProps) { - const { children, onClose } = props; + const { children, onClose, goToSource } = props; const handleError = (error: Error, errorInfo: React.ErrorInfo) => { console.error("Error caught by DataMapperErrorBoundary:", error, errorInfo); @@ -34,7 +35,7 @@ export function DataMapperErrorBoundary(props: DataMapperErrorBoundaryProps) { return ( } + FallbackComponent={(fallbackProps) => } onError={handleError} > {children} diff --git a/workspaces/ballerina/data-mapper/src/index.tsx b/workspaces/ballerina/data-mapper/src/index.tsx index 56ae47e1ccb..7654c42509c 100644 --- a/workspaces/ballerina/data-mapper/src/index.tsx +++ b/workspaces/ballerina/data-mapper/src/index.tsx @@ -88,16 +88,17 @@ export interface DataMapperEditorProps { } export interface DataMapperProps extends DataMapperEditorProps { + goToSource: () => void; expressionBar: ExpressionBarProps; } -export function DataMapper({ expressionBar, ...props }: DataMapperProps) { +export function DataMapper({ goToSource, expressionBar, ...props }: DataMapperProps) { return ( - + - + From 83d8d5abf40e3f685d4596d8d1cad210e6a6c4f0 Mon Sep 17 00:00:00 2001 From: vinukab Date: Tue, 9 Dec 2025 13:28:36 +0530 Subject: [PATCH 020/102] fixed issues in the pack command --- .../src/features/project/cmds/cmd-runner.ts | 8 +++- .../src/features/project/cmds/pack.ts | 43 ++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts index 26edf3ed08b..4adf71b06ee 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/cmd-runner.ts @@ -66,11 +66,14 @@ export const FOCUS_DEBUG_CONSOLE_COMMAND = 'workbench.debug.action.focusRepl'; export enum BALLERINA_COMMANDS { TEST = "test", BUILD = "build", FORMAT = "format", RUN = "run", RUN_WITH_WATCH = "run --watch", DOC = "doc", ADD = "add", OTHER = "other", PACK = "pack", RUN_WITH_EXPERIMENTAL = "run --experimental", - BUILD_WITH_EXPERIMENTAL = "build --experimental", + BUILD_WITH_EXPERIMENTAL = "build --experimental", PACK_WITH_EXPERIMENTAL = "pack --experimental", } export enum PROJECT_TYPE { - SINGLE_FILE = "SINGLE_FILE_PROJECT", BUILD_PROJECT = "BUILD_PROJECT", BALR_PROJECT = "BALR_PROJECT" + SINGLE_FILE = "SINGLE_FILE_PROJECT", + BUILD_PROJECT = "BUILD_PROJECT", + BALR_PROJECT = "BALR_PROJECT", + WORKSPACE = "WORKSPACE_PROJECT" } export enum COMMAND_OPTIONS { @@ -204,3 +207,4 @@ export function getRunCommand(): BALLERINA_COMMANDS { } return BALLERINA_COMMANDS.RUN; } + diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts index 89d55cca0f1..bddc5fa04be 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts @@ -23,12 +23,15 @@ import { } from "../../telemetry"; import { runCommand, BALLERINA_COMMANDS, PROJECT_TYPE, PALETTE_COMMANDS, MESSAGES } from "./cmd-runner"; -import { getCurrentBallerinaProject } +import { getCurrentBallerinaProject, getCurrentProjectRoot } from "../../../utils/project-utils"; import { LANGUAGE } from "../../../core"; +import { StateMachine } from "../../../stateMachine"; +import { MACHINE_VIEW } from "@wso2/ballerina-core"; +import { createVersionNumber, isSupportedSLVersion } from "../../../utils"; export function activatePackCommand() { - // register run project build handler + // register run project pack handler commands.registerCommand(PALETTE_COMMANDS.PACK, async () => { try { sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_PROJECT_PACK, CMP_PROJECT_PACK); @@ -38,11 +41,38 @@ export function activatePackCommand() { return; } - const currentProject = extension.ballerinaExtInstance.getDocumentContext().isActiveDiagram() ? await - getCurrentBallerinaProject(extension.ballerinaExtInstance.getDocumentContext().getLatestDocument()?.toString()) - : await getCurrentBallerinaProject(); + const context = StateMachine.context(); + const { workspacePath, view: webviewType, projectPath } = context; + + let targetPath = projectPath ?? ""; + + if (workspacePath && webviewType === MACHINE_VIEW.WorkspaceOverview) { + targetPath = workspacePath; + } else if (workspacePath && !projectPath) { + try { + targetPath = await getCurrentProjectRoot(); + } catch (error) { + targetPath = workspacePath; + } + } else { + targetPath = await getCurrentProjectRoot(); + } + + if (!targetPath) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + return; + } + + const currentProject = await getCurrentBallerinaProject(targetPath); + + let balCommand = BALLERINA_COMMANDS.PACK + + if (isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { + balCommand = BALLERINA_COMMANDS.PACK_WITH_EXPERIMENTAL; + } + if (currentProject.kind !== PROJECT_TYPE.SINGLE_FILE) { - runCommand(currentProject, extension.ballerinaExtInstance.getBallerinaCmd(), BALLERINA_COMMANDS.PACK, + runCommand(currentProject, extension.ballerinaExtInstance.getBallerinaCmd(), balCommand, currentProject.path!); } else { window.showErrorMessage(MESSAGES.INVALID_PACK); @@ -58,3 +88,4 @@ export function activatePackCommand() { } }); } + From fbf5f49d0a9286a21058cafcd18fda3bbd4370d5 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 9 Dec 2025 14:58:14 +0530 Subject: [PATCH 021/102] Refactor ErrorScreen component: update styling --- .../DataMapper/ErrorBoundary/Error/index.tsx | 31 ++++++++------- .../DataMapper/ErrorBoundary/Error/style.ts | 39 ++++++------------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index cb72cc9a93a..cea571f34fc 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -37,22 +37,27 @@ export default function ErrorScreen(props: ErrorScreenProps) { return ( <>

-
-
-
+
+
+
- -
-
- -
-
+ +
+
+ +
+
+ +
+

This mapping cannot be visualized.

Please raise an issue with the sample code in our issue tracker diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index 9105a55aeaf..587ea175b21 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -29,31 +29,13 @@ export const useStyles = () => ({ margin: '25vh auto', width: 'fit-content' }), - errorContainer: css({ - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column" - }), - errorTitle: css({ - color: "var(--vscode-badge-background)", - textAlign: "center" - }), - errorMsg: css({ - paddingTop: "16px", - color: "var(--vscode-checkbox-border)", - textAlign: "center" - }), + closeButtonContainer: css({ position: 'absolute', top: '16px', right: '16px' }), - errorImg: css({ - display: 'flex', - justifyContent: 'center', - width: '100%' - }), + iconContainer: css({ display: 'flex', gap: '8px', @@ -80,10 +62,9 @@ export const useStyles = () => ({ opacity: 0.5, cursor: 'not-allowed' }), - errorBanner: css({ - borderColor: "var(--vscode-errorForeground)" - }), - errorMessage: css({ + + + errorBody: css({ zIndex: 1, position: 'absolute', top: '50%', @@ -92,7 +73,8 @@ export const useStyles = () => ({ width: '500px', animation: `${fadeIn} 0.5s ease-in-out` }), - warningContainer: css({ + + errorContainer: css({ marginTop: 20, marginLeft: 16, marginRight: 16, @@ -105,15 +87,16 @@ export const useStyles = () => ({ display: 'flex', flexDirection: 'row', height: 'fit-content', + borderColor: "var(--vscode-errorForeground)" }), - warningIcon: css({ + infoIcon: css({ display: 'flex', alignItems: 'center', position: 'absolute', top: '50%', - color: 'var(--vscode-editorWarning-foreground)' + color: 'var(--vscode-editorInfo-foreground)' }), - warningBody: css({ + errorMessage: css({ marginLeft: 35 }) }); From 14beab9c2d8a8b821617331c6f41cb466b9503d1 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 9 Dec 2025 16:12:53 +0530 Subject: [PATCH 022/102] Refactor ErrorScreen and DataMapperErrorBoundary: improve styling and simplify props --- .../DataMapper/ErrorBoundary/Error/index.tsx | 53 +++++---- .../DataMapper/ErrorBoundary/Error/style.ts | 108 +++++++++--------- .../DataMapper/ErrorBoundary/index.tsx | 6 +- 3 files changed, 80 insertions(+), 87 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index cea571f34fc..b5631eaa4b6 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -15,20 +15,27 @@ * specific language governing permissions and limitations * under the License. */ -import * as React from "react"; + +import React from "react"; import { useErrorBoundary } from "react-error-boundary"; import { useStyles } from "./style"; -import { Button, Codicon, Icon, Typography } from "@wso2/ui-toolkit"; +import { Button, Codicon, Icon } from "@wso2/ui-toolkit"; import { ISSUES_URL } from "../../../Diagram/utils/constants"; -import classNames from "classnames"; - interface ErrorScreenProps { - onClose?: () => void; - error?: Error; + onClose: () => void; goToSource: () => void; } +function IconButton(props: { icon: string, onClick: () => void, tooltip?: string }) { + const { icon, onClick, tooltip } = props; + return ( + + ); +} + export default function ErrorScreen(props: ErrorScreenProps) { const classes = useStyles(); const { resetBoundary } = useErrorBoundary(); @@ -37,30 +44,22 @@ export default function ErrorScreen(props: ErrorScreenProps) { return ( <>

-
-
-
- -
-
- -
-
- -
-
- +
+
+
+
+ +
+
+ + + +
-
+

This mapping cannot be visualized.

- Please raise an issue with the sample code in our issue tracker + Please raise an issue with the sample code in our issue tracker.

diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index 587ea175b21..e92a111cad3 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -23,36 +23,8 @@ const fadeIn = keyframes` `; export const useStyles = () => ({ - root: css({ - position: 'relative', - flexGrow: 1, - margin: '25vh auto', - width: 'fit-content' - }), - closeButtonContainer: css({ - position: 'absolute', - top: '16px', - right: '16px' - }), - - iconContainer: css({ - display: 'flex', - gap: '8px', - justifyContent: 'center', - marginTop: '16px' - }), - gridContainer: css({ - height: "100%" - }), - link: css({ - color: "var(--vscode-editor-selectionBackground)", - textDecoration: "underline", - "&:hover, &:focus, &:active": { - color: "var(--vscode-editor-selectionBackground)", - textDecoration: "underline", - } - }), + overlay: css({ zIndex: 1, position: 'absolute', @@ -63,40 +35,62 @@ export const useStyles = () => ({ cursor: 'not-allowed' }), - - errorBody: css({ - zIndex: 1, - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '500px', - animation: `${fadeIn} 0.5s ease-in-out` - }), - errorContainer: css({ - marginTop: 20, - marginLeft: 16, - marginRight: 16, - backgroundColor: 'var(--vscode-editorWidget-background)', - color: 'var(--vscode-sideBarSectionHeader-foreground)', - padding: 10, - minWidth: 120, - width: 'fit-content', - textAlign: 'left', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxWidth: '500px', + animation: `${fadeIn} 0.5s ease-in-out`, + zIndex: 2 + }), + + errorBody: css({ + backgroundColor: 'var(--vscode-editorWidget-background)', + color: 'var(--vscode-sideBarSectionHeader-foreground)', + padding: '16px', + borderColor: "var(--vscode-errorForeground)", + border: '1px solid', + borderRadius: '4px', + display: 'flex', + flexDirection: 'column', + gap: '12px' + }), + + headerContainer: css({ display: 'flex', - flexDirection: 'row', - height: 'fit-content', - borderColor: "var(--vscode-errorForeground)" + alignItems: 'center', + justifyContent: 'space-between', + gap: '12px' }), - infoIcon: css({ + + infoIconContainer: css({ display: 'flex', alignItems: 'center', - position: 'absolute', - top: '50%', color: 'var(--vscode-editorInfo-foreground)' }), + + actionButtons: css({ + display: 'flex', + gap: '4px', + alignItems: 'center' + }), + errorMessage: css({ - marginLeft: 35 - }) + paddingLeft: '8px', + '& p': { + margin: '8px 0' + } + }), + + link: css({ + color: "var(--vscode-editor-selectionBackground)", + textDecoration: "underline", + "&:hover, &:focus, &:active": { + color: "var(--vscode-editor-selectionBackground)", + textDecoration: "underline", + } + }), + }); diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx index 4d69568f163..554932ce9c2 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/index.tsx @@ -15,14 +15,14 @@ * specific language governing permissions and limitations * under the License. */ -import * as React from "react"; +import React from "react"; import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary"; import ErrorScreen from "./Error"; export interface DataMapperErrorBoundaryProps { children: React.ReactNode; - onClose?: () => void; + onClose: () => void; goToSource: () => void; } @@ -35,7 +35,7 @@ export function DataMapperErrorBoundary(props: DataMapperErrorBoundaryProps) { return ( } + FallbackComponent={() => } onError={handleError} > {children} From c2183b041bdb7c71564bdd4cecd34076eaa77e45 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 9 Dec 2025 16:23:27 +0530 Subject: [PATCH 023/102] Refactor DataMapper component: simplify goToSource and onClose handlers --- .../src/views/DataMapper/index.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx index 74dedd4e5ae..df282d8ae05 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx @@ -39,28 +39,22 @@ export interface DataMapperProps { export function DataMapper(props: DataMapperProps) { const {rpcClient} = useRpcContext(); - const goToSource = () => { - // const lineRange = props.codedata.lineRange; - // if (lineRange) { - // const position: NodePosition = { - // startLine: lineRange.startLine?.line, - // startColumn: lineRange.startLine?.offset, - // endLine: lineRange.endLine?.line, - // endColumn: lineRange.endLine?.offset, - // }; - // rpcClient.getCommonRpcClient().goToSource({ position, fileName: lineRange.fileName }); - // } - rpcClient.getCommonRpcClient().executeCommand({ + const goToSource = () => { + rpcClient.getCommonRpcClient()?.executeCommand({ commands: ["ballerina.show.source"] }); }; + const onClose = props.onClose || (() => { + rpcClient.getVisualizerRpcClient()?.goBack(); + }); + return ( <> - - + + ); From ab1071cd9aeb6fd9bfa5055dadd3021c0f6dad4f Mon Sep 17 00:00:00 2001 From: ChamodA Date: Tue, 9 Dec 2025 17:48:38 +0530 Subject: [PATCH 024/102] Refactor DataMapperEditor: remove IOErrorComponent and pass diagram errors to the ErrorBoundary --- .../src/components/DataMapper/DataMapperEditor.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index 287fc68b684..c80071ae209 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -158,8 +158,6 @@ export function DataMapperEditor(props: DataMapperEditorProps) { const [views, dispatch] = useReducer(viewsReducer, initialView); const [nodes, setNodes] = useState([]); - const [errorKind, setErrorKind] = useState(); - const [hasInternalError, setHasInternalError] = useState(false); const { isSMConfigPanelOpen, resetSubMappingConfig } = useDMSubMappingConfigPanelStore( useShallow(state => ({ @@ -284,7 +282,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { ]); } catch (error) { console.error("Error generating nodes:", error); - throw new Error("Error generating nodes: " + error); + throw error; } }; @@ -320,7 +318,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { }; const handleErrors = (kind: ErrorNodeKind) => { - setErrorKind(kind); + throw new Error("Diagram rendering error:" + kind); }; const autoMapWithAI = async () => { @@ -345,7 +343,6 @@ export function DataMapperEditor(props: DataMapperEditorProps) { views={views} reusable={reusable} switchView={switchView} - hasEditDisabled={!!errorKind} onClose={handleOnClose} onBack={handleOnBack} onRefresh={onRefresh} @@ -355,8 +352,7 @@ export function DataMapperEditor(props: DataMapperEditorProps) { undoRedoGroup={undoRedoGroup} /> )} - {errorKind && } - {nodes.length > 0 && !errorKind && ( + {nodes.length > 0 && ( <> Date: Tue, 9 Dec 2025 18:10:40 +0530 Subject: [PATCH 025/102] Remove legacy error boundary components --- .../DataMapper/DataMapperEditor.tsx | 2 - .../ErrorBoundaryLegacy/Error/index.tsx | 64 ------------------- .../ErrorBoundaryLegacy/Error/style.ts | 63 ------------------ .../DataMapper/ErrorBoundaryLegacy/index.tsx | 58 ----------------- 4 files changed, 187 deletions(-) delete mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx delete mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts delete mode 100644 workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx index c80071ae209..0b593838b08 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/DataMapperEditor.tsx @@ -27,7 +27,6 @@ import DataMapperDiagram from "../Diagram/Diagram"; import { DataMapperHeader } from "./Header/DataMapperHeader"; import { DataMapperNodeModel } from "../Diagram/Node/commons/DataMapperNode"; import { IONodeInitVisitor } from "../../visitors/IONodeInitVisitor"; -import { DataMapperErrorBoundaryLegacy } from "./ErrorBoundaryLegacy"; import { traverseNode } from "../../utils/model-utils"; import { View } from "./Views/DataMapperView"; import { @@ -41,7 +40,6 @@ import { import { KeyboardNavigationManager } from "../../utils/keyboard-navigation-manager"; import { DataMapperEditorProps } from "../../index"; import { ErrorNodeKind } from "./Error/RenderingError"; -import { IOErrorComponent } from "./Error/DataMapperError"; import { IntermediateNodeInitVisitor } from "../../visitors/IntermediateNodeInitVisitor"; import { LinkConnectorNode, diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx deleted file mode 100644 index 8e5c49d3edc..00000000000 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import * as React from "react"; - -import { useStyles } from "./style"; -import { Button, Codicon, Typography } from "@wso2/ui-toolkit"; -import { ISSUES_URL } from "../../../Diagram/utils/constants"; - -interface ErrorScreenProps { - onClose: () => void; -}; - -export default function ErrorScreen(props: ErrorScreenProps) { - const classes = useStyles(); - - return ( - <> -
- -
-
-
- - - - - -
- - A problem occurred while rendering the Data Mapper. - - - Please raise an issue with the sample code in our issue tracker - -
- - ); -} diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts deleted file mode 100644 index 2dd91e01f48..00000000000 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/Error/style.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { css } from "@emotion/css"; - -export const useStyles = () => ({ - root: css({ - position: 'relative', - flexGrow: 1, - margin: '25vh auto', - width: 'fit-content' - }), - errorContainer: css({ - display: "flex", - justifyContent: "center", - alignItems: "center", - flexDirection: "column" - }), - errorTitle: css({ - color: "var(--vscode-badge-background)", - textAlign: "center" - }), - errorMsg: css({ - paddingTop: "16px", - color: "var(--vscode-checkbox-border)", - textAlign: "center" - }), - closeButtonContainer: css({ - position: 'absolute', - top: '16px', - right: '16px' - }), - errorImg: css({ - display: 'flex', - justifyContent: 'center', - width: '100%' - }), - gridContainer: css({ - height: "100%" - }), - link: css({ - color: "var(--vscode-editor-selectionBackground)", - textDecoration: "underline", - "&:hover, &:focus, &:active": { - color: "var(--vscode-editor-selectionBackground)", - textDecoration: "underline", - } - }) -}); diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx deleted file mode 100644 index 069f191048e..00000000000 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundaryLegacy/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import * as React from "react"; - -import ErrorScreen from "./Error"; - -export interface DataMapperErrorBoundaryProps { - hasError: boolean; - children?: React.ReactNode; - onClose: () => void; -} - -export class DataMapperErrorBoundaryC extends React.Component { - state = { hasError: false } - - static getDerivedStateFromProps(props: DataMapperErrorBoundaryProps, state: { hasError: boolean }) { - // Only update from props if we're not in an error state - if (!state.hasError) { - return { - hasError: props.hasError - }; - } - return null; // Don't update state if we're in an error state - } - - static getDerivedStateFromError() { - return { hasError: true }; - } - - componentDidCatch(error: any, errorInfo: any) { - // tslint:disable: no-console - console.error(error, errorInfo); - } - - render() { - if (this.state.hasError) { - return ; - } - return this.props?.children; - } -} - -export const DataMapperErrorBoundaryLegacy = DataMapperErrorBoundaryC; From 8b1edf449539873398e4c693b318cf9e88eaf400 Mon Sep 17 00:00:00 2001 From: SGeesan Date: Wed, 10 Dec 2025 12:12:55 +0530 Subject: [PATCH 026/102] Fix add module behaviour --- .../src/features/project/cmds/add.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts index 74f3ed094af..5eb43e2f790 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts @@ -29,8 +29,7 @@ import { } from "../../../utils/project-utils"; import { BallerinaProject, MACHINE_VIEW } from "@wso2/ballerina-core"; import { StateMachine } from "../../../stateMachine"; -import { getBallerinaPackages } from "../../../../src/utils"; -import path from "path"; +import { getBallerinaPackages } from "../../../utils"; function activateAddCommand() { // register ballerina add handler @@ -43,21 +42,28 @@ function activateAddCommand() { return; } - let currentProject:BallerinaProject const context = StateMachine.context(); - const { workspacePath, view: webviewType, projectPath } = context; + const { workspacePath, view: webviewType, projectPath, projectInfo } = context; let targetPath = projectPath ?? ""; if (workspacePath && webviewType === MACHINE_VIEW.WorkspaceOverview) { - const packages = await getBallerinaPackages(Uri.file(workspacePath)); - targetPath = await getPackage(packages); + const packages = projectInfo?.children; + const selection = await getPackage(packages.map((pkg) => pkg.projectPath)); + if (!selection) { + return; + } + targetPath = selection; } else if (workspacePath && !projectPath) { try { targetPath = await getCurrentProjectRoot(); } catch (error) { const packages = await getBallerinaPackages(Uri.file(workspacePath)); - targetPath = await getPackage(packages); + const selection = await getPackage(packages); + if (!selection) { + return; + } + targetPath = selection; } } else { targetPath = await getCurrentProjectRoot(); @@ -68,7 +74,7 @@ function activateAddCommand() { return; } - currentProject = await getCurrentBallerinaProject(targetPath); + const currentProject = await getCurrentBallerinaProject(targetPath); if (currentProject.kind === PROJECT_TYPE.SINGLE_FILE || !currentProject.path) { sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_ERROR_EXECUTE_PROJECT_ADD, CMP_PROJECT_ADD, @@ -97,15 +103,15 @@ function activateAddCommand() { export { activateAddCommand }; // Prompts user to select a package -async function getPackage(packages: string[]): Promise{ +async function getPackage(packages: string[]): Promise { const options: PackageQuickPickItem[] = packages.map((pkg) => { return { - label: path.basename(pkg), + label: pkg, path: pkg }; }); if (options.length === 0) { - return ""; + return undefined; } else if (options.length === 1) { return options[0].path; @@ -113,7 +119,7 @@ async function getPackage(packages: string[]): Promise{ let resultItem = await window.showQuickPick(options, { placeHolder: `Select a Package to add the module to`, }); - return resultItem ? resultItem.path : ""; + return resultItem ? resultItem.path : undefined; } interface PackageQuickPickItem extends QuickPickItem { From 75b74ec15de7ce71b20fa7aba9197d09aa293bdb Mon Sep 17 00:00:00 2001 From: SGeesan Date: Wed, 10 Dec 2025 12:26:27 +0530 Subject: [PATCH 027/102] Fix add module null pointer potential --- .../src/features/project/cmds/add.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts index 5eb43e2f790..44115a0c537 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts @@ -27,9 +27,8 @@ import { getCurrentBallerinaProject, getCurrentProjectRoot } from "../../../utils/project-utils"; -import { BallerinaProject, MACHINE_VIEW } from "@wso2/ballerina-core"; +import { MACHINE_VIEW } from "@wso2/ballerina-core"; import { StateMachine } from "../../../stateMachine"; -import { getBallerinaPackages } from "../../../utils"; function activateAddCommand() { // register ballerina add handler @@ -48,6 +47,10 @@ function activateAddCommand() { let targetPath = projectPath ?? ""; if (workspacePath && webviewType === MACHINE_VIEW.WorkspaceOverview) { const packages = projectInfo?.children; + if (!packages || packages.length === 0) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + return; + } const selection = await getPackage(packages.map((pkg) => pkg.projectPath)); if (!selection) { return; @@ -58,8 +61,12 @@ function activateAddCommand() { try { targetPath = await getCurrentProjectRoot(); } catch (error) { - const packages = await getBallerinaPackages(Uri.file(workspacePath)); - const selection = await getPackage(packages); + const packages = projectInfo?.children; + if (!packages || packages.length === 0) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + return; + } + const selection = await getPackage(packages.map((pkg) => pkg.projectPath)); if (!selection) { return; } From 9a042b6ce1b6a709446ec0561c3a422c0e09cede Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 10 Dec 2025 14:19:31 +0530 Subject: [PATCH 028/102] Integrate persist LS and rpc calls --- .../src/rpc-types/connector-wizard/index.ts | 14 +++++- .../rpc-types/connector-wizard/interfaces.ts | 42 +++++++++++++++++ .../rpc-types/connector-wizard/rpc-type.ts | 4 +- .../src/core/extended-language-client.ts | 16 ++++++- .../connector-wizard/rpc-handler.ts | 8 +++- .../connector-wizard/rpc-manager.ts | 47 ++++++++++++++++++- .../connector-wizard/rpc-client.ts | 18 ++++++- 7 files changed, 142 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts index 3bd574f9f56..f5b2b71779e 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts @@ -16,8 +16,20 @@ * under the License. */ -import { ConnectorRequest, ConnectorResponse, ConnectorsRequest, ConnectorsResponse } from "./interfaces"; +import { + ConnectorRequest, + ConnectorResponse, + ConnectorsRequest, + ConnectorsResponse, + PersistClientGenerateRequest, + IntrospectDatabaseResponse, + PersistClientGenerateResponse, + IntrospectDatabaseRequest +} from "./interfaces"; + export interface ConnectorWizardAPI { getConnector: (params: ConnectorRequest) => Promise; getConnectors: (params: ConnectorsRequest) => Promise; + introspectDatabase: (params: IntrospectDatabaseRequest) => Promise; + persistClientGenerate: (params: PersistClientGenerateRequest) => Promise; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts index 4cd31515a31..dbd2f68be93 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts @@ -17,6 +17,7 @@ */ import { BallerinaConnectorInfo, BallerinaConnectorsRequest, BallerinaConnector } from "../../interfaces/ballerina"; +import { TextEdit } from "../../interfaces/extended-lang-client"; export interface ConnectorRequest { id?: string @@ -41,3 +42,44 @@ export interface ConnectorsResponse { local?: BallerinaConnector[]; error?: string; } + +export interface IntrospectDatabaseRequest { + projectPath: string; + dbSystem: string; + host: string; + port: string; + database: string; + user: string; + password: string; +} + +export interface IntrospectDatabaseResponse { + tables?: string[]; + errorMsg?: string; +} + +export interface PersistClientGenerateRequest { + projectPath: string; + name: string; + dbSystem: string; + host: string; + port: number; + user: string; + password: string; + database: string; + selectedTables: string[]; + module?: string; +} + +export interface PersistClientGenerateResponse { + source?: PersistSource; + errorMsg?: string; + stackTrace?: string; +} + +export interface PersistSource { + isModuleExists?: boolean; + textEditsMap?: { + [key: string]: TextEdit[]; + }; +} diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts index e38504aee03..6ed12bf08e8 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts @@ -17,9 +17,11 @@ * * THIS FILE INCLUDES AUTO GENERATED CODE */ -import { ConnectorRequest, ConnectorResponse, ConnectorsRequest, ConnectorsResponse } from "./interfaces"; +import { ConnectorRequest, ConnectorResponse, ConnectorsRequest, ConnectorsResponse, PersistClientGenerateRequest, IntrospectDatabaseResponse, PersistClientGenerateResponse, IntrospectDatabaseRequest } from "./interfaces"; import { RequestType } from "vscode-messenger-common"; const _preFix = "connector-wizard"; export const getConnector: RequestType = { method: `${_preFix}/getConnector` }; export const getConnectors: RequestType = { method: `${_preFix}/getConnectors` }; +export const introspectDatabase: RequestType = { method: `${_preFix}/introspectDatabase` }; +export const persistClientGenerate: RequestType = { method: `${_preFix}/persistClientGenerate` }; diff --git a/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts b/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts index 816c4f46e56..1220d670d50 100644 --- a/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts +++ b/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts @@ -275,7 +275,11 @@ import { ProjectMigrationResult, FieldPropertyRequest, ClausePositionResponse, - ClausePositionRequest + ClausePositionRequest, + IntrospectDatabaseRequest, + IntrospectDatabaseResponse, + PersistClientGenerateRequest, + PersistClientGenerateResponse } from "@wso2/ballerina-core"; import { BallerinaExtension } from "./index"; import { debug, handlePullModuleProgress } from "../utils"; @@ -459,6 +463,8 @@ enum EXTENDED_APIS { OPEN_API_GENERATE_CLIENT = 'openAPIService/genClient', OPEN_API_GENERATED_MODULES = 'openAPIService/getModules', OPEN_API_CLIENT_DELETE = 'openAPIService/deleteModule', + PERSIST_DATABASE_INTROSPECTION = 'persistService/introspectDatabase', + PERSIST_CLIENT_GENERATE = 'persistService/generatePersistClient', GET_PROJECT_INFO = 'designModelService/projectInfo', GET_ARTIFACTS = 'designModelService/artifacts', PUBLISH_ARTIFACTS = 'designModelService/publishArtifacts', @@ -698,6 +704,14 @@ export class ExtendedLangClient extends LanguageClient implements ExtendedLangCl return this.sendRequest(EXTENDED_APIS.CONNECTOR_CONNECTOR, params); } + async introspectDatabase(params: IntrospectDatabaseRequest): Promise { + return this.sendRequest(EXTENDED_APIS.PERSIST_DATABASE_INTROSPECTION, params); + } + + async generatePersistClient(params: PersistClientGenerateRequest): Promise { + return this.sendRequest(EXTENDED_APIS.PERSIST_CLIENT_GENERATE, params); + } + async getRecord(params: RecordParams): Promise { const isSupported = await this.isExtendedServiceSupported(EXTENDED_APIS.CONNECTOR_RECORD); if (!isSupported) { diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts index 861e30d48b5..537e5c80b86 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts @@ -21,7 +21,11 @@ import { ConnectorRequest, ConnectorsRequest, getConnector, - getConnectors + getConnectors, + introspectDatabase, + IntrospectDatabaseRequest, + persistClientGenerate, + PersistClientGenerateRequest } from "@wso2/ballerina-core"; import { Messenger } from "vscode-messenger"; import { ConnectorWizardRpcManager } from "./rpc-manager"; @@ -30,4 +34,6 @@ export function registerConnectorWizardRpcHandlers(messenger: Messenger) { const rpcManger = new ConnectorWizardRpcManager(); messenger.onRequest(getConnector, (args: ConnectorRequest) => rpcManger.getConnector(args)); messenger.onRequest(getConnectors, (args: ConnectorsRequest) => rpcManger.getConnectors(args)); + messenger.onRequest(introspectDatabase, (args: IntrospectDatabaseRequest) => rpcManger.introspectDatabase(args)); + messenger.onRequest(persistClientGenerate, (args: PersistClientGenerateRequest) => rpcManger.persistClientGenerate(args)); } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts index f35aca856f6..0f9c2de8417 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts @@ -23,9 +23,14 @@ import { ConnectorResponse, ConnectorWizardAPI, ConnectorsRequest, - ConnectorsResponse + ConnectorsResponse, + IntrospectDatabaseRequest, + IntrospectDatabaseResponse, + PersistClientGenerateRequest, + PersistClientGenerateResponse } from "@wso2/ballerina-core"; import { StateMachine } from "../../stateMachine"; +import { updateSourceCode } from "../../utils/source-utils"; export class ConnectorWizardRpcManager implements ConnectorWizardAPI { @@ -62,4 +67,44 @@ export class ConnectorWizardRpcManager implements ConnectorWizardAPI { }); }); } + + async introspectDatabase(params: IntrospectDatabaseRequest): Promise { + return new Promise((resolve) => { + StateMachine.langClient() + .introspectDatabase(params) + .then((response) => { + console.log(">>> introspect database response", response); + resolve(response as IntrospectDatabaseResponse); + }) + .catch((error) => { + console.log(">>> error introspecting database", error); + resolve(undefined); + }); + }); + } + + async persistClientGenerate(params: PersistClientGenerateRequest): Promise { + return new Promise(async (resolve) => { + try { + const response = await StateMachine.langClient().generatePersistClient(params); + console.log(">>> persist client generate response", response); + + const persistResponse = response as PersistClientGenerateResponse; + + // Apply text edits if provided + if (persistResponse?.source?.textEditsMap) { + await updateSourceCode({ + textEdits: persistResponse.source.textEditsMap, + description: `Database Connection and Connector Generation` + }); + console.log(">>> Applied text edits for database connection"); + } + + resolve(persistResponse); + } catch (error) { + console.log(">>> error persisting client", error); + resolve(undefined); + } + }); + } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts index e35fbe3cb55..d29641f06c3 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts @@ -20,11 +20,17 @@ import { ConnectorRequest, ConnectorResponse, - ConnectorWizardAPI, ConnectorsRequest, ConnectorsResponse, + ConnectorWizardAPI, getConnector, - getConnectors + getConnectors, + introspectDatabase, + IntrospectDatabaseRequest, + IntrospectDatabaseResponse, + persistClientGenerate, + PersistClientGenerateRequest, + PersistClientGenerateResponse } from "@wso2/ballerina-core"; import { HOST_EXTENSION } from "vscode-messenger-common"; import { Messenger } from "vscode-messenger-webview"; @@ -43,4 +49,12 @@ export class ConnectorWizardRpcClient implements ConnectorWizardAPI { getConnectors(params: ConnectorsRequest): Promise { return this._messenger.sendRequest(getConnectors, HOST_EXTENSION, params); } + + introspectDatabase(params: IntrospectDatabaseRequest): Promise { + return this._messenger.sendRequest(introspectDatabase, HOST_EXTENSION, params); + } + + persistClientGenerate(params: PersistClientGenerateRequest): Promise { + return this._messenger.sendRequest(persistClientGenerate, HOST_EXTENSION, params); + } } From 74738309b28a21908e8d3adae7d4e842de1dd5ea Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 10 Dec 2025 14:19:51 +0530 Subject: [PATCH 029/102] Allow creating folder path first if file is missing --- .../ballerina-extension/src/utils/source-utils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts index dc3b1ffbc95..46fea53a42e 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts @@ -23,7 +23,8 @@ import { Uri } from 'vscode'; import { ArtifactData, EVENT_TYPE, MACHINE_VIEW, ProjectStructureArtifactResponse, STModification, TextEdit } from '@wso2/ballerina-core'; import { openView, StateMachine, undoRedoManager } from '../stateMachine'; import { ArtifactsUpdated, ArtifactNotificationHandler } from './project-artifacts-handler'; -import { existsSync, writeFileSync } from 'fs'; +import { existsSync, writeFileSync, mkdirSync } from 'fs'; +import * as path from 'path'; import { notifyCurrentWebview } from '../RPCLayer'; import { applyBallerinaTomlEdit } from '../rpc-managers/bi-diagram/utils'; @@ -49,6 +50,11 @@ export async function updateSourceCode(updateSourceCodeRequest: UpdateSourceCode const fileUri = key.startsWith("file:") ? Uri.parse(key) : Uri.file(key); const fileUriString = fileUri.toString(); if (!existsSync(fileUri.fsPath)) { + // Ensure parent directory exists before creating the file + const dirPath = path.dirname(fileUri.fsPath); + if (!existsSync(dirPath)) { + mkdirSync(dirPath, { recursive: true }); + } writeFileSync(fileUri.fsPath, ''); await new Promise(resolve => setTimeout(resolve, 500)); // Add small delay to ensure file is created await StateMachine.langClient().didOpen({ From bd3067699cbb6c49eb8b5eb757d6730702bd5aa0 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Wed, 10 Dec 2025 16:22:25 +0530 Subject: [PATCH 030/102] Refactor ErrorScreen component and improve error handling UI --- .../DataMapper/ErrorBoundary/Error/index.tsx | 29 +++++----- .../DataMapper/ErrorBoundary/Error/style.ts | 56 +++++++++---------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index b5631eaa4b6..f8d60afc17a 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -22,20 +22,12 @@ import { useErrorBoundary } from "react-error-boundary"; import { useStyles } from "./style"; import { Button, Codicon, Icon } from "@wso2/ui-toolkit"; import { ISSUES_URL } from "../../../Diagram/utils/constants"; + interface ErrorScreenProps { onClose: () => void; goToSource: () => void; } -function IconButton(props: { icon: string, onClick: () => void, tooltip?: string }) { - const { icon, onClick, tooltip } = props; - return ( - - ); -} - export default function ErrorScreen(props: ErrorScreenProps) { const classes = useStyles(); const { resetBoundary } = useErrorBoundary(); @@ -51,17 +43,28 @@ export default function ErrorScreen(props: ErrorScreenProps) {
- - - +
-

This mapping cannot be visualized.

+

This mapping cannot be visualized. Please switch to the source view to continue editing.

Please raise an issue with the sample code in our issue tracker.

+
+ + + +
diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index e92a111cad3..e666193368f 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -15,16 +15,11 @@ * specific language governing permissions and limitations * under the License. */ -import { css, keyframes } from "@emotion/css"; -const fadeIn = keyframes` - from { opacity: 0.5; } - to { opacity: 1; } -`; +import { css } from "@emotion/css"; export const useStyles = () => ({ - - + overlay: css({ zIndex: 1, position: 'absolute', @@ -36,29 +31,27 @@ export const useStyles = () => ({ }), errorContainer: css({ - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '90%', - maxWidth: '500px', - animation: `${fadeIn} 0.5s ease-in-out`, - zIndex: 2 - }), - - errorBody: css({ - backgroundColor: 'var(--vscode-editorWidget-background)', - color: 'var(--vscode-sideBarSectionHeader-foreground)', - padding: '16px', - borderColor: "var(--vscode-errorForeground)", - border: '1px solid', - borderRadius: '4px', - display: 'flex', - flexDirection: 'column', - gap: '12px' - }), + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '90%', + maxWidth: '500px', + zIndex: 2 + }), - headerContainer: css({ + errorBody: css({ + backgroundColor: 'var(--vscode-editorWidget-background)', + color: 'var(--vscode-editor-foreground)', + padding: '16px', + border: '1px solid', + borderRadius: '4px', + display: 'flex', + flexDirection: 'column', + gap: '12px' + }), + + headerContainer: css({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', @@ -70,11 +63,12 @@ export const useStyles = () => ({ alignItems: 'center', color: 'var(--vscode-editorInfo-foreground)' }), - + actionButtons: css({ display: 'flex', gap: '4px', - alignItems: 'center' + alignItems: 'center', + alignSelf: 'flex-end' }), errorMessage: css({ From 6e7c843aa6d9ccb578cdf50b33594f0c17804d16 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 10 Dec 2025 17:28:37 +0530 Subject: [PATCH 031/102] Add new connections views --- .../Connection/AddConnectionPopup/index.tsx | 660 ++++++++++++++++++ .../Connection/ConnectionConfigView/index.tsx | 2 +- .../ConnectionConfigurationPopup/index.tsx | 583 ++++++++++++++++ .../DatabaseConnectionPopup/index.tsx | 576 +++++++++++++++ 4 files changed, 1820 insertions(+), 1 deletion(-) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx new file mode 100644 index 00000000000..bb48a87ddf1 --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -0,0 +1,660 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useState } from "react"; +import styled from "@emotion/styled"; +import { AvailableNode, Category, Item, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; +import { useRpcContext } from "@wso2/ballerina-rpc-client"; +import { Button, Codicon, Icon, SearchBox, ThemeColors, Typography, Overlay, ProgressRing } from "@wso2/ui-toolkit"; +import { cloneDeep, debounce } from "lodash"; +import ButtonCard from "../../../../components/ButtonCard"; +import { ConnectorIcon } from "@wso2/bi-diagram"; +import AddConnectionWizard from "../AddConnectionWizard"; +import ConnectionConfigurationPopup from "../ConnectionConfigurationPopup"; +import DatabaseConnectionPopup from "../DatabaseConnectionPopup"; +import { BodyTinyInfo } from "../../../styles"; + +const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 800px; + height: 80%; + max-height: 800px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 20px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +const PopupHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const PopupContent = styled.div` + flex: 1; + overflow-y: auto; + padding: 24px 32px; + display: flex; + flex-direction: column; + gap: 24px; +`; + +const IntroText = styled(Typography)` + font-size: 14px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + line-height: 1.5; + margin: 0; +`; + +const SearchContainer = styled.div` + width: 100%; +`; + +const StyledSearchBox = styled(SearchBox)` + width: 100%; +`; + +const Section = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const SectionTitle = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const CreateConnectorOptions = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const ConnectorOptionCard = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background-color: ${ThemeColors.PRIMARY_CONTAINER}; + border-color: ${ThemeColors.PRIMARY}; + } +`; + +const ConnectorOptionIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_CONTAINER}; + flex-shrink: 0; +`; + +const ConnectorOptionContent = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; +`; + +const ConnectorOptionTitle = styled(Typography)` + font-size: 14px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConnectorOptionDescription = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const ConnectorOptionButtons = styled.div` + display: flex; + gap: 8px; + flex-wrap: wrap; +`; + +const ConnectorTypeButton = styled(Button)` + font-size: 12px; + padding: 4px 12px; + height: auto; +`; + +const ArrowIcon = styled.div` + display: flex; + align-items: center; + color: ${ThemeColors.ON_SURFACE_VARIANT}; +`; + +const SectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +`; + +const FilterButtons = styled.div` + display: flex; + gap: 4px; + align-items: center; +`; + +const FilterButton = styled.button<{ active?: boolean }>` + font-size: 12px; + padding: 6px 12px; + height: 28px; + border-radius: 4px; + border: none; + cursor: pointer; + font-weight: ${(props: { active?: boolean }) => (props.active ? 600 : 400)}; + background-color: ${(props: { active?: boolean }) => + props.active ? ThemeColors.PRIMARY : "transparent"}; + color: ${(props: { active?: boolean }) => + props.active ? ThemeColors.ON_PRIMARY : ThemeColors.ON_SURFACE_VARIANT}; + transition: all 0.2s ease; + + &:hover { + background-color: ${(props: { active?: boolean }) => + props.active ? ThemeColors.PRIMARY : ThemeColors.SURFACE_CONTAINER}; + color: ${(props: { active?: boolean }) => + props.active ? ThemeColors.ON_PRIMARY : ThemeColors.ON_SURFACE}; + } + + // &:focus { + // outline: 1px solid ${ThemeColors.PRIMARY}; + // outline-offset: 2px; + // } +`; + +const ConnectorsGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + margin-top: 8px; +`; + +interface AddConnectionPopupProps { + projectPath: string; + fileName: string; + target?: LinePosition; + onClose?: (parent?: ParentPopupData) => void; +} + +export function AddConnectionPopup(props: AddConnectionPopupProps) { + const { projectPath, fileName, target, onClose } = props; + const { rpcClient } = useRpcContext(); + + const [searchText, setSearchText] = useState(""); + const [connectors, setConnectors] = useState([]); + const [isSearching, setIsSearching] = useState(false); + const [fetchingInfo, setFetchingInfo] = useState(false); + const [filterType, setFilterType] = useState<"All" | "Standard" | "Organization">("All"); + const [wizardStep, setWizardStep] = useState<"database" | "api" | "connector" | null>(null); + const [selectedConnector, setSelectedConnector] = useState(null); + + useEffect(() => { + setIsSearching(true); + fetchConnectors(); + }, []); + + useEffect(() => { + setIsSearching(true); + debouncedSearch(searchText); + return () => debouncedSearch.cancel(); + }, [searchText]); + + useEffect(() => { + if (filterType !== "All") { + setIsSearching(true); + fetchConnectors(); + } + }, [filterType]); + + rpcClient?.onProjectContentUpdated((state: boolean) => { + if (state) { + fetchConnectors(); + } + }); + + const fetchConnectors = (filter?: boolean) => { + setFetchingInfo(true); + const defaultPosition: LinePosition = { line: 0, offset: 0 }; + const position = target || defaultPosition; + rpcClient + .getBIDiagramRpcClient() + .search({ + position: { + startLine: position, + endLine: position, + }, + filePath: fileName, + queryMap: { + limit: 60, + filterByCurrentOrg: filter ?? filterType === "Organization", + }, + searchKind: "CONNECTOR", + }) + .then(async (model) => { + console.log(">>> bi connectors", model); + console.log(">>> bi filtered connectors", model.categories); + setConnectors(model.categories); + }) + .finally(() => { + setIsSearching(false); + setFetchingInfo(false); + }); + }; + + const handleSearch = (text: string) => { + const defaultPosition: LinePosition = { line: 0, offset: 0 }; + const position = target || defaultPosition; + rpcClient + .getBIDiagramRpcClient() + .search({ + position: { + startLine: position, + endLine: position, + }, + filePath: fileName, + queryMap: { + q: text, + limit: 60, + filterByCurrentOrg: filterType === "Organization" ? true : false, + }, + searchKind: "CONNECTOR", + }) + .then(async (model) => { + console.log(">>> bi searched connectors", model); + console.log(">>> bi filtered connectors", model.categories); + setConnectors(model.categories); + }) + .finally(() => { + setIsSearching(false); + }); + }; + + const debouncedSearch = debounce(handleSearch, 1100); + + const handleOnSearch = (text: string) => { + setSearchText(text); + }; + + const handleDatabaseConnection = () => { + // Navigate to database connection wizard + // This would need to be implemented based on your database connection flow + setWizardStep("database"); + }; + + const handleApiSpecConnection = () => { + // Navigate to API spec connection wizard (OpenAPI/WSDL) + setWizardStep("api"); + }; + + const handleSelectConnector = (connector: AvailableNode) => { + if (!connector.codedata) { + console.error(">>> Error selecting connector. No codedata found"); + return; + } + setSelectedConnector(connector); + setWizardStep("connector"); + }; + + const handleBackToConnectorList = () => { + setWizardStep(null); + setSelectedConnector(null); + }; + + const handleCloseWizard = (parent?: ParentPopupData) => { + setWizardStep(null); + setSelectedConnector(null); + if (parent) { + onClose?.(parent); + } + }; + + const filterItems = (items: Item[]): Item[] => { + return items + .map((item) => { + if ("items" in item) { + const filteredItems = filterItems(item.items); + return { + ...item, + items: filteredItems, + }; + } else { + const lowerCaseTitle = item.metadata.label.toLowerCase(); + const lowerCaseDescription = item.metadata.description?.toLowerCase() || ""; + const lowerCaseSearchText = searchText.toLowerCase(); + if ( + lowerCaseTitle.includes(lowerCaseSearchText) || + lowerCaseDescription.includes(lowerCaseSearchText) + ) { + return item; + } + } + }) + .filter(Boolean); + }; + + const filteredCategories = cloneDeep(connectors).map((category) => { + if (!category || !category.items) { + return category; + } + category.items = filterItems(category.items); + return category; + }).filter((category) => { + // Map filterType to category labels similar to ConnectorView + // "Standard" maps to "StandardLibrary" (exclude Local and CurrentOrg) + // "Organization" maps to "CurrentOrg" + if (filterType === "Standard") { + return category.metadata.label !== "Local" && category.metadata.label !== "CurrentOrg"; + } else if (filterType === "Organization") { + return category.metadata.label === "CurrentOrg"; + } + // "All" shows all categories except Local (which is handled separately) + return category.metadata.label !== "Local"; + }); + + const isLoading = isSearching || fetchingInfo; + + // Show configuration form when connector is selected + if (wizardStep === "connector" && selectedConnector) { + return ( + + ); + } + + if (wizardStep === "api") { + return ( + <> + + + + ); + } + + if (wizardStep === "database") { + return ( + + ); + } + + const handleClosePopup = () => { + if (onClose) { + onClose(); + } else { + rpcClient.getVisualizerRpcClient()?.goHome(); + } + + }; + + const openLearnMoreURL = () => { + rpcClient.getCommonRpcClient().openExternalUrl({ + url: 'https://ballerina.io/learn/publish-packages-to-ballerina-central/' + }) + }; + + return ( + <> + + + + Add Connection + handleClosePopup()}> + + + + + + To establish your connection, first define a connector. You may create a custom connector using + an API specification or by introspecting a database. Alternatively, you can select one of the + pre-built connectors below. You will then be guided to provide the required details to complete + the connection setup. + + + + + + +
+ CREATE NEW CONNECTOR + + + + + + + Connect via API Specification + + Import an OpenAPI or WSDL file to create a connector + + + + OpenAPI + + + WSDL + + + + + + + + + + + + + Connect to a Database + + Enter credentials to introspect and discover database tables + + + + MySQL + + + MSSQL + + + PostgreSQL + + + + + + + + +
+ +
+ + PRE-BUILT CONNECTORS + + setFilterType("All")} + > + All + + setFilterType("Standard")} + > + Standard + + setFilterType("Organization")} + > + Organization + + + + {isLoading && ( +
+ +
+ )} + {!isLoading && filteredCategories && filteredCategories.length > 0 && ( + + {filteredCategories.map((category, index) => { + if (!category.items || category.items.length === 0) { + return null; + } + + return ( + + {category.items.map((connector, connectorIndex) => { + const availableNode = connector as AvailableNode; + if (!("codedata" in connector)) { + return null; + } + return ( + + ) : ( + + ) + } + onClick={() => handleSelectConnector(availableNode)} + /> + ); + })} + + ); + })} + + )} + {!isLoading && (!filteredCategories || filteredCategories.length === 0) && ( +
+ {filterType === "Organization" ? ( + <> + + No connectors found in your organization. You can create and publish connectors to Ballerina Central. + + + Learn how to{' '} + { + openLearnMoreURL(); + }} + > + publish packages to Ballerina Central + + + + ) : ( + + No connectors found. + + )} +
+ )} +
+
+
+ + ); +} + +export default AddConnectionPopup; + diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx index 046628e7094..64826d2127a 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx @@ -27,7 +27,7 @@ import { ConnectionKind } from "../../../../components/ConnectionSelector"; import { FormSubmitOptions } from "../../FlowDiagram"; const Container = styled.div` - max-width: 600px; + max-width: 800px; height: calc(100% - 32px); `; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx new file mode 100644 index 00000000000..ebf0414800f --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx @@ -0,0 +1,583 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef, useState } from "react"; +import styled from "@emotion/styled"; +import { + AvailableNode, + Category, + DataMapperDisplayMode, + DIRECTORY_MAP, + FlowNode, + LinePosition, + ParentPopupData, + SubPanel, + SubPanelView, +} from "@wso2/ballerina-core"; +import { useRpcContext } from "@wso2/ballerina-rpc-client"; +import { Button, Codicon, Icon, ThemeColors, Typography, Overlay } from "@wso2/ui-toolkit"; +import { ConnectorIcon } from "@wso2/bi-diagram"; +import ConnectionConfigView from "../ConnectionConfigView"; +import { getFormProperties } from "../../../../utils/bi"; +import { ExpressionFormField } from "@wso2/ballerina-side-panel"; +import { RelativeLoader } from "../../../../components/RelativeLoader"; +import { HelperView } from "../../HelperView"; +import { DownloadIcon } from "../../../../components/DownloadIcon"; +import { FormSubmitOptions } from "../../FlowDiagram"; +import { cloneDeep } from "lodash"; +import { URI, Utils } from "vscode-uri"; + +const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 800px; + height: 80%; + max-height: 800px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 20px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +const ConfigHeader = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const BackButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const ConfigTitleContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConfigSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const ConnectorInfoCard = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + margin: 24px 32px; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + position: relative; +`; + +const ConnectorInfoIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_CONTAINER}; + flex-shrink: 0; + + & > img { + width: 32px; + height: 32px; + object-fit: contain; + } + + & > svg { + width: 32px; + height: 32px; + } +`; + +const StyledCodicon = styled(Codicon)` + font-size: 32px; + width: 32px; + height: 32px; +`; + +const StyledConnectorIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + + & > img { + width: 32px; + height: 32px; + object-fit: contain; + } + + & > svg { + width: 32px; + height: 32px; + } +`; + +const ConnectorInfoContent = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const ConnectorInfoName = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConnectorInfoDescription = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const ConnectorTag = styled.div` + position: absolute; + top: 12px; + right: 12px; + padding: 4px 12px; + border-radius: 12px; + background-color: ${ThemeColors.SURFACE_CONTAINER}; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const TagText = styled(Typography)` + font-size: 11px; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConfigContent = styled.div` + flex: 1; + overflow-y: auto; + padding: 0 32px 24px 32px; +`; + +const ConnectionDetailsSection = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 16px; +`; + +const ConnectionDetailsTitle = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConnectionDetailsSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const StatusContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; + padding: 40px; +`; + +const StatusCard = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + + & > svg { + font-size: 32px; + width: 32px; + height: 32px; + color: ${ThemeColors.ON_SURFACE}; + } +`; + +const StatusText = styled(Typography)` + margin-top: 16px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-size: 14px; + text-align: center; +`; + +enum PullingStatus { + FETCHING = "fetching", + PULLING = "pulling", + SUCCESS = "success", + ERROR = "error", +} + +enum SavingFormStatus { + SAVING = "saving", + SUCCESS = "success", + ERROR = "error", +} + +interface ConnectionConfigurationPopupProps { + selectedConnector: AvailableNode; + fileName: string; + target?: LinePosition; + onClose: (parent?: ParentPopupData) => void; + onBack: () => void; + filteredCategories?: Category[]; +} + +export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopupProps) { + const { selectedConnector, fileName, target, onClose, onBack, filteredCategories = [] } = props; + const { rpcClient } = useRpcContext(); + + const [pullingStatus, setPullingStatus] = useState(undefined); + const [savingFormStatus, setSavingFormStatus] = useState(undefined); + const selectedNodeRef = useRef(); + const [subPanel, setSubPanel] = useState({ view: SubPanelView.UNDEFINED }); + const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); + + useEffect(() => { + // Fetch node template when component mounts + const fetchNodeTemplate = async () => { + if (!selectedConnector.codedata) { + console.error(">>> Error selecting connector. No codedata found"); + return; + } + + try { + let timer: ReturnType | null = null; + let didTimeout = false; + + // Set status to FETCHING before starting + setPullingStatus(PullingStatus.FETCHING); + selectedNodeRef.current = undefined; + + // Start a timer for 3 seconds + const timeoutPromise = new Promise((resolve) => { + timer = setTimeout(() => { + didTimeout = true; + setPullingStatus(PullingStatus.PULLING); + resolve(); + }, 3000); + }); + + // Start the request + const nodeTemplatePromise = rpcClient.getBIDiagramRpcClient().getNodeTemplate({ + position: target || null, + filePath: fileName, + id: selectedConnector.codedata, + }); + + // Wait for either the timer or the request to finish + const response = await Promise.race([ + nodeTemplatePromise.then((res) => { + if (timer) { + clearTimeout(timer); + timer = null; + } + return res; + }), + timeoutPromise.then(() => nodeTemplatePromise), + ]); + + if (didTimeout) { + // If it timed out, set status to SUCCESS + setPullingStatus(PullingStatus.SUCCESS); + } + + console.log(">>> FlowNode template", response); + selectedNodeRef.current = response.flowNode; + const formProperties = getFormProperties(response.flowNode); + console.log(">>> Form properties", formProperties); + + if (Object.keys(formProperties).length === 0) { + // add node to source code + handleOnFormSubmit(response.flowNode); + return; + } + } catch (error) { + console.error(">>> Error selecting connector", error); + setPullingStatus(PullingStatus.ERROR); + } finally { + // After few seconds, set status to undefined + setTimeout(() => { + setPullingStatus(undefined); + }, 2000); + } + }; + + fetchNodeTemplate(); + }, [selectedConnector, fileName, target, rpcClient]); + + const handleOnFormSubmit = async (node: FlowNode, _dataMapperMode?: DataMapperDisplayMode, options?: FormSubmitOptions) => { + console.log(">>> on form submit", node); + if (selectedNodeRef.current) { + setSavingFormStatus(SavingFormStatus.SAVING); + const visualizerLocation = await rpcClient.getVisualizerLocation(); + let connectionsFilePath = visualizerLocation.documentUri || visualizerLocation.projectPath; + + if (node.codedata.isGenerated && !connectionsFilePath.endsWith(".bal")) { + connectionsFilePath = Utils.joinPath(URI.file(connectionsFilePath), "main.bal").fsPath; + } + + if (connectionsFilePath === "") { + console.error(">>> Error updating source code. No source file found"); + setSavingFormStatus(SavingFormStatus.ERROR); + return; + } + + // node property scope is local. then use local file path and line position + if ((node.properties?.scope?.value as string)?.toLowerCase() === "local") { + node.codedata.lineRange = { + fileName: visualizerLocation.documentUri, + startLine: target, + endLine: target, + }; + } + + // Check if the node is a connector + const isConnector = node.codedata.node === "NEW_CONNECTION"; + + rpcClient + .getBIDiagramRpcClient() + .getSourceCode({ + filePath: connectionsFilePath, + flowNode: node, + isConnector: isConnector, + }) + .then((response) => { + console.log(">>> Updated source code", response); + if (!isConnector) { + selectedNodeRef.current = undefined; + if (options?.postUpdateCallBack) { + options.postUpdateCallBack(); + } + return; + } + if (response.artifacts.length > 0) { + // clear memory + selectedNodeRef.current = undefined; + setSavingFormStatus(SavingFormStatus.SUCCESS); + const newConnection = response.artifacts.find((artifact) => artifact.isNew); + onClose({ recentIdentifier: newConnection.name, artifactType: DIRECTORY_MAP.CONNECTION }); + } else { + console.error(">>> Error updating source code", response); + setSavingFormStatus(SavingFormStatus.ERROR); + } + }); + } + }; + + const handleSubPanel = (subPanel: SubPanel) => { + setSubPanel(subPanel); + }; + + const updateExpressionField = (data: ExpressionFormField) => { + setUpdatedExpressionField(data); + }; + + const handleResetUpdatedExpressionField = () => { + setUpdatedExpressionField(undefined); + }; + + const findSubPanelComponent = (subPanel: SubPanel) => { + switch (subPanel.view) { + case SubPanelView.HELPER_PANEL: + return ( + + ); + default: + return null; + } + }; + + const getConnectorTag = () => { + if (selectedConnector.codedata?.org === "ballerinax") { + return "Standard"; + } + const isStandard = filteredCategories.some( + (cat) => + cat.metadata.label !== "CurrentOrg" && + cat.items?.some((item) => (item as AvailableNode).codedata?.id === selectedConnector.codedata?.id) + ); + return isStandard ? "Standard" : "Organization"; + }; + + return ( + <> + + + + + + + + Configure {selectedConnector.metadata.label} + + Configure connection settings for this connector + + + onClose()}> + + + + + + + {selectedConnector.metadata.icon ? ( + + + + ) : ( + + )} + + + {selectedConnector.metadata.label} + + {selectedConnector.metadata.description || ""} + + + + {getConnectorTag()} + + + + + {pullingStatus && ( + + {pullingStatus === PullingStatus.FETCHING && ( + + )} + {pullingStatus === PullingStatus.PULLING && ( + + + + Please wait while the connector is being pulled. + + + )} + {pullingStatus === PullingStatus.SUCCESS && ( + + + Connector pulled successfully. + + )} + {pullingStatus === PullingStatus.ERROR && ( + + + + Failed to pull the connector. Please try again. + + + )} + + )} + {!pullingStatus && selectedNodeRef.current && (() => { + // Remove description property from node before passing to form + // since it's already shown in the connector info card + const nodeWithoutDescription = cloneDeep(selectedNodeRef.current); + if (nodeWithoutDescription.metadata?.description) { + delete nodeWithoutDescription.metadata.description; + } + return ( + <> + + Connection Details + + Configure your connection settings + + + + + ); + })()} + + + + ); +} + +export default ConnectionConfigurationPopup; + diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx new file mode 100644 index 00000000000..3d1f461947f --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -0,0 +1,576 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState } from "react"; +import styled from "@emotion/styled"; +import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps } from "@wso2/ui-toolkit"; +import { Stepper } from "@wso2/ui-toolkit"; +import { LinePosition, ParentPopupData } from "@wso2/ballerina-core"; +import { useRpcContext } from "@wso2/ballerina-rpc-client"; + +const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 800px; + height: 80%; + max-height: 800px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 20px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +const PopupHeader = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const BackButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const HeaderTitleContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const PopupSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const StepperContainer = styled.div` + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const ContentContainer = styled.div` + flex: 1; + overflow-y: auto; + padding: 24px 32px; +`; + +const StepContent = styled.div` + display: flex; + flex-direction: column; + gap: 24px; +`; + +const SectionTitle = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const SectionSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const FormSection = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const FormField = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const ActionButton = styled(Button)` + width: 100%; + margin-top: 24px; +`; + +const TablesGrid = styled.div` + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + margin-top: 16px; +`; + +const TableCard = styled.div<{ selected?: boolean }>` + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border: 1px solid ${(props: { selected?: boolean }) => (props.selected ? ThemeColors.PRIMARY : ThemeColors.OUTLINE_VARIANT)}; + border-radius: 8px; + background-color: ${(props: { selected?: boolean }) => (props.selected ? ThemeColors.PRIMARY_CONTAINER : ThemeColors.SURFACE_DIM)}; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + border-color: ${ThemeColors.PRIMARY}; + background-color: ${ThemeColors.PRIMARY_CONTAINER}; + } +`; + +const TableCheckbox = styled.input` + width: 18px; + height: 18px; + cursor: pointer; +`; + +const TableName = styled(Typography)` + font-size: 14px; + font-weight: 500; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const SelectAllButton = styled(Button)` + margin-top: 16px; + align-self: flex-start; +`; + +const SelectionInfo = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +`; + +const ActionButtonsContainer = styled.div` + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 24px; +`; + +interface DatabaseConnectionPopupProps { + fileName: string; + target?: LinePosition; + onClose?: (data?: ParentPopupData) => void; + onBack?: () => void; +} + +type DatabaseType = "PostgreSQL" | "MySQL" | "MSSQL"; + +interface DatabaseCredentials { + databaseType: DatabaseType; + host: string; + port: string; + databaseName: string; + username: string; + password: string; +} + +interface DatabaseTable { + name: string; + selected: boolean; +} + +const DATABASE_TYPES: OptionProps[] = [ + { id: "postgresql", value: "PostgreSQL", content: "PostgreSQL" }, + { id: "mysql", value: "MySQL", content: "MySQL" }, + { id: "mssql", value: "MSSQL", content: "MSSQL" }, +]; + +const DEFAULT_PORTS: Record = { + PostgreSQL: "5432", + MySQL: "3306", + MSSQL: "1433", +}; + +export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { + const { fileName, target, onClose, onBack } = props; + const { rpcClient } = useRpcContext(); + + const [currentStep, setCurrentStep] = useState(0); + const [credentials, setCredentials] = useState({ + databaseType: "PostgreSQL", + host: "", + port: "5432", + databaseName: "mydb", + username: "", + password: "", + }); + const [tables, setTables] = useState([]); + const [isIntrospecting, setIsIntrospecting] = useState(false); + const [connectionName, setConnectionName] = useState(""); + const [isSaving, setIsSaving] = useState(false); + + const steps = ["Introspect Database", "Select Tables", "Create Connection"]; + + const handleDatabaseTypeChange = (value: string) => { + const dbType = value as DatabaseType; + setCredentials({ + ...credentials, + databaseType: dbType, + port: DEFAULT_PORTS[dbType], + }); + }; + + const handleCredentialsChange = (field: keyof DatabaseCredentials, value: string) => { + setCredentials({ + ...credentials, + [field]: value, + }); + }; + + const handleIntrospect = async () => { + setIsIntrospecting(true); + try { + // Map database type to dbSystem format expected by RPC + const dbSystemMap: Record = { + PostgreSQL: "postgresql", + MySQL: "mysql", + MSSQL: "mssql", + }; + + const visualizerLocation = await rpcClient.getVisualizerLocation(); + const projectPath = visualizerLocation.projectPath; + + const response = await rpcClient.getConnectorWizardRpcClient().introspectDatabase({ + projectPath: projectPath, + dbSystem: dbSystemMap[credentials.databaseType], + host: credentials.host, + port: credentials.port, + database: credentials.databaseName, + user: credentials.username, + password: credentials.password, + }); + + if (response.errorMsg) { + console.error(">>> Error introspecting database", response.errorMsg); + // TODO: Show error message to user + return; + } + + if (response.tables && response.tables.length > 0) { + const databaseTables: DatabaseTable[] = response.tables.map((tableName) => ({ + name: tableName, + selected: false, + })); + setTables(databaseTables); + setCurrentStep(1); + } else { + console.warn(">>> No tables found in database"); + // TODO: Show message to user that no tables were found + } + } catch (error) { + console.error(">>> Error introspecting database", error); + // TODO: Show error message to user + } finally { + setIsIntrospecting(false); + } + }; + + const handleTableToggle = (index: number) => { + const updatedTables = [...tables]; + updatedTables[index].selected = !updatedTables[index].selected; + setTables(updatedTables); + }; + + const handleSelectAll = () => { + const allSelected = tables.every((table) => table.selected); + setTables(tables.map((table) => ({ ...table, selected: !allSelected }))); + }; + + const handleContinueToConnectionDetails = () => { + setCurrentStep(2); + }; + + const handleSaveConnection = async () => { + setIsSaving(true); + try { + // Get project path + const visualizerLocation = await rpcClient.getVisualizerLocation(); + const projectPath = visualizerLocation.projectPath; + + // Map database type to dbSystem format expected by RPC + const dbSystemMap: Record = { + PostgreSQL: "postgresql", + MySQL: "mysql", + MSSQL: "mssql", + }; + + // Get selected tables - if all tables are selected, use ["*"] + const selectedTableNames = tables.filter((t) => t.selected).map((t) => t.name); + const allTablesSelected = tables.length > 0 && selectedTableNames.length === tables.length; + const selectedTables = allTablesSelected ? ["*"] : selectedTableNames; + + const response = await rpcClient.getConnectorWizardRpcClient().persistClientGenerate({ + projectPath: projectPath, + name: connectionName, + dbSystem: dbSystemMap[credentials.databaseType], + host: credentials.host, + port: parseInt(credentials.port, 10), + user: credentials.username, + password: credentials.password, + database: credentials.databaseName, + selectedTables: selectedTables, + }); + + if (response.errorMsg) { + console.error(">>> Error saving connection", response.errorMsg); + if (response.stackTrace) { + console.error(">>> Stack trace", response.stackTrace); + } + // TODO: Show error message to user + return; + } + + // Log success and text edits info if available + if (response.source?.textEditsMap) { + console.log(">>> Connection created successfully with text edits", Object.keys(response.source.textEditsMap)); + } + if (response.source?.isModuleExists !== undefined) { + console.log(">>> Module exists:", response.source.isModuleExists); + } + + + onClose?.(); + } catch (error) { + console.error(">>> Error saving connection", error); + // TODO: Show error message to user + } finally { + setIsSaving(false); + } + }; + + const selectedTablesCount = tables.filter((t) => t.selected).length; + const totalTablesCount = tables.length; + + const renderStepContent = () => { + switch (currentStep) { + case 0: + return ( + +
+ Database Credentials + + Enter credentials to connect and introspect the database + +
+ + + + + + handleCredentialsChange("host", value)} + /> + + + handleCredentialsChange("port", value)} + /> + + + handleCredentialsChange("databaseName", value)} + /> + + + handleCredentialsChange("username", value)} + /> + + + handleCredentialsChange("password", value)} + /> + + + {isIntrospecting ? "Connecting..." : "Connect & Introspect Database"} + + +
+ ); + + case 1: + return ( + + +
+ Select Tables + + Choose which tables to include in this connector + +
+ + {selectedTablesCount} of {totalTablesCount} selected + +
+ + {tables.map((table, index) => ( + handleTableToggle(index)} + > + handleTableToggle(index)} + /> + {table.name} + + ))} + + + Select All + + + + + +
+ ); + + case 2: + return ( + +
+ Connection Details + + Name your connection to complete the setup + +
+ + + + + This name will be used to reference this connection in your code + + + + + + + +
+ ); + + default: + return null; + } + }; + + return ( + <> + + + + + + + + Connect to a Database + + Enter database credentials to introspect and discover available tables + + + onClose?.()}> + + + + + + + {renderStepContent()} + + + ); +} + +export default DatabaseConnectionPopup; + From 561d5e0fffaf4e4b99072b1e43d4a9750f67d706 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 11 Dec 2025 10:55:42 +0530 Subject: [PATCH 032/102] Fix color variable in errorBody style --- .../src/components/DataMapper/ErrorBoundary/Error/style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index e666193368f..31ba82a13c7 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -42,7 +42,7 @@ export const useStyles = () => ({ errorBody: css({ backgroundColor: 'var(--vscode-editorWidget-background)', - color: 'var(--vscode-editor-foreground)', + color: 'var(--vscode-foreground)', padding: '16px', border: '1px solid', borderRadius: '4px', From 1828bf9a1baafa989de0a9a08031a6b97de24f11 Mon Sep 17 00:00:00 2001 From: SGeesan Date: Thu, 11 Dec 2025 11:09:53 +0530 Subject: [PATCH 033/102] refactor add module --- .../src/features/project/cmds/add.ts | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts index 44115a0c537..4a0608ef776 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/add.ts @@ -18,7 +18,7 @@ import { LANGUAGE } from "../../../core"; import { extension } from "../../../BalExtensionContext"; -import { commands, QuickPickItem, Uri, window } from "vscode"; +import { commands, QuickPickItem, window } from "vscode"; import { TM_EVENT_PROJECT_ADD, TM_EVENT_ERROR_EXECUTE_PROJECT_ADD, CMP_PROJECT_ADD, sendTelemetryEvent, sendTelemetryException, getMessageObject } from "../../telemetry"; @@ -27,7 +27,7 @@ import { getCurrentBallerinaProject, getCurrentProjectRoot } from "../../../utils/project-utils"; -import { MACHINE_VIEW } from "@wso2/ballerina-core"; +import { MACHINE_VIEW, ProjectInfo } from "@wso2/ballerina-core"; import { StateMachine } from "../../../stateMachine"; function activateAddCommand() { @@ -46,12 +46,7 @@ function activateAddCommand() { let targetPath = projectPath ?? ""; if (workspacePath && webviewType === MACHINE_VIEW.WorkspaceOverview) { - const packages = projectInfo?.children; - if (!packages || packages.length === 0) { - window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - return; - } - const selection = await getPackage(packages.map((pkg) => pkg.projectPath)); + const selection = await getPackage(projectInfo); if (!selection) { return; } @@ -60,13 +55,8 @@ function activateAddCommand() { } else if (workspacePath && !projectPath) { try { targetPath = await getCurrentProjectRoot(); - } catch (error) { - const packages = projectInfo?.children; - if (!packages || packages.length === 0) { - window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - return; - } - const selection = await getPackage(packages.map((pkg) => pkg.projectPath)); + } catch (error){ + const selection = await getPackage(projectInfo); if (!selection) { return; } @@ -110,26 +100,18 @@ function activateAddCommand() { export { activateAddCommand }; // Prompts user to select a package -async function getPackage(packages: string[]): Promise { - const options: PackageQuickPickItem[] = packages.map((pkg) => { - return { - label: pkg, - path: pkg - }; - }); - if (options.length === 0) { +async function getPackage(projectInfo: ProjectInfo): Promise { + const packages = projectInfo?.children; + if (!packages || packages.length === 0) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); return undefined; } - else if (options.length === 1) { - return options[0].path; + else if (packages.length === 1) { + return packages[0].projectPath; } - let resultItem = await window.showQuickPick(options, { + const packagePaths = packages.map((pkg) => pkg.projectPath); + const resultItem = await window.showQuickPick(packagePaths.map(path => ({ label: path })), { placeHolder: `Select a Package to add the module to`, }); - return resultItem ? resultItem.path : undefined; -} - -interface PackageQuickPickItem extends QuickPickItem { - label: string, - path: string + return resultItem ? resultItem.label : undefined; } \ No newline at end of file From 23878b21605725f061cddb2590af012bd2cf2db1 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 11 Dec 2025 11:13:26 +0530 Subject: [PATCH 034/102] Import ThemeColors and update Data Mapper errorBody border style --- .../src/components/DataMapper/ErrorBoundary/Error/style.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index 31ba82a13c7..51dc48d84d5 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -17,6 +17,7 @@ */ import { css } from "@emotion/css"; +import { ThemeColors } from "@wso2/ui-toolkit"; export const useStyles = () => ({ @@ -44,7 +45,7 @@ export const useStyles = () => ({ backgroundColor: 'var(--vscode-editorWidget-background)', color: 'var(--vscode-foreground)', padding: '16px', - border: '1px solid', + border: `1px solid ${ThemeColors.OUTLINE_VARIANT}`, borderRadius: '4px', display: 'flex', flexDirection: 'column', From 3deb95f23b433137d05147efb5bbce9aec1f4408 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Thu, 11 Dec 2025 11:20:46 +0530 Subject: [PATCH 035/102] Remove unused data-mapper.spec.ts test file in other artifacts --- .../other-artifacts/data-mapper.spec.ts | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/other-artifacts/data-mapper.spec.ts diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/other-artifacts/data-mapper.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/other-artifacts/data-mapper.spec.ts deleted file mode 100644 index 9ddeca96fd9..00000000000 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/other-artifacts/data-mapper.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ - -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { test } from '@playwright/test'; -import { addArtifact, initTest, page } from '../utils/helpers'; -import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; -import { ProjectExplorer } from '../utils/pages'; - -export default function createTests() { - test.describe('Data Mapper Artifact Tests', { - tag: '@group1', - }, async () => { - let functionName = ''; - initTest(); - test('Create Data Mapper Artifact', async ({ }, testInfo) => { - const testAttempt = testInfo.retry + 1; - console.log('Creating a new data mapper in test attempt: ', testAttempt); - // Creating a HTTP Service - await addArtifact('Data Mapper Artifact', 'data-mapper'); - const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); - if (!artifactWebView) { - throw new Error('WSO2 Integrator: BI webview not found'); - } - functionName = `sample${testAttempt}`; - const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); - await form.switchToFormView(false, artifactWebView); - await form.fill({ - values: { - 'Data Mapper Name*Name of the data mapper': { - type: 'input', - value: functionName, - } - } - }); - await form.submit('Create'); - const context = artifactWebView.locator(`text=${functionName}`); - await context.waitFor(); - const projectExplorer = new ProjectExplorer(page.page); - await projectExplorer.findItem(['sample', `${functionName}`], true); - const updateArtifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); - if (!updateArtifactWebView) { - throw new Error('WSO2 Integrator: BI webview not found'); - } - }); - - test('Editing Data Mapper Artifact', async ({ }, testInfo) => { - const testAttempt = testInfo.retry + 1; - console.log('Editing a data mapper in test attempt: ', testAttempt); - const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); - if (!artifactWebView) { - throw new Error('WSO2 Integrator: BI webview not found'); - } - const editBtn = artifactWebView.locator('#bi-edit'); - await editBtn.waitFor(); - await editBtn.click({ force: true }); - const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); - await form.switchToFormView(false, artifactWebView); - await form.fill({ - values: { - 'Return Type': { - type: 'textarea', - value: 'string', - additionalProps: { clickLabel: true } - } - } - }); - await form.submit('Save'); - const context = artifactWebView.locator(`text=${functionName}`); - await context.waitFor(); - const contextReturnType = artifactWebView.locator('span:has(i.fw-bi-return)', { hasText: 'string' }); - await contextReturnType.waitFor(); - }); - }); -} From a53209da99f2c3d426ba9894dda1087a9ea6e6c7 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:20:09 +0530 Subject: [PATCH 036/102] Fix issues in the MI Extension --- workspaces/mi/mi-extension/package.json | 11 ++++++ .../mi/mi-extension/src/constants/index.ts | 1 + .../mi/mi-extension/src/debugger/activate.ts | 39 +++++++++++++++++-- .../mi-extension/src/debugger/debugAdapter.ts | 4 +- .../mi-extension/src/debugger/debugHelper.ts | 10 +++-- .../mi/mi-extension/src/debugger/debugger.ts | 2 +- .../mi-extension/src/util/onboardingUtils.ts | 2 +- .../mi-visualizer/src/views/Diagram/Proxy.tsx | 12 +++--- 8 files changed, 66 insertions(+), 15 deletions(-) diff --git a/workspaces/mi/mi-extension/package.json b/workspaces/mi/mi-extension/package.json index b69c3b201f8..af10ee02abc 100644 --- a/workspaces/mi/mi-extension/package.json +++ b/workspaces/mi/mi-extension/package.json @@ -163,6 +163,12 @@ "default": true, "description": "Update car plugin version automatically" }, + "MI.serverTimeoutInSecs": { + "type": "number", + "default": 120, + "minimum": 30, + "description": "Maximum wait time for server startup in seconds" + }, "MI.logging.loggingLevel": { "type": "string", "enum": [ @@ -503,6 +509,11 @@ "icon": "$(play)", "category": "MI" }, + { + "command": "MI.terminate-server", + "title": "Stop MI Server Session", + "category": "MI" + }, { "command": "MI.build-bal-module", "title": "Build Ballerina Module", diff --git a/workspaces/mi/mi-extension/src/constants/index.ts b/workspaces/mi/mi-extension/src/constants/index.ts index fafa6f1760a..69ecaa28b0c 100644 --- a/workspaces/mi/mi-extension/src/constants/index.ts +++ b/workspaces/mi/mi-extension/src/constants/index.ts @@ -91,6 +91,7 @@ export const COMMANDS = { REMOTE_DEPLOY_PROJECT: 'MI.remote-deploy-project', CREATE_DOCKER_IMAGE: 'MI.create-docker-image', BUILD_AND_RUN_PROJECT: 'MI.build-and-run', + TERMINATE_SERVER: 'MI.terminate-server', BUILD_BAL_MODULE: 'MI.build-bal-module', ADD_DATA_SOURCE_COMMAND: 'MI.project-explorer.add-data-source', SHOW_DATA_SOURCE: 'MI.show.data-source', diff --git a/workspaces/mi/mi-extension/src/debugger/activate.ts b/workspaces/mi/mi-extension/src/debugger/activate.ts index eb2d774dea3..eacbee0cb5c 100644 --- a/workspaces/mi/mi-extension/src/debugger/activate.ts +++ b/workspaces/mi/mi-extension/src/debugger/activate.ts @@ -21,7 +21,7 @@ import { CancellationToken, DebugConfiguration, ProviderResult, Uri, window, wor import { MiDebugAdapter } from './debugAdapter'; import { COMMANDS } from '../constants'; import { extension } from '../MIExtensionContext'; -import {executeBuildTask, executeRemoteDeployTask, getServerPath} from './debugHelper'; +import {executeBuildTask, executeRemoteDeployTask, getServerPath, stopServer} from './debugHelper'; import { getDockerTask } from './tasks'; import { getStateMachine, refreshUI } from '../stateMachine'; import * as fs from 'fs'; @@ -222,9 +222,14 @@ export function activateDebugger(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand(COMMANDS.BUILD_AND_RUN_PROJECT, async (args: any) => { const webview = [...webviews.values()].find(webview => webview.getWebview()?.active); - + let projectUri: string | undefined = undefined; if (webview && webview?.getProjectUri()) { - const projectUri = webview.getProjectUri(); + projectUri = webview.getProjectUri(); + } + if (!projectUri) { + projectUri = await askForProject(); + } + if (projectUri) { const projectWorkspace = workspace.getWorkspaceFolder(Uri.file(projectUri)); const launchJsonPath = path.join(projectUri, '.vscode', 'launch.json'); const envPath = path.join(projectUri, '.env'); @@ -279,6 +284,34 @@ export function activateDebugger(context: vscode.ExtensionContext) { } })); + context.subscriptions.push(vscode.commands.registerCommand(COMMANDS.TERMINATE_SERVER, async () => { + + const webview = [...webviews.values()].find(webview => webview.getWebview()?.active); + let projectUri: string | undefined = undefined; + if (webview && webview?.getProjectUri()) { + projectUri = webview.getProjectUri(); + } + if (!projectUri) { + projectUri = await askForProject(); + } + if (projectUri) { + try { + getServerPath(projectUri).then(async (serverPath) => { + if (!serverPath) { + vscode.window.showErrorMessage("Server path not found to terminate the server."); + return; + } + await stopServer(projectUri, serverPath, process.platform === 'win32'); + vscode.commands.executeCommand('setContext', 'MI.isRunning', 'false'); + }); + } catch(error) { + vscode.window.showErrorMessage('Error occurred while terminating the server.'); + } + } else { + vscode.window.showErrorMessage('Error occurred while determining the project to terminate the server.'); + } + })); + context.subscriptions.push(vscode.commands.registerCommand(COMMANDS.BUILD_BAL_MODULE, async () => { const editor = vscode.window.activeTextEditor; if (!editor) { diff --git a/workspaces/mi/mi-extension/src/debugger/debugAdapter.ts b/workspaces/mi/mi-extension/src/debugger/debugAdapter.ts index a492451563a..ea827ca30f2 100644 --- a/workspaces/mi/mi-extension/src/debugger/debugAdapter.ts +++ b/workspaces/mi/mi-extension/src/debugger/debugAdapter.ts @@ -202,7 +202,7 @@ export class MiDebugAdapter extends LoggingDebugSession { response.body.supportsReadMemoryRequest = true; response.body.supportsWriteMemoryRequest = true; - response.body.supportSuspendDebuggee = true; + response.body.supportSuspendDebuggee = false; response.body.supportTerminateDebuggee = true; // response.body.supportsFunctionBreakpoints = true; response.body.supportsDelayedStackTraceLoading = false; @@ -290,7 +290,7 @@ export class MiDebugAdapter extends LoggingDebugSession { executeTasks(this.projectUri, serverPath, isDebugAllowed) .then(async () => { if (args?.noDebug) { - checkServerReadiness().then(() => { + checkServerReadiness(this.projectUri).then(() => { openRuntimeServicesWebview(this.projectUri); extension.isServerStarted = true; RPCLayer._messengers.get(this.projectUri)?.sendNotification(miServerRunStateChanged, { type: 'webview', webviewType: 'micro-integrator.runtime-services-panel' }, 'Running'); diff --git a/workspaces/mi/mi-extension/src/debugger/debugHelper.ts b/workspaces/mi/mi-extension/src/debugger/debugHelper.ts index effaae44f82..bfa72fcd98f 100644 --- a/workspaces/mi/mi-extension/src/debugger/debugHelper.ts +++ b/workspaces/mi/mi-extension/src/debugger/debugHelper.ts @@ -93,9 +93,11 @@ function checkServerLiveness(): Promise { }); } -export function checkServerReadiness(): Promise { +export function checkServerReadiness(projectUri: string): Promise { const startTime = Date.now(); - const maxTimeout = 120000; + const config = workspace.getConfiguration('MI', Uri.file(projectUri)); + const configuredTimeout = config.get("serverTimeoutInSecs"); + const maxTimeout = (Number.isFinite(Number(configuredTimeout)) && Number(configuredTimeout) > 0) ? Number(configuredTimeout) * 1000 : 120000; const retryInterval = 2000; return new Promise((resolve, reject) => { @@ -421,7 +423,9 @@ export async function stopServer(projectUri: string, serverPath: string, isWindo } export async function executeTasks(projectUri: string, serverPath: string, isDebug: boolean): Promise { - const maxTimeout = 120000; + const config = workspace.getConfiguration('MI', Uri.file(projectUri)); + const configuredTimeout = config.get("serverTimeoutInSecs"); + const maxTimeout = (Number.isFinite(Number(configuredTimeout)) && Number(configuredTimeout) > 0) ? Number(configuredTimeout) * 1000 : 120000; return new Promise(async (resolve, reject) => { const langClient = await MILanguageClient.getInstance(projectUri); const isTerminated = await langClient.shutdownTryoutServer(); diff --git a/workspaces/mi/mi-extension/src/debugger/debugger.ts b/workspaces/mi/mi-extension/src/debugger/debugger.ts index 665d0d7e6cf..814de8d0fde 100644 --- a/workspaces/mi/mi-extension/src/debugger/debugger.ts +++ b/workspaces/mi/mi-extension/src/debugger/debugger.ts @@ -376,7 +376,7 @@ export class Debugger extends EventEmitter { return new Promise(async (resolve, reject) => { this.startDebugger().then(() => { extension.preserveActivity = true; - checkServerReadiness().then(() => { + checkServerReadiness(this.projectUri).then(() => { this.sendResumeCommand().then(async () => { const allRuntimeBreakpoints = this.getAllRuntimeBreakpoints(); if (allRuntimeBreakpoints.size > 0) { diff --git a/workspaces/mi/mi-extension/src/util/onboardingUtils.ts b/workspaces/mi/mi-extension/src/util/onboardingUtils.ts index 0ab29e8fb57..342ff937b18 100644 --- a/workspaces/mi/mi-extension/src/util/onboardingUtils.ts +++ b/workspaces/mi/mi-extension/src/util/onboardingUtils.ts @@ -1094,7 +1094,7 @@ async function runBallerinaBuildsWithProgress(projectPath: string, isBallerinaIn console.debug('[Ballerina Build] Ballerina Command Path:', balCommand); // Check if Ballerina executable exists - if (!fs.existsSync(balCommand)) { + if (!(isBallerinaInstalled || fs.existsSync(balCommand))) { console.debug('[Ballerina Build] Error: Ballerina executable not found at:', balCommand); vscode.window.showErrorMessage(`Ballerina executable not found at: ${balCommand}`); reject(new Error('Ballerina executable not found')); diff --git a/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx b/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx index a6b76068d9d..2512b205df5 100644 --- a/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx @@ -24,7 +24,7 @@ import { View, ViewContent, ViewHeader } from "../../components/View"; import { EditProxyForm, ProxyProps } from "../Forms/EditForms/EditProxyForm"; import { generateProxyData, onProxyEdit } from "../../utils/form"; import { useVisualizerContext } from "@wso2/mi-rpc-client"; -import { EVENT_TYPE, MACHINE_VIEW } from "@wso2/mi-core"; +import { EVENT_TYPE, MACHINE_VIEW, Platform } from "@wso2/mi-core"; import path from "path"; export interface ProxyViewProps { @@ -47,17 +47,19 @@ export const ProxyView = ({ model: ProxyModel, documentUri, diagnostics }: Proxy const handleEditProxy = () => { setFormOpen(true); } - const onSave = (data: EditProxyForm) => { + const onSave = async (data: EditProxyForm) => { let artifactNameChanged = false; let documentPath = documentUri; - if (path.basename(documentUri).split('.')[0] !== data.name) { - rpcClient.getMiDiagramRpcClient().renameFile({existingPath: documentUri, newPath: path.join(path.dirname(documentUri), `${data.name}.xml`)}); + const machineView = await rpcClient.getVisualizerState(); + const proxyName = machineView.platform === Platform.WINDOWS ? path.win32.basename(documentUri).split('.')[0] : path.basename(documentUri).split('.')[0]; + if (proxyName !== data.name) { + await rpcClient.getMiDiagramRpcClient().renameFile({existingPath: documentUri, newPath: path.join(path.dirname(documentUri), `${data.name}.xml`)}); artifactNameChanged = true; documentPath = path.join(path.dirname(documentUri), `${data.name}.xml`); } onProxyEdit(data, model, documentPath, rpcClient); if (artifactNameChanged) { - rpcClient.getMiVisualizerRpcClient().openView({ type: EVENT_TYPE.OPEN_VIEW, location: { view: MACHINE_VIEW.Overview } }); + await rpcClient.getMiVisualizerRpcClient().openView({ type: EVENT_TYPE.OPEN_VIEW, location: { view: MACHINE_VIEW.Overview } }); } else { setFormOpen(false); } From 2ad61fc9a48eae3f5ef4a6e0dadc4b6ac9bb0d12 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Thu, 11 Dec 2025 13:29:17 +0530 Subject: [PATCH 037/102] Add error banner on connection fail --- .../Connection/AddConnectionPopup/index.tsx | 1 + .../DatabaseConnectionPopup/index.tsx | 209 +++++++++++++++--- 2 files changed, 182 insertions(+), 28 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index bb48a87ddf1..845ec931f35 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -448,6 +448,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { target={target} onClose={handleCloseWizard} onBack={handleBackToConnectorList} + onBrowseConnectors={handleBackToConnectorList} /> ); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 3d1f461947f..346117cf833 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -18,7 +18,7 @@ import React, { useState } from "react"; import styled from "@emotion/styled"; -import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps } from "@wso2/ui-toolkit"; +import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps, Icon } from "@wso2/ui-toolkit"; import { Stepper } from "@wso2/ui-toolkit"; import { LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -126,8 +126,20 @@ const FormField = styled.div` `; const ActionButton = styled(Button)` - width: 100%; margin-top: 24px; + width: 100% !important; + min-width: 0 !important; + max-width: none !important; + display: flex !important; + justify-content: center; + align-items: center; + align-self: stretch; + box-sizing: border-box; + + & > div { + width: 100% !important; + max-width: 100% !important; + } `; const TablesGrid = styled.div` @@ -169,7 +181,7 @@ const TableName = styled(Typography)` const SelectAllButton = styled(Button)` margin-top: 16px; - align-self: flex-start; + align-self: flex-end; `; const SelectionInfo = styled.div` @@ -182,8 +194,77 @@ const SelectionInfo = styled.div` const ActionButtonsContainer = styled.div` display: flex; gap: 12px; - justify-content: flex-end; + justify-content: center; margin-top: 24px; + width: 100%; +`; + +const ConfigurablesPanel = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + margin-top: 16px; +`; + +const ConfigurablesDescription = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; + line-height: 1.5; +`; + +const ErrorContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + border: 1px solid ${ThemeColors.ERROR}; + margin-top: 16px; +`; + +const ErrorHeader = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const ErrorTitle = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ERROR}; + margin: 0; +`; + +const SeparatorLine = styled.div` + width: 100%; + height: 1px; + background-color: ${ThemeColors.OUTLINE_VARIANT}; + opacity: 0.5; +`; + +const BrowseMoreButton = styled(Button)` + margin-top: 0; + width: 100% !important; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--vscode-button-secondaryBackground, #3c3c3c) !important; + color: var(--vscode-button-secondaryForeground, #ffffff) !important; + border-radius: 4px; + + &:hover { + background-color: var(--vscode-button-secondaryHoverBackground, #4a4a4a) !important; + } + + & > span { + color: var(--vscode-button-secondaryForeground, #ffffff) !important; + } `; interface DatabaseConnectionPopupProps { @@ -191,6 +272,7 @@ interface DatabaseConnectionPopupProps { target?: LinePosition; onClose?: (data?: ParentPopupData) => void; onBack?: () => void; + onBrowseConnectors?: () => void; } type DatabaseType = "PostgreSQL" | "MySQL" | "MSSQL"; @@ -222,7 +304,7 @@ const DEFAULT_PORTS: Record = { }; export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - const { fileName, target, onClose, onBack } = props; + const { fileName, target, onClose, onBack, onBrowseConnectors} = props; const { rpcClient } = useRpcContext(); const [currentStep, setCurrentStep] = useState(0); @@ -238,6 +320,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const [isIntrospecting, setIsIntrospecting] = useState(false); const [connectionName, setConnectionName] = useState(""); const [isSaving, setIsSaving] = useState(false); + const [connectionError, setConnectionError] = useState(null); const steps = ["Introspect Database", "Select Tables", "Create Connection"]; @@ -255,10 +338,15 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { ...credentials, [field]: value, }); + // Clear error when user modifies credentials + if (connectionError) { + setConnectionError(null); + } }; const handleIntrospect = async () => { setIsIntrospecting(true); + setConnectionError(null); try { // Map database type to dbSystem format expected by RPC const dbSystemMap: Record = { @@ -282,7 +370,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { if (response.errorMsg) { console.error(">>> Error introspecting database", response.errorMsg); - // TODO: Show error message to user + setConnectionError(response.errorMsg); return; } @@ -293,13 +381,15 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { })); setTables(databaseTables); setCurrentStep(1); + setConnectionError(null); } else { console.warn(">>> No tables found in database"); - // TODO: Show message to user that no tables were found + setConnectionError("No tables found in the database."); } } catch (error) { console.error(">>> Error introspecting database", error); - // TODO: Show error message to user + const errorMessage = error instanceof Error ? error.message : "Unable to connect to the database. Please verify your credentials and ensure the database server is accessible."; + setConnectionError(errorMessage); } finally { setIsIntrospecting(false); } @@ -378,9 +468,41 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { } }; + const handleBrowseMoreConnectors = () => { + if (onBrowseConnectors) { + onBrowseConnectors(); + } else { + // Fallback: close this popup and let parent handle navigation + onClose?.(); + } + }; + const selectedTablesCount = tables.filter((t) => t.selected).length; const totalTablesCount = tables.length; + const renderErrorDisplay = () => { + if (!connectionError) return null; + + return ( + + + + Connection Failed + + + Unable to connect to the database. Please verify your credentials and ensure the database server is accessible. + + + + Or try using a pre-built connector: + + + Browse Pre-built Connectors + + + ); + }; + const renderStepContent = () => { switch (currentStep) { case 0: @@ -392,6 +514,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { Enter credentials to connect and introspect the database
+ {renderErrorDisplay()} {isIntrospecting ? "Connecting..." : "Connect & Introspect Database"} @@ -489,17 +612,12 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { Select All - - - - + ); @@ -509,7 +627,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) {
Connection Details - Name your connection to complete the setup + Name your connection and configure default values for configurables
@@ -525,18 +643,53 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { This name will be used to reference this connection in your code
- - - -
+ + Connection Configurables + + Host, port, username, password, and database name will be created as configurables. Define default values below. + + + + handleCredentialsChange("host", value)} + /> + + + handleCredentialsChange("port", value)} + /> + + + handleCredentialsChange("username", value)} + /> + + + handleCredentialsChange("databaseName", value)} + /> + + + + + {isSaving ? "Saving..." : "Save Connection"} + ); From 08211903c1df1d38cb38052096e3264b0625932e Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Thu, 11 Dec 2025 13:29:28 +0530 Subject: [PATCH 038/102] Add edit connection popup --- .../Connection/EditConnectionPopup/index.tsx | 403 ++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx new file mode 100644 index 00000000000..d2ac8d8deb3 --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx @@ -0,0 +1,403 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useState } from "react"; +import styled from "@emotion/styled"; +import { FlowNode, LinePosition, ParentPopupData, SubPanel, SubPanelView } from "@wso2/ballerina-core"; +import { useRpcContext } from "@wso2/ballerina-rpc-client"; +import { Button, Codicon, ThemeColors, Typography, Overlay, ProgressRing } from "@wso2/ui-toolkit"; +import ConnectionConfigView from "../ConnectionConfigView"; +import { getFormProperties } from "../../../../utils/bi"; +import { ExpressionFormField } from "@wso2/ballerina-side-panel"; +import { HelperView } from "../../HelperView"; +import { ConnectorIcon } from "@wso2/bi-diagram"; +import { cloneDeep } from "lodash"; + +const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 800px; + height: 80%; + max-height: 800px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 20px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +const ConfigHeader = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const BackButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const ConfigTitleContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConfigSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const ConnectorInfoCard = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + margin: 24px 32px; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + position: relative; +`; + +const ConnectorInfoIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_CONTAINER}; + flex-shrink: 0; + + & > img { + width: 32px; + height: 32px; + object-fit: contain; + } + + & > svg { + width: 32px; + height: 32px; + } +`; + +const StyledCodicon = styled(Codicon)` + font-size: 32px; + width: 32px; + height: 32px; +`; + +const StyledConnectorIcon = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + + & > img { + width: 32px; + height: 32px; + object-fit: contain; + } + + & > svg { + width: 32px; + height: 32px; + } +`; + +const ConnectorInfoContent = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const ConnectorInfoName = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const ConnectorInfoDescription = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + + +const ContentContainer = styled.div` + flex: 1; + overflow-y: auto; + padding: 24px 32px; +`; + +const LoadingContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; + padding: 40px; +`; + +interface EditConnectionPopupProps { + connectionName: string; + fileName?: string; + target?: LinePosition; + onClose?: (data?: ParentPopupData) => void; +} + +export function EditConnectionPopup(props: EditConnectionPopupProps) { + const { connectionName, fileName, target, onClose } = props; + const { rpcClient } = useRpcContext(); + + const [connection, setConnection] = useState(); + const [filePath, setFilePath] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); + const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); + + useEffect(() => { + const fetchConnection = async () => { + setIsLoading(true); + try { + const res = await rpcClient.getBIDiagramRpcClient().getModuleNodes(); + console.log(">>> moduleNodes", { moduleNodes: res }); + + if (!res.flowModel.connections || res.flowModel.connections.length === 0) { + console.error(">>> No connections found"); + if (onClose) { + onClose(); + } else { + rpcClient.getVisualizerRpcClient()?.goHome(); + } + return; + } + + const connector = res.flowModel.connections.find( + (node) => node.properties.variable.value === connectionName + ); + + if (!connector) { + console.error(">>> Error finding connector", { connectionName }); + if (onClose) { + onClose(); + } else { + rpcClient.getVisualizerRpcClient()?.goHome(); + } + return; + } + + const connectionFile = connector.codedata.lineRange.fileName; + const connectionFilePath = (await rpcClient.getVisualizerRpcClient().joinProjectPath({ + segments: [connectionFile] + })).filePath; + + setFilePath(connectionFilePath); + setConnection(connector); + + const formProperties = getFormProperties(connector); + console.log(">>> Connector form properties", formProperties); + } catch (error) { + console.error(">>> Error fetching connection", error); + if (onClose) { + onClose(); + } else { + rpcClient.getVisualizerRpcClient()?.goHome(); + } + } finally { + setIsLoading(false); + } + }; + + fetchConnection(); + }, [connectionName, rpcClient, onClose]); + + const handleClosePopup = () => { + if (onClose) { + onClose(); + } else { + rpcClient.getVisualizerRpcClient()?.goHome(); + } + }; + + const handleOnFormSubmit = async (node: FlowNode) => { + console.log(">>> on form submit", node); + if (connection) { + setIsSaving(true); + try { + if (filePath === "") { + console.error(">>> Error updating source code. No source file found"); + setIsSaving(false); + return; + } + + const response = await rpcClient.getBIDiagramRpcClient().getSourceCode({ + filePath: filePath, + flowNode: node, + isConnector: true, + }); + + console.log(">>> Updated source code", response); + if (response.artifacts.length > 0) { + handleClosePopup(); + } else { + console.error(">>> Error updating source code", response); + } + } catch (error) { + console.error(">>> Error saving connection", error); + } finally { + setIsSaving(false); + } + } + }; + + + + const updateExpressionField = (data: ExpressionFormField) => { + setUpdatedExpressionField(data); + }; + + const handleResetUpdatedExpressionField = () => { + setUpdatedExpressionField(undefined); + }; + + + const handleBack = () => { + handleClosePopup(); + }; + + const getConnectorName = () => { + return connection?.codedata?.module || connectionName || "Connection"; + }; + + const getConnectorDescription = () => { + return connection?.metadata?.description || ""; + }; + + const getConnectorIcon = () => { + return connection?.metadata?.icon; + }; + + if (isLoading) { + return ( + <> + + + + + + + + ); + } + + if (!connection) { + return null; + } + + return ( + <> + + + + + + + + Edit Connection + + Update connection settings for this connector + + + + + + + + + + {getConnectorIcon() ? ( + + + + ) : ( + + )} + + + {getConnectorName()} + + {getConnectorDescription()} + + + + + + { + // Remove description property from node before passing to form + // since it's already shown in the connector info card + const nodeWithoutDescription = cloneDeep(connection); + if (nodeWithoutDescription?.metadata?.description) { + delete nodeWithoutDescription.metadata.description; + } + return nodeWithoutDescription; + })()} + onSubmit={handleOnFormSubmit} + updatedExpressionField={updatedExpressionField} + resetUpdatedExpressionField={handleResetUpdatedExpressionField} + isSaving={isSaving} + /> + + + + ); +} + +export default EditConnectionPopup; + From 9eebe0e1f53cd6ff8780c483e7a1429f0fbd218a Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Thu, 11 Dec 2025 13:29:47 +0530 Subject: [PATCH 039/102] Integrate new UI to connection views --- .../ballerina/ballerina-visualizer/src/MainPanel.tsx | 6 ++++-- .../ballerina/ballerina-visualizer/src/PopupPanel.tsx | 9 +++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx index 52de4b84a17..23ac82ae32b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx @@ -82,6 +82,8 @@ import { ServiceFunctionForm } from "./views/BI/ServiceFunctionForm"; import ServiceConfigureView from "./views/BI/ServiceDesigner/ServiceConfigureView"; import { WorkspaceOverview } from "./views/BI/WorkspaceOverview"; import { SamplesView } from "./views/BI/SamplesView"; +import AddConnectionPopup from "./views/BI/Connection/AddConnectionPopup"; +import EditConnectionPopup from "./views/BI/Connection/EditConnectionPopup"; const globalStyles = css` *, @@ -538,7 +540,7 @@ const MainPanel = () => { break; case MACHINE_VIEW.AddConnectionWizard: setViewComponent( - @@ -546,7 +548,7 @@ const MainPanel = () => { break; case MACHINE_VIEW.EditConnectionWizard: setViewComponent( - ); diff --git a/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx index 7237af4ed1c..c0c20ba25f6 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx @@ -25,6 +25,8 @@ import { ThemeColors, Overlay } from "@wso2/ui-toolkit"; import EditConnectionWizard from "./views/BI/Connection/EditConnectionWizard"; import { FunctionForm } from "./views/BI"; import { DataMapper } from "./views/DataMapper"; +import AddConnectionPopup from "./views/BI/Connection/AddConnectionPopup"; +import EditConnectionPopup from "./views/BI/Connection/EditConnectionPopup"; const ViewContainer = styled.div<{ isFullScreen?: boolean }>` position: fixed; @@ -70,12 +72,11 @@ const PopupPanel = (props: PopupPanelProps) => { case MACHINE_VIEW.AddConnectionWizard: rpcClient.getVisualizerLocation().then((location) => { setViewComponent( - ); }); @@ -84,11 +85,11 @@ const PopupPanel = (props: PopupPanelProps) => { rpcClient.getVisualizerLocation().then((location) => { setViewComponent( <> - - + {/* */} ); }); From 0126bf96c9ef4b4dca19aea9a7976043d4bcd0f7 Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 12 Dec 2025 09:49:46 +0530 Subject: [PATCH 040/102] Update CHANGELOG --- workspaces/mi/mi-extension/CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/workspaces/mi/mi-extension/CHANGELOG.md b/workspaces/mi/mi-extension/CHANGELOG.md index 04232606b56..f4b642a1ff8 100644 --- a/workspaces/mi/mi-extension/CHANGELOG.md +++ b/workspaces/mi/mi-extension/CHANGELOG.md @@ -2,11 +2,20 @@ All notable changes to the "micro-integrator" extension will be documented in this file. +## [3.1.1] - 2025-12-12 + +### Fixed + +Fixed: Diagram issue when using Filter and If-Else mediators ([#1356](https://github.com/wso2/mi-vscode/issues/1356)) +Fixed: Ballerina Executable Not Found Error ([#1357](https://github.com/wso2/mi-vscode/issues/1357)) +Fixed: Cannot edit a proxy in Windows OS ([#1368](https://github.com/wso2/mi-vscode/issues/1368)) +Fixed: Issues in the build and run functionality ([#1369](https://github.com/wso2/mi-vscode/issues/1369)) + ## [3.1.0] - 2025-12-08 ### New Features -Added: Nested query support for data services via a seperate query view +Added: Nested query support for data services via a separate query view Added: Oracle synonym support for data service resource generation ### Fixed From f14414a5c44aecc805b70f7de6e85f214de2342e Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Fri, 12 Dec 2025 10:18:47 +0530 Subject: [PATCH 041/102] fix template editor behaviour --- .../components/editors/ExpressionField.tsx | 5 ++-- .../ChipExpressionDefaultConfig.ts | 2 +- .../Configurations.tsx | 23 ------------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index b86bdff8fe0..6f6ab20212d 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -32,10 +32,11 @@ import { LineRange } from '@wso2/ballerina-core/lib/interfaces/common'; import { FormField, HelperpaneOnChangeOptions } from '../Form/types'; import { ChipExpressionEditorComponent } from './MultiModeExpressionEditor/ChipExpressionEditor/components/ChipExpressionEditor'; import RecordConfigPreviewEditor from './MultiModeExpressionEditor/RecordConfigPreviewEditor/RecordConfigPreviewEditor'; -import { RawTemplateEditorConfig, StringTemplateEditorConfig, PrimaryModeChipExpressionEditorConfig } from './MultiModeExpressionEditor/Configurations'; +import { RawTemplateEditorConfig, StringTemplateEditorConfig } from './MultiModeExpressionEditor/Configurations'; import NumberExpressionEditor from './MultiModeExpressionEditor/NumberExpressionEditor/NumberEditor'; import BooleanEditor from './MultiModeExpressionEditor/BooleanEditor/BooleanEditor'; import { SQLExpressionEditor } from './MultiModeExpressionEditor/SqlExpressionEditor/SqlExpressionEditor'; +import { ChipExpressionEditorDefaultConfiguration } from './MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig'; export interface ExpressionField { field: FormField; @@ -285,7 +286,7 @@ export const ExpressionField: React.FC = ({ onOpenExpandedMode={onOpenExpandedMode} onRemove={onRemove} isInExpandedMode={isInExpandedMode} - configuration={new PrimaryModeChipExpressionEditorConfig(primaryMode)} + configuration={new ChipExpressionEditorDefaultConfiguration()} /> ); }; diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts index 327024a32ec..581dc318f6b 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/ChipExpressionEditor/ChipExpressionDefaultConfig.ts @@ -19,7 +19,7 @@ import FXButton from "./components/FxButton"; import { ParsedToken } from "./utils"; -export abstract class ChipExpressionEditorDefaultConfiguration { +export class ChipExpressionEditorDefaultConfiguration { getHelperValue(value: string, token?: ParsedToken) { return value; } diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx index 83f5f56c56b..8e5e4bfa617 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/MultiModeExpressionEditor/Configurations.tsx @@ -172,26 +172,3 @@ export class ChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfi return `\$\{${value}\}`; } } - -export class PrimaryModeChipExpressionEditorConfig extends ChipExpressionEditorDefaultConfiguration { - private readonly primaryMode: InputMode; - - constructor(primaryMode: InputMode) { - super(); - this.primaryMode = primaryMode; - } - - getHelperValue(value: string, token?: ParsedToken): string { - const isTemplateEditor = ( - this.primaryMode === InputMode.TEXT || - this.primaryMode === InputMode.TEMPLATE || - this.primaryMode === InputMode.SQL - ); - - - if (isTemplateEditor && (!token || token.type !== TokenType.FUNCTION)) { - return `\$\{${value}\}`; - } - return value; - } -} From 81c09b4de06b7b8f62e00775b2b1e335bf578f4c Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Fri, 12 Dec 2025 10:29:33 +0530 Subject: [PATCH 042/102] remove unused props --- .../src/components/editors/ExpressionField.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx index 6f6ab20212d..a2f96b23f6d 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/editors/ExpressionField.tsx @@ -105,7 +105,6 @@ const EditorRibbon = ({ onClick }: { onClick: () => void }) => { export const ExpressionField: React.FC = ({ inputMode, field, - primaryMode, name, value, completions, @@ -116,22 +115,15 @@ export const ExpressionField: React.FC = ({ targetLineRange, onChange, extractArgsFromFunction, - onCompletionSelect, onFocus, onBlur, onSave, onCancel, onRemove, - isHelperPaneOpen, - changeHelperPaneState, getHelperPane, - helperPaneHeight, - helperPaneWidth, growRange, - helperPaneZIndex, exprRef, anchorRef, - onToggleHelperPane, sanitizedExpression, rawExpression, onOpenExpandedMode, From 3b7759dabe4e7397c452e1348b28d9ef21b9b074 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 12 Dec 2025 13:13:50 +0530 Subject: [PATCH 043/102] Integrate WSDL related LS and rpc calls --- .../src/rpc-types/connector-wizard/index.ts | 5 +++- .../rpc-types/connector-wizard/interfaces.ts | 20 +++++++++++++ .../rpc-types/connector-wizard/rpc-type.ts | 14 +++++++++- .../src/core/extended-language-client.ts | 9 +++++- .../src/rpc-managers/common/utils.ts | 2 +- .../connector-wizard/rpc-handler.ts | 5 +++- .../connector-wizard/rpc-manager.ts | 28 ++++++++++++++++++- .../connector-wizard/rpc-client.ts | 9 +++++- 8 files changed, 85 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts index f5b2b71779e..e2ded2bcde2 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/index.ts @@ -24,7 +24,9 @@ import { PersistClientGenerateRequest, IntrospectDatabaseResponse, PersistClientGenerateResponse, - IntrospectDatabaseRequest + IntrospectDatabaseRequest, + WSDLApiClientGenerationRequest, + WSDLApiClientGenerationResponse } from "./interfaces"; export interface ConnectorWizardAPI { @@ -32,4 +34,5 @@ export interface ConnectorWizardAPI { getConnectors: (params: ConnectorsRequest) => Promise; introspectDatabase: (params: IntrospectDatabaseRequest) => Promise; persistClientGenerate: (params: PersistClientGenerateRequest) => Promise; + generateWSDLApiClient: (params: WSDLApiClientGenerationRequest) => Promise; } diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts index dbd2f68be93..f8edbed01cd 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts @@ -83,3 +83,23 @@ export interface PersistSource { [key: string]: TextEdit[]; }; } + +export interface WSDLApiClientGenerationRequest { + projectPath: string; + module: string; + wsdlFilePath: string; + portName?: string; + operations?: string[]; +} + +export interface WSDLApiClientGenerationResponse { + source?: WSDLApiClientSource; + errorMsg?: string; + stackTrace?: string; +} + +export interface WSDLApiClientSource { + textEditsMap: { + [key: string]: TextEdit[]; + }; +} diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts index 6ed12bf08e8..3c7e971302c 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/rpc-type.ts @@ -17,7 +17,18 @@ * * THIS FILE INCLUDES AUTO GENERATED CODE */ -import { ConnectorRequest, ConnectorResponse, ConnectorsRequest, ConnectorsResponse, PersistClientGenerateRequest, IntrospectDatabaseResponse, PersistClientGenerateResponse, IntrospectDatabaseRequest } from "./interfaces"; +import { + ConnectorRequest, + ConnectorResponse, + ConnectorsRequest, + ConnectorsResponse, + PersistClientGenerateRequest, + IntrospectDatabaseResponse, + PersistClientGenerateResponse, + IntrospectDatabaseRequest, + WSDLApiClientGenerationRequest, + WSDLApiClientGenerationResponse +} from "./interfaces"; import { RequestType } from "vscode-messenger-common"; const _preFix = "connector-wizard"; @@ -25,3 +36,4 @@ export const getConnector: RequestType = { export const getConnectors: RequestType = { method: `${_preFix}/getConnectors` }; export const introspectDatabase: RequestType = { method: `${_preFix}/introspectDatabase` }; export const persistClientGenerate: RequestType = { method: `${_preFix}/persistClientGenerate` }; +export const generateWSDLApiClient: RequestType = { method: `${_preFix}/generateWSDLApiClient` }; diff --git a/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts b/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts index 1220d670d50..1bfc17679c6 100644 --- a/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts +++ b/workspaces/ballerina/ballerina-extension/src/core/extended-language-client.ts @@ -279,7 +279,9 @@ import { IntrospectDatabaseRequest, IntrospectDatabaseResponse, PersistClientGenerateRequest, - PersistClientGenerateResponse + PersistClientGenerateResponse, + WSDLApiClientGenerationRequest, + WSDLApiClientGenerationResponse } from "@wso2/ballerina-core"; import { BallerinaExtension } from "./index"; import { debug, handlePullModuleProgress } from "../utils"; @@ -465,6 +467,7 @@ enum EXTENDED_APIS { OPEN_API_CLIENT_DELETE = 'openAPIService/deleteModule', PERSIST_DATABASE_INTROSPECTION = 'persistService/introspectDatabase', PERSIST_CLIENT_GENERATE = 'persistService/generatePersistClient', + WSDL_API_CLIENT_GENERATE = 'wsdlService/genClient', GET_PROJECT_INFO = 'designModelService/projectInfo', GET_ARTIFACTS = 'designModelService/artifacts', PUBLISH_ARTIFACTS = 'designModelService/publishArtifacts', @@ -712,6 +715,10 @@ export class ExtendedLangClient extends LanguageClient implements ExtendedLangCl return this.sendRequest(EXTENDED_APIS.PERSIST_CLIENT_GENERATE, params); } + async generateWSDLApiClient(params: WSDLApiClientGenerationRequest): Promise { + return this.sendRequest(EXTENDED_APIS.WSDL_API_CLIENT_GENERATE, params); + } + async getRecord(params: RecordParams): Promise { const isSupported = await this.isExtendedServiceSupported(EXTENDED_APIS.CONNECTOR_RECORD); if (!isSupported) { diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts index fee5752c260..3dc2a3ebcda 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/common/utils.ts @@ -92,7 +92,7 @@ export async function askFilePath() { canSelectMany: false, defaultUri: Uri.file(os.homedir()), filters: { - 'Files': ['yaml', 'json', 'yml', 'graphql'] + 'Files': ['yaml', 'json', 'yml', 'graphql', 'wsdl'] }, title: "Select a file", }); diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts index 537e5c80b86..9c6bfb19ba6 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-handler.ts @@ -20,12 +20,14 @@ import { ConnectorRequest, ConnectorsRequest, + generateWSDLApiClient, getConnector, getConnectors, introspectDatabase, IntrospectDatabaseRequest, persistClientGenerate, - PersistClientGenerateRequest + PersistClientGenerateRequest, + WSDLApiClientGenerationRequest } from "@wso2/ballerina-core"; import { Messenger } from "vscode-messenger"; import { ConnectorWizardRpcManager } from "./rpc-manager"; @@ -36,4 +38,5 @@ export function registerConnectorWizardRpcHandlers(messenger: Messenger) { messenger.onRequest(getConnectors, (args: ConnectorsRequest) => rpcManger.getConnectors(args)); messenger.onRequest(introspectDatabase, (args: IntrospectDatabaseRequest) => rpcManger.introspectDatabase(args)); messenger.onRequest(persistClientGenerate, (args: PersistClientGenerateRequest) => rpcManger.persistClientGenerate(args)); + messenger.onRequest(generateWSDLApiClient, (args: WSDLApiClientGenerationRequest) => rpcManger.generateWSDLApiClient(args)); } diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts index 0f9c2de8417..1a1e960a21e 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/connector-wizard/rpc-manager.ts @@ -27,7 +27,9 @@ import { IntrospectDatabaseRequest, IntrospectDatabaseResponse, PersistClientGenerateRequest, - PersistClientGenerateResponse + PersistClientGenerateResponse, + WSDLApiClientGenerationRequest, + WSDLApiClientGenerationResponse } from "@wso2/ballerina-core"; import { StateMachine } from "../../stateMachine"; import { updateSourceCode } from "../../utils/source-utils"; @@ -107,4 +109,28 @@ export class ConnectorWizardRpcManager implements ConnectorWizardAPI { } }); } + + async generateWSDLApiClient(params: WSDLApiClientGenerationRequest): Promise { + return new Promise(async (resolve) => { + try { + const response = await StateMachine.langClient().generateWSDLApiClient(params); + console.log(">>> generate wsdl api client response", response); + + const wsdlResponse = response as WSDLApiClientGenerationResponse; + + if (wsdlResponse?.source?.textEditsMap) { + await updateSourceCode({ + textEdits: wsdlResponse.source.textEditsMap, + description: `WSDL API Client Generation` + }); + console.log(">>> Applied text edits for wsdl api client"); + } + + resolve(wsdlResponse); + } catch (error) { + console.log(">>> error generating wsdl api client", error); + resolve(undefined); + } + }); + } } diff --git a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts index d29641f06c3..f23fb8f8d77 100644 --- a/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts +++ b/workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/connector-wizard/rpc-client.ts @@ -23,6 +23,7 @@ import { ConnectorsRequest, ConnectorsResponse, ConnectorWizardAPI, + generateWSDLApiClient, getConnector, getConnectors, introspectDatabase, @@ -30,7 +31,9 @@ import { IntrospectDatabaseResponse, persistClientGenerate, PersistClientGenerateRequest, - PersistClientGenerateResponse + PersistClientGenerateResponse, + WSDLApiClientGenerationRequest, + WSDLApiClientGenerationResponse } from "@wso2/ballerina-core"; import { HOST_EXTENSION } from "vscode-messenger-common"; import { Messenger } from "vscode-messenger-webview"; @@ -57,4 +60,8 @@ export class ConnectorWizardRpcClient implements ConnectorWizardAPI { persistClientGenerate(params: PersistClientGenerateRequest): Promise { return this._messenger.sendRequest(persistClientGenerate, HOST_EXTENSION, params); } + + generateWSDLApiClient(params: WSDLApiClientGenerationRequest): Promise { + return this._messenger.sendRequest(generateWSDLApiClient, HOST_EXTENSION, params); + } } From ab0805a71e7e7efeb058864ee8f3206da05140b6 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 12 Dec 2025 13:14:21 +0530 Subject: [PATCH 044/102] Add spec based connector generator view --- .../Connection/APIConnectionPopup/index.tsx | 626 ++++++++++++++++++ .../Connection/AddConnectionPopup/index.tsx | 7 +- 2 files changed, 629 insertions(+), 4 deletions(-) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx new file mode 100644 index 00000000000..46f67c3ca8e --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -0,0 +1,626 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useMemo, useState } from "react"; +import styled from "@emotion/styled"; +import { Button, Codicon, Dropdown, Overlay, Stepper, TextField, ThemeColors, Typography } from "@wso2/ui-toolkit"; +import { AvailableNode, Category, DataMapperDisplayMode, DIRECTORY_MAP, FlowNode, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; +import { useRpcContext } from "@wso2/ballerina-rpc-client"; +import { ExpressionFormField } from "@wso2/ballerina-side-panel"; +import ConnectionConfigView from "../ConnectionConfigView"; +import { getFormProperties } from "../../../../utils/bi"; +import { FormSubmitOptions } from "../../FlowDiagram"; + +const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 860px; + height: 82%; + max-height: 840px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 20px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +const PopupHeader = styled.div` + display: flex; + align-items: center; + gap: 16px; + padding: 24px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const BackButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const HeaderTitleContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const PopupSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +const StepperContainer = styled.div` + padding: 20px 32px 18px 32px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +const ContentContainer = styled.div` + flex: 1; + overflow-y: auto; + padding: 24px 32px; + background: ${ThemeColors.SURFACE_DIM}; +`; + +const StepContent = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +`; + +const SectionTitle = styled(Typography)` + font-size: 16px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const SectionSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +const FormSection = styled.div` + display: flex; + flex-direction: column; + gap: 18px; + background: transparent; + border: none; + padding: 0; +`; + +const FormField = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const UploadCard = styled.div<{ hasFile?: boolean }>` + display: flex; + align-items: center; + gap: 12px; + padding: 16px 18px; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + border-radius: 14px; + background: ${ThemeColors.SURFACE_DIM}; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + border-color: ${ThemeColors.PRIMARY}; + background: ${ThemeColors.SURFACE_CONTAINER}; + } + + ${(props: { hasFile?: boolean }) => + props.hasFile + ? ` + border-color: ${ThemeColors.PRIMARY}; + background: ${ThemeColors.PRIMARY_CONTAINER}; + ` + : ""} +`; + +const UploadIcon = styled.div` + width: 48px; + height: 48px; + border-radius: 12px; + background: ${ThemeColors.SURFACE_BRIGHT}; + display: flex; + align-items: center; + justify-content: center; + color: ${ThemeColors.ON_SURFACE}; +`; + +const UploadText = styled.div` + display: flex; + flex-direction: column; + gap: 2px; +`; + +const UploadTitle = styled(Typography)` + font-size: 14px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +const UploadSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + + +const ActionButton = styled(Button)` + margin-top: 12px; + width: 100% !important; + min-width: 0 !important; + display: flex !important; + justify-content: center; + align-items: center; +`; + +const SummaryCard = styled.div` + padding: 12px 14px; + border-radius: 10px; + background: ${ThemeColors.SURFACE_CONTAINER}; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + color: ${ThemeColors.ON_SURFACE}; + font-size: 12px; +`; + +const InfoRow = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +const StepHeader = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +const DisabledActionButton = styled(Button)` + width: 100% !important; + min-width: 0 !important; + margin-top: 4px; +`; + +const FormGrid = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +const StepBadge = styled.div<{ active?: boolean; completed?: boolean }>` + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: ${(props: { active?: boolean; completed?: boolean }) => + props.completed + ? ThemeColors.PRIMARY_CONTAINER + : props.active + ? ThemeColors.SURFACE_DIM + : ThemeColors.SURFACE_CONTAINER}; + border: 1px solid + ${(props: { active?: boolean; completed?: boolean }) => + props.completed + ? ThemeColors.PRIMARY + : props.active + ? ThemeColors.OUTLINE + : ThemeColors.OUTLINE_VARIANT}; + color: ${(props: { active?: boolean; completed?: boolean }) => + props.completed ? ThemeColors.PRIMARY : ThemeColors.ON_SURFACE_VARIANT}; + font-weight: 600; +`; + +const StepperLabel = styled.div` + display: flex; + flex-direction: column; + gap: 2px; +`; + +const StepperRow = styled.div` + display: flex; + align-items: center; + gap: 16px; +`; + +const StepperCopy = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +interface APIConnectionPopupProps { + projectPath: string; + fileName: string; + target?: LinePosition; + onClose?: (parent?: ParentPopupData) => void; + onBack?: () => void; + onGenerateSubmit?: () => Promise | void; + onSaveConnection?: () => Promise | void; +} + +export function APIConnectionPopup(props: APIConnectionPopupProps) { + const { projectPath, fileName, target, onBack, onClose, onGenerateSubmit, onSaveConnection } = props; + const { rpcClient } = useRpcContext(); + + const [currentStep, setCurrentStep] = useState(0); + const [specType, setSpecType] = useState("OpenAPI"); + const [selectedFilePath, setSelectedFilePath] = useState(""); + const [connectorName, setConnectorName] = useState(""); + + const [isSavingConnector, setIsSavingConnector] = useState(false); + const [isSavingConnection, setIsSavingConnection] = useState(false); + const [selectedFlowNode, setSelectedFlowNode] = useState(undefined); + const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); + + const steps = useMemo(() => ["Import API Spec", "Create Connection"], []); + + const apiSpecOptions = useMemo( + () => [ + { id: "openapi", value: "OpenAPI", content: "OpenAPI" }, + { id: "wsdl", value: "WSDL", content: "WSDL" }, + ], + [] + ); + + const supportedFileFormats = useMemo(() => { + const isOpenApi = specType.toLowerCase() === "openapi"; + return isOpenApi ? ".yaml, .yml, .json" : ".wsdl"; + }, [specType]); + + const handleFileSelect = async () => { + if (!rpcClient) { + return; + } + const projectDirectory = await rpcClient.getCommonRpcClient().selectFileOrDirPath({ isFile: true }); + if (projectDirectory.path) { + setSelectedFilePath(projectDirectory.path); + } + }; + + const getFileName = (filePath: string) => { + if (!filePath) return ""; + const parts = filePath.split(/[/\\]/); + return parts[parts.length - 1]; + }; + + const handleOnGenerateSubmit = async (specFilePath: string, module: string, specType: string) => { + if (!rpcClient) { + return { success: false, errorMessage: "RPC client not available" }; + } + + const isOpenApi = specType.toLowerCase() === "openapi"; + + if (isOpenApi) { + const response = await rpcClient.getBIDiagramRpcClient().generateOpenApiClient({ + openApiContractPath: specFilePath, + projectPath: projectPath, + module: module, + }); + return { + success: !response.errorMessage, + errorMessage: response.errorMessage, + }; + } else { + // WSDL generation + const response = await rpcClient.getConnectorWizardRpcClient().generateWSDLApiClient({ + wsdlFilePath: specFilePath, + projectPath: projectPath, + module: module, + }); + return { + success: !response.errorMsg, + errorMessage: response.errorMsg, + }; + } + }; + + const findConnectorByModule = (categories: Category[], moduleName: string): AvailableNode | null => { + for (const category of categories) { + if (category.items) { + for (const item of category.items) { + if ("codedata" in item) { + const availableNode = item as AvailableNode; + if (availableNode.codedata?.module === moduleName) { + return availableNode; + } + } + } + } + } + return null; + }; + + const handleSaveConnector = async () => { + if (!selectedFilePath || !connectorName || !rpcClient) { + return; + } + setIsSavingConnector(true); + const generateResponse = await handleOnGenerateSubmit(selectedFilePath, connectorName, specType); + + // Only proceed if there's no error message + if (generateResponse?.success) { + // Wait a bit for the connector to be available, then search for it + try { + // Small delay to ensure the connector is available + await new Promise(resolve => setTimeout(resolve, 500)); + + const defaultPosition = target || { line: 0, offset: 0 }; + const searchResponse = await rpcClient.getBIDiagramRpcClient().search({ + position: { + startLine: defaultPosition, + endLine: defaultPosition, + }, + filePath: fileName, + queryMap: { + limit: 60, + filterByCurrentOrg: false, + }, + searchKind: "CONNECTOR", + }); + + // Find the connector we just created + const createdConnector = findConnectorByModule(searchResponse.categories, connectorName); + if (createdConnector && createdConnector.codedata) { + // Get the flowNode template + const nodeTemplateResponse = await rpcClient.getBIDiagramRpcClient().getNodeTemplate({ + position: target || null, + filePath: fileName, + id: createdConnector.codedata, + }); + setSelectedFlowNode(nodeTemplateResponse.flowNode); + } else { + console.warn(">>> Created connector not found in search results"); + } + } catch (error) { + console.error(">>> Error finding created connector", error); + } + setCurrentStep(1); + } else { + console.error(">>> Error generating connector:", generateResponse?.errorMessage); + // Optionally show error to user + } + setIsSavingConnector(false); + }; + + const handleOnFormSubmit = async (node: FlowNode, _dataMapperMode?: DataMapperDisplayMode, options?: FormSubmitOptions) => { + console.log(">>> on form submit", node); + if (selectedFlowNode) { + setIsSavingConnection(true); + const visualizerLocation = await rpcClient.getVisualizerLocation(); + let connectionsFilePath = visualizerLocation.documentUri || visualizerLocation.projectPath; + + if (node.codedata.isGenerated && !connectionsFilePath.endsWith(".bal")) { + connectionsFilePath += "/main.bal"; + } + + if (connectionsFilePath === "") { + console.error(">>> Error updating source code. No source file found"); + setIsSavingConnection(false); + return; + } + + // node property scope is local. then use local file path and line position + if ((node.properties?.scope?.value as string)?.toLowerCase() === "local") { + node.codedata.lineRange = { + fileName: visualizerLocation.documentUri, + startLine: target, + endLine: target, + }; + } + + // Check if the node is a connector + const isConnector = node.codedata.node === "NEW_CONNECTION"; + + rpcClient + .getBIDiagramRpcClient() + .getSourceCode({ + filePath: connectionsFilePath, + flowNode: node, + isConnector: isConnector, + }) + .then((response) => { + console.log(">>> Updated source code", response); + if (response.artifacts.length > 0) { + setIsSavingConnection(false); + const newConnection = response.artifacts.find((artifact) => artifact.isNew); + if (onClose) { + onClose({ recentIdentifier: newConnection.name, artifactType: DIRECTORY_MAP.CONNECTION }); + } + } else { + console.error(">>> Error updating source code", response); + setIsSavingConnection(false); + } + }) + .catch((error) => { + console.error(">>> Error saving connection", error); + setIsSavingConnection(false); + }); + } + }; + + const handleSaveConnection = async () => { + setIsSavingConnection(true); + if (onSaveConnection) { + await onSaveConnection(); + } + setIsSavingConnection(false); + }; + + const renderStepper = () => { + return ( + <> + + + + + ); + }; + + const renderImportStep = () => ( + + + + Connector Configuration + + + Import API specification for the connector + + + + + setSpecType(value)} + /> + + + setConnectorName(value)} + placeholder="Enter connector name" + /> + + Name of the connector module to be generated + + + + + Import Specification File + + + + + + + + {selectedFilePath ? getFileName(selectedFilePath) : "Choose file to import"} + + Supports {supportedFileFormats} files + + + + + {isSavingConnector ? "Saving..." : "Save Connector"} + + + + ); + + const renderConnectionStep = () => { + if (selectedFlowNode) { + return ( + + setUpdatedExpressionField(undefined)} + isPullingConnector={isSavingConnection} + /> + + ); + } + return ( + + + + Loading connector configuration... + + + + ); + }; + + const renderStepContent = () => { + if (currentStep === 0) { + return renderImportStep(); + } + return renderConnectionStep(); + }; + + return ( + <> + + + + + + + + Connect via API Specification + Import an API specification file to create a connection + + onClose?.()}> + + + + {renderStepper()} + {renderStepContent()} + + + ); +} + +export default APIConnectionPopup; + diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 845ec931f35..1175dcdff28 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -24,10 +24,11 @@ import { Button, Codicon, Icon, SearchBox, ThemeColors, Typography, Overlay, Pro import { cloneDeep, debounce } from "lodash"; import ButtonCard from "../../../../components/ButtonCard"; import { ConnectorIcon } from "@wso2/bi-diagram"; -import AddConnectionWizard from "../AddConnectionWizard"; +import APIConnectionPopup from "../APIConnectionPopup"; import ConnectionConfigurationPopup from "../ConnectionConfigurationPopup"; import DatabaseConnectionPopup from "../DatabaseConnectionPopup"; import { BodyTinyInfo } from "../../../styles"; +import AddConnectionWizard from "../AddConnectionWizard"; const PopupOverlay = styled(Overlay)` z-index: 1999; @@ -429,13 +430,11 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { return ( <> - ); From d90ee021b57494c56b28bf222c1cfecb4150892a Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:29:06 +0530 Subject: [PATCH 045/102] Fix proxy renaming issue on Windows OS --- workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx b/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx index 2512b205df5..c03236cc218 100644 --- a/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Diagram/Proxy.tsx @@ -53,9 +53,10 @@ export const ProxyView = ({ model: ProxyModel, documentUri, diagnostics }: Proxy const machineView = await rpcClient.getVisualizerState(); const proxyName = machineView.platform === Platform.WINDOWS ? path.win32.basename(documentUri).split('.')[0] : path.basename(documentUri).split('.')[0]; if (proxyName !== data.name) { - await rpcClient.getMiDiagramRpcClient().renameFile({existingPath: documentUri, newPath: path.join(path.dirname(documentUri), `${data.name}.xml`)}); + const updatedPath = machineView.platform === Platform.WINDOWS ? path.join(path.win32.dirname(documentUri), `${data.name}.xml`) : path.join(path.dirname(documentUri), `${data.name}.xml`); + await rpcClient.getMiDiagramRpcClient().renameFile({existingPath: documentUri, newPath: updatedPath}); artifactNameChanged = true; - documentPath = path.join(path.dirname(documentUri), `${data.name}.xml`); + documentPath = updatedPath; } onProxyEdit(data, model, documentPath, rpcClient); if (artifactNameChanged) { From ac09f365c071d0629963286a610c72812c280114 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Fri, 12 Dec 2025 13:57:12 +0530 Subject: [PATCH 046/102] Refactor DataMapperView to streamline sub-mapping handling and improve code readability --- .../ballerina-visualizer/src/Hooks.tsx | 11 +++++---- .../src/views/DataMapper/DataMapperView.tsx | 23 ++++++++----------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx b/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx index 1505bef523e..9705db3fddc 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/Hooks.tsx @@ -33,14 +33,17 @@ export const useDataMapperModel = ( const getDMModel = async () => { try { + + const codeDataPosition = { + line: codedata.lineRange.startLine.line, + offset: codedata.lineRange.startLine.offset + }; + const modelParams = { filePath, codedata, targetField: viewId, - position: position ?? { - line: codedata.lineRange.startLine.line, - offset: codedata.lineRange.startLine.offset - } + position: viewState.subMappingName ? codeDataPosition : (position ?? codeDataPosition) }; console.log('>>> [Data Mapper] Model Parameters:', modelParams); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index 3fd223d4053..5b1c515c3ed 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -246,20 +246,15 @@ export function DataMapperView(props: DataMapperProps) { const handleView = async (viewId: string, isSubMapping?: boolean) => { if (isSubMapping) { - if (viewState.subMappingName) { - // If the view is a sub mapping, we can reuse the codedata of the parent view - setViewState({ viewId, codedata: viewState.codedata, subMappingName: viewState.subMappingName }); - } else { - const resp = await rpcClient - .getDataMapperRpcClient() - .getSubMappingCodedata({ - filePath, - codedata: viewState.codedata, - view: viewId - }); - console.log(">>> [Data Mapper] getSubMappingCodedata response:", resp); - setViewState({ viewId, codedata: resp.codedata, subMappingName: viewId }); - } + const resp = await rpcClient + .getDataMapperRpcClient() + .getSubMappingCodedata({ + filePath, + codedata: viewState.codedata, + view: viewId + }); + console.log(">>> [Data Mapper] getSubMappingCodedata response:", resp); + setViewState({ viewId, codedata: resp.codedata, subMappingName: viewId }); } else { if (viewState.subMappingName) { // If the view is a sub mapping, we need to get the codedata of the parent mapping From 6d393300855ae94d9caa6fb83d53460411a9e412 Mon Sep 17 00:00:00 2001 From: madushajg Date: Fri, 12 Dec 2025 16:16:55 +0530 Subject: [PATCH 047/102] Enable running 'BI:Debug Integration' command for multi-package workspaces and directories --- .../src/features/bi/activator.ts | 189 +++++++++++------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts index 30cd723aabd..067d19b6da8 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts @@ -41,7 +41,7 @@ import { createVersionNumber, findBallerinaPackageRoot, isSupportedSLVersion } f import { extension } from "../../BalExtensionContext"; import { VisualizerWebview } from "../../views/visualizer/webview"; import { getCurrentProjectRoot, tryGetCurrentBallerinaFile } from "../../utils/project-utils"; -import { needsProjectDiscovery, promptPackageSelection, requiresPackageSelection } from "../../utils/command-utils"; +import { selectPackageOrPrompt, needsProjectDiscovery, requiresPackageSelection } from "../../utils/command-utils"; import { findWorkspaceTypeFromWorkspaceFolders } from "../../rpc-managers/common/utils"; import { MESSAGES } from "../project"; @@ -49,66 +49,6 @@ const FOCUS_DEBUG_CONSOLE_COMMAND = 'workbench.debug.action.focusRepl'; const TRACE_SERVER_OFF = "off"; const TRACE_SERVER_VERBOSE = "verbose"; -/** - * Helper function to handle command invocation with proper context resolution. - * Supports both tree view clicks and command palette invocation. - * - * @param item - The tree item (undefined when invoked from command palette) - * @param view - The view to open - * @param additionalViewParams - Additional parameters to pass to the view - */ -async function handleCommandWithContext( - item: TreeItem | undefined, - view: MACHINE_VIEW, - additionalViewParams: Record = {} -): Promise { - const { projectInfo, projectPath, view: currentView, workspacePath } = StateMachine.context(); - const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; - const hasActiveTextEditor = !!window.activeTextEditor; - - const currentBallerinaFile = tryGetCurrentBallerinaFile(); - const projectRoot = await findBallerinaPackageRoot(currentBallerinaFile); - - // Scenario 1: Multi-package workspace invoked from command palette - if (!item) { - if (requiresPackageSelection(workspacePath, currentView, projectPath, isWebviewOpen, hasActiveTextEditor)) { - await handleCommandWithPackageSelection(projectInfo, view, additionalViewParams); - return; - } - - if (needsProjectDiscovery(projectInfo, projectRoot, projectPath)) { - try { - const success = await tryHandleCommandWithDiscoveredProject(view, additionalViewParams); - if (!success) { - window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - } - } catch { - window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - } - return; - } - - openView(EVENT_TYPE.OPEN_VIEW, { - view, - projectPath, - ...additionalViewParams - }); - } - // Scenario 2: Invoked from tree view with item context - else if (item?.resourceUri) { - const projectPath = item.resourceUri.fsPath; - openView(EVENT_TYPE.OPEN_VIEW, { - view, - projectPath, - ...additionalViewParams - }); - } - // Scenario 3: Default - no specific context - else { - openView(EVENT_TYPE.OPEN_VIEW, { view, ...additionalViewParams }); - } -} - export function activate(context: BallerinaExtension) { const isWorkspaceSupported = isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)); @@ -134,7 +74,7 @@ export function activate(context: BallerinaExtension) { commands.registerCommand(BI_COMMANDS.BI_DEBUG_PROJECT, () => { commands.executeCommand(FOCUS_DEBUG_CONSOLE_COMMAND); - startDebugging(Uri.file(StateMachine.context().projectPath), false, true); + handleDebugCommandWithContext(); }); commands.registerCommand(BI_COMMANDS.ADD_CONNECTIONS, async (item?: TreeItem) => { @@ -233,6 +173,122 @@ export function activate(context: BallerinaExtension) { } +/** + * Helper function to handle command invocation with proper context resolution. + * Supports both tree view clicks and command palette invocation. + * + * @param item - The tree item (undefined when invoked from command palette) + * @param view - The view to open + * @param additionalViewParams - Additional parameters to pass to the view + */ +async function handleCommandWithContext( + item: TreeItem | undefined, + view: MACHINE_VIEW, + additionalViewParams: Record = {} +): Promise { + const { projectInfo, projectPath, view: currentView, workspacePath } = StateMachine.context(); + const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; + const hasActiveTextEditor = !!window.activeTextEditor; + + const currentBallerinaFile = tryGetCurrentBallerinaFile(); + const projectRoot = await findBallerinaPackageRoot(currentBallerinaFile); + + // Scenario 1: Multi-package workspace invoked from command palette + if (!item) { + if (requiresPackageSelection(workspacePath, currentView, projectPath, isWebviewOpen, hasActiveTextEditor)) { + await handleCommandWithPackageSelection(projectInfo, view, additionalViewParams); + return; + } + + if (needsProjectDiscovery(projectInfo, projectRoot, projectPath)) { + try { + const success = await tryHandleCommandWithDiscoveredProject(view, additionalViewParams); + if (!success) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + } + } catch { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + } + return; + } + + openView(EVENT_TYPE.OPEN_VIEW, { + view, + projectPath, + ...additionalViewParams + }); + } + // Scenario 2: Invoked from tree view with item context + else if (item?.resourceUri) { + const projectPath = item.resourceUri.fsPath; + openView(EVENT_TYPE.OPEN_VIEW, { + view, + projectPath, + ...additionalViewParams + }); + } + // Scenario 3: Default - no specific context + else { + openView(EVENT_TYPE.OPEN_VIEW, { view, ...additionalViewParams }); + } +} + +/** Handles the debug command based on current workspace context. */ +async function handleDebugCommandWithContext() { + const { workspacePath, view, projectPath, projectInfo } = StateMachine.context(); + const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; + const hasActiveTextEditor = !!window.activeTextEditor; + + const currentBallerinaFile = tryGetCurrentBallerinaFile(); + const projectRoot = await findBallerinaPackageRoot(currentBallerinaFile); + + if (requiresPackageSelection(workspacePath, view, projectPath, isWebviewOpen, hasActiveTextEditor)) { + await handleDebugCommandWithPackageSelection(projectInfo); + return; + } + + if (needsProjectDiscovery(projectInfo, projectRoot, projectPath)) { + try { + await handleDebugCommandWithProjectDiscovery(); + } catch { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + } + return; + } + + startDebugging(Uri.file(projectPath), false, true); +} + +/** + * Prompts user to select a package and starts debugging. + * @param projectInfo - The project info + * @returns void + */ +async function handleDebugCommandWithPackageSelection(projectInfo: ProjectInfo) { + const availablePackages = projectInfo?.children.map((child: any) => child.projectPath) ?? []; + + const selectedPackage = await selectPackageOrPrompt(availablePackages, "Select a package to debug"); + if (!selectedPackage) { + return; + } + + await StateMachine.updateProjectRootAndInfo(selectedPackage, projectInfo); + startDebugging(Uri.file(selectedPackage), false, true); +} + +/** Discovers project root from active file and starts debugging. */ +async function handleDebugCommandWithProjectDiscovery() { + const packageRoot = await getCurrentProjectRoot(); + + if (packageRoot) { + const projectInfo = await StateMachine.langClient().getProjectInfo({ projectPath: packageRoot }); + await StateMachine.updateProjectRootAndInfo(packageRoot, projectInfo); + startDebugging(Uri.file(packageRoot), false, true); + } else { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + } +} + function openBallerinaTomlFile(context: BallerinaExtension) { const projectPath = StateMachine.context().projectPath || StateMachine.context().workspacePath; if (!projectPath) { @@ -473,15 +529,10 @@ async function handleCommandWithPackageSelection( ): Promise { const availablePackages = projectInfo?.children.map((child: any) => child.projectPath) ?? []; - if (availablePackages.length === 0) { - window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - return false; - } - - const selectedPackage = await promptPackageSelection(availablePackages); + const selectedPackage = await selectPackageOrPrompt(availablePackages); if (!selectedPackage) { - return false; // User cancelled + return false; } openView(EVENT_TYPE.OPEN_VIEW, { From 2e8f7defe1e5d4568fce27eb68c365f66295021c Mon Sep 17 00:00:00 2001 From: madushajg Date: Fri, 12 Dec 2025 16:18:46 +0530 Subject: [PATCH 048/102] Refactor package selection utility to handle single project workspaces --- .../config-generator/configGenerator.ts | 6 ++---- .../src/features/project/cmds/pack.ts | 2 +- .../src/utils/command-utils.ts | 17 ++++++++++++++++- .../src/views/ai-panel/activate.ts | 15 +++------------ .../src/views/visualizer/activate.ts | 12 +++--------- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/config-generator/configGenerator.ts b/workspaces/ballerina/ballerina-extension/src/features/config-generator/configGenerator.ts index 4a21bf27d09..5b55eceaffa 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/config-generator/configGenerator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/config-generator/configGenerator.ts @@ -32,7 +32,7 @@ import { openView, StateMachine } from "../../stateMachine"; import * as path from "path"; import { TracerMachine } from "../tracing"; import { VisualizerWebview } from "../../views/visualizer/webview"; -import { promptPackageSelection } from "../../utils/command-utils"; +import { selectPackageOrPrompt } from "../../utils/command-utils"; const UNUSED_IMPORT_ERR_CODE = "BCE2002"; @@ -53,9 +53,7 @@ export async function prepareAndGenerateConfig( const packages = StateMachine.context().projectInfo?.children; const packageList = packages?.map((child) => child.projectPath) ?? []; - const selectedPackage = await promptPackageSelection(packageList, "Select a package to run"); - - // User cancelled selection + const selectedPackage = await selectPackageOrPrompt(packageList, "Select a package to run"); if (!selectedPackage) { return; } diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts index bddc5fa04be..e7b7e719c36 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/pack.ts @@ -65,7 +65,7 @@ export function activatePackCommand() { const currentProject = await getCurrentBallerinaProject(targetPath); - let balCommand = BALLERINA_COMMANDS.PACK + let balCommand = BALLERINA_COMMANDS.PACK; if (isSupportedSLVersion(extension.ballerinaExtInstance, createVersionNumber(2201, 13, 0)) && extension.ballerinaExtInstance.enabledExperimentalFeatures()) { balCommand = BALLERINA_COMMANDS.PACK_WITH_EXPERIMENTAL; diff --git a/workspaces/ballerina/ballerina-extension/src/utils/command-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/command-utils.ts index 81a7365205e..1008d75e7ab 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/command-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/command-utils.ts @@ -18,6 +18,7 @@ import { window } from "vscode"; import { MACHINE_VIEW, ProjectInfo } from "@wso2/ballerina-core"; +import { MESSAGES } from "../features/project"; export function requiresPackageSelection( workspacePath: string | undefined, @@ -33,7 +34,7 @@ export function requiresPackageSelection( ); } -export async function promptPackageSelection( +async function promptPackageSelection( availablePackages: string[], placeHolder?: string ): Promise { @@ -43,6 +44,20 @@ export async function promptPackageSelection( }); } +export async function selectPackageOrPrompt( + availablePackages: string[], + placeHolder?: string +): Promise { + if (availablePackages.length === 0) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + return; + } + if (availablePackages.length === 1) { + return availablePackages[0]; + } + return await promptPackageSelection(availablePackages, placeHolder); +} + export function needsProjectDiscovery( projectInfo: ProjectInfo, projectRoot: string | undefined, diff --git a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts index 5ec1579ee30..e872726510b 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/ai-panel/activate.ts @@ -24,7 +24,7 @@ import { notifyAiWebview } from '../../RPCLayer'; import { openView, StateMachine } from '../../stateMachine'; import { MESSAGES } from '../../features/project/cmds/cmd-runner'; import { VisualizerWebview } from '../visualizer/webview'; -import { needsProjectDiscovery, promptPackageSelection, requiresPackageSelection } from '../../utils/command-utils'; +import { selectPackageOrPrompt, needsProjectDiscovery, requiresPackageSelection } from '../../utils/command-utils'; import { getCurrentProjectRoot, tryGetCurrentBallerinaFile } from '../../utils/project-utils'; import { findBallerinaPackageRoot } from '../../utils'; import { findWorkspaceTypeFromWorkspaceFolders } from '../../rpc-managers/common/utils'; @@ -80,19 +80,10 @@ async function handleOpenAIPanel(defaultPrompt?: AIPanelPrompt): Promise { async function handleWorkspaceLevelAIPanel(projectInfo: ProjectInfo): Promise { const availablePackages = projectInfo?.children.map((child: ProjectInfo) => child.projectPath) ?? []; - if (availablePackages.length === 0) { - vscode.window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - return false; - } - try { - const selectedPackage = await promptPackageSelection( - availablePackages, - "Select a package to open AI panel" - ); - + const selectedPackage = await selectPackageOrPrompt(availablePackages, "Select a package to open AI panel"); if (!selectedPackage) { - return true; // User cancelled + return true; } openPackageOverviewView(selectedPackage); diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts index fd212c0f631..d3bfc9aeef8 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/activate.ts @@ -26,7 +26,7 @@ import { createVersionNumber, findBallerinaPackageRoot, isSupportedSLVersion } f import { VisualizerWebview } from './webview'; import { findWorkspaceTypeFromWorkspaceFolders } from '../../rpc-managers/common/utils'; import { getCurrentProjectRoot, tryGetCurrentBallerinaFile } from '../../utils/project-utils'; -import { requiresPackageSelection, needsProjectDiscovery, promptPackageSelection } from '../../utils/command-utils'; +import { requiresPackageSelection, needsProjectDiscovery, selectPackageOrPrompt } from '../../utils/command-utils'; export function activateSubscriptions() { const context = extension.context; @@ -200,15 +200,9 @@ function openTypeDiagramView(projectPath?: string, resetHistory = false): void { async function openTypeDiagramForWorkspace(projectInfo: ProjectInfo): Promise { const availablePackages = projectInfo?.children.map((child: any) => child.projectPath) ?? []; - if (availablePackages.length === 0) { - vscode.window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); - return false; - } - - const selectedPackage = await promptPackageSelection(availablePackages, "Select a package to open type diagram"); - + const selectedPackage = await selectPackageOrPrompt(availablePackages, "Select a package to open type diagram"); if (!selectedPackage) { - return false; // User cancelled + return false; } openTypeDiagramView(selectedPackage); From 5c3c9774f54a9a91b5a4118f65b2303481a6c4ba Mon Sep 17 00:00:00 2001 From: madushajg Date: Fri, 12 Dec 2025 18:42:03 +0530 Subject: [PATCH 049/102] Address review suggestions --- .../ballerina-extension/src/features/bi/activator.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts index 067d19b6da8..b83183eb239 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts @@ -256,6 +256,11 @@ async function handleDebugCommandWithContext() { return; } + if (!projectPath) { + window.showErrorMessage(MESSAGES.NO_PROJECT_FOUND); + return; + } + startDebugging(Uri.file(projectPath), false, true); } @@ -265,7 +270,7 @@ async function handleDebugCommandWithContext() { * @returns void */ async function handleDebugCommandWithPackageSelection(projectInfo: ProjectInfo) { - const availablePackages = projectInfo?.children.map((child: any) => child.projectPath) ?? []; + const availablePackages = projectInfo?.children.map((child: ProjectInfo) => child.projectPath) ?? []; const selectedPackage = await selectPackageOrPrompt(availablePackages, "Select a package to debug"); if (!selectedPackage) { From aa62ef9e1c8fba76c8f5be6372a51bff48b78d74 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 12 Dec 2025 21:44:54 +0530 Subject: [PATCH 050/102] Fix form closing when toml updates --- .../src/rpc-managers/bi-diagram/rpc-manager.ts | 17 +++++++++++------ .../src/utils/source-utils.ts | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts index 7d3e31e45a5..326a85ac25e 100644 --- a/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts +++ b/workspaces/ballerina/ballerina-extension/src/rpc-managers/bi-diagram/rpc-manager.ts @@ -1934,7 +1934,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { projectPath: projectPath, module: params.module }; - StateMachine.langClient().openApiGenerateClient(request).then((res) => { + StateMachine.langClient().openApiGenerateClient(request).then(async (res) => { if (!res.source || !res.source.textEditsMap) { console.error("textEditsMap is undefined or null"); reject(new Error("textEditsMap is undefined or null")); @@ -1947,11 +1947,16 @@ export class BiDiagramRpcManager implements BIDiagramAPI { return; } - // Convert the plain object back to a Map - const textEditsMap = new Map(Object.entries(res.source.textEditsMap)); - textEditsMap.forEach(async (value, key) => { - await this.applyTextEdits(key, value); - }); + + if (res?.source?.textEditsMap) { + await updateSourceCode({ + textEdits: res.source.textEditsMap, + description: `OpenAPI Client Generation`, + skipUpdateViewOnTomlUpdate: true + }); + console.log(">>> Applied text edits for openapi client"); + } + resolve({}); }).catch((error) => { console.log(">>> error generating openapi client", error); diff --git a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts index 46fea53a42e..5150e8700b7 100644 --- a/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts +++ b/workspaces/ballerina/ballerina-extension/src/utils/source-utils.ts @@ -38,6 +38,7 @@ export interface UpdateSourceCodeRequest { identifier?: string; skipPayloadCheck?: boolean; // This is used to skip the payload check because the payload data might become empty as a result of a change. Example: Deleting a component. isRenameOperation?: boolean; // This is used to identify if the update is a rename operation. + skipUpdateViewOnTomlUpdate?: boolean; // This is used to skip updating the view on toml updates in certain scenarios. } export async function updateSourceCode(updateSourceCodeRequest: UpdateSourceCodeRequest, isChangeFromHelperPane?: boolean): Promise { @@ -168,7 +169,7 @@ export async function updateSourceCode(updateSourceCodeRequest: UpdateSourceCode } return new Promise((resolve, reject) => { - if (tomlFilesUpdated) { + if (tomlFilesUpdated && !updateSourceCodeRequest?.skipUpdateViewOnTomlUpdate) { StateMachine.setReadyMode(); resolve([]); return; From 0628d1db55d013032c1d5d717b27be9ffee12021 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 12 Dec 2025 21:45:58 +0530 Subject: [PATCH 051/102] Fix form closing on popup issues --- .../ballerina-visualizer/src/MainPanel.tsx | 5 +++++ .../BI/Connection/APIConnectionPopup/index.tsx | 17 +++-------------- .../BI/Connection/AddConnectionPopup/index.tsx | 14 +++++++++----- .../DatabaseConnectionPopup/index.tsx | 4 ++-- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx index 23ac82ae32b..9ba2dd62ad8 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx @@ -543,6 +543,7 @@ const MainPanel = () => { ); break; @@ -650,6 +651,10 @@ const MainPanel = () => { .openView({ type: EVENT_TYPE.CLOSE_VIEW, location: { view: null, recentIdentifier: parent?.recentIdentifier, artifactType: parent?.artifactType }, isPopup: true }); }; + const handleNavigateToOverview = () => { + rpcClient.getVisualizerRpcClient().goHome(); + }; + const handlePopupClose = (id: string) => { closeModal(id); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index 46f67c3ca8e..61f323321ef 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -279,12 +279,10 @@ interface APIConnectionPopupProps { target?: LinePosition; onClose?: (parent?: ParentPopupData) => void; onBack?: () => void; - onGenerateSubmit?: () => Promise | void; - onSaveConnection?: () => Promise | void; } export function APIConnectionPopup(props: APIConnectionPopupProps) { - const { projectPath, fileName, target, onBack, onClose, onGenerateSubmit, onSaveConnection } = props; + const { projectPath, fileName, target, onBack, onClose } = props; const { rpcClient } = useRpcContext(); const [currentStep, setCurrentStep] = useState(0); @@ -468,9 +466,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { if (response.artifacts.length > 0) { setIsSavingConnection(false); const newConnection = response.artifacts.find((artifact) => artifact.isNew); - if (onClose) { - onClose({ recentIdentifier: newConnection.name, artifactType: DIRECTORY_MAP.CONNECTION }); - } + onClose?.({ recentIdentifier: newConnection.name, artifactType: DIRECTORY_MAP.CONNECTION }); } else { console.error(">>> Error updating source code", response); setIsSavingConnection(false); @@ -478,19 +474,12 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { }) .catch((error) => { console.error(">>> Error saving connection", error); + }).finally(() => { setIsSavingConnection(false); }); } }; - const handleSaveConnection = async () => { - setIsSavingConnection(true); - if (onSaveConnection) { - await onSaveConnection(); - } - setIsSavingConnection(false); - }; - const renderStepper = () => { return ( <> diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 1175dcdff28..5b464585da3 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -233,10 +233,11 @@ interface AddConnectionPopupProps { fileName: string; target?: LinePosition; onClose?: (parent?: ParentPopupData) => void; + onNavigateToOverview?: () => void; } export function AddConnectionPopup(props: AddConnectionPopupProps) { - const { projectPath, fileName, target, onClose } = props; + const { projectPath, fileName, target, onClose, onNavigateToOverview } = props; const { rpcClient } = useRpcContext(); const [searchText, setSearchText] = useState(""); @@ -364,6 +365,9 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { setSelectedConnector(null); if (parent) { onClose?.(parent); + if (onNavigateToOverview) { + onNavigateToOverview(); + } } }; @@ -435,6 +439,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { fileName={fileName} target={target} onClose={handleCloseWizard} + onBack={handleBackToConnectorList} /> ); @@ -453,12 +458,11 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { } const handleClosePopup = () => { - if (onClose) { - onClose(); + if (onNavigateToOverview) { + onNavigateToOverview(); } else { - rpcClient.getVisualizerRpcClient()?.goHome(); + onClose?.(); } - }; const openLearnMoreURL = () => { diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 346117cf833..fd14716b649 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -20,7 +20,7 @@ import React, { useState } from "react"; import styled from "@emotion/styled"; import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps, Icon } from "@wso2/ui-toolkit"; import { Stepper } from "@wso2/ui-toolkit"; -import { LinePosition, ParentPopupData } from "@wso2/ballerina-core"; +import { DIRECTORY_MAP, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; const PopupOverlay = styled(Overlay)` @@ -459,7 +459,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { } - onClose?.(); + onClose?.({ recentIdentifier: connectionName, artifactType: DIRECTORY_MAP.CONNECTION }); } catch (error) { console.error(">>> Error saving connection", error); // TODO: Show error message to user From 958451453b99f84244895a0d79e753fa6d4a9cda Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 00:12:05 +0530 Subject: [PATCH 052/102] Refactor authentication handling by removing authStore and integrating WSO2AuthenticationProvider - Removed all references to authStore and replaced them with ext.authProvider for user authentication state management. - Updated commands related to user login, logout, and context management to utilize the new authentication provider. - Ensured that user information retrieval and context updates are consistent with the new authentication flow. - Deleted the authStore implementation and adjusted related stores and utilities to maintain functionality. --- .../src/PlatformExtensionApi.ts | 3 +- .../src/auth/wso2-auth-provider.ts | 406 ++++++++++++++++++ .../src/cmds/clone-project-cmd.ts | 3 +- .../src/cmds/cmd-utils.ts | 6 +- .../src/cmds/create-component-cmd.ts | 12 +- .../src/cmds/refresh-directory-context-cmd.ts | 3 +- .../src/cmds/sign-in-cmd.ts | 18 - .../src/cmds/sign-in-with-code-cmd.ts | 3 +- .../src/cmds/sign-out-cmd.ts | 5 +- .../src/error-utils.ts | 17 +- .../wso2-platform-extension/src/extension.ts | 29 +- .../src/extensionVariables.ts | 2 + .../wso2-platform-extension/src/status-bar.ts | 6 +- .../src/stores/auth-store.ts | 83 ---- .../src/stores/context-store.ts | 9 +- .../src/stores/data-cache-store.ts | 3 +- .../src/tarminal-handlers.ts | 5 +- .../src/telemetry/utils.ts | 6 +- .../src/uri-handlers.ts | 9 +- .../src/webviews/WebviewRPC.ts | 7 +- 20 files changed, 468 insertions(+), 167 deletions(-) create mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts delete mode 100644 workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index d22e458c484..9bd82ea5f71 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -19,14 +19,13 @@ import type { ComponentKind, IWso2PlatformExtensionAPI, openClonedDirReq } from "@wso2/wso2-platform-core"; import { ext } from "./extensionVariables"; import { hasDirtyRepo } from "./git/util"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; import { webviewStateStore } from "./stores/webview-state-store"; import { openClonedDir } from "./uri-handlers"; import { isSamePath } from "./utils"; export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { - public isLoggedIn = () => !!authStore.getState().state?.userInfo; + public isLoggedIn = () => !!ext.authProvider?.getState().state?.userInfo; public getDirectoryComponents = (fsPath: string) => (contextStore .getState() diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts new file mode 100644 index 00000000000..7e68b2cb8c8 --- /dev/null +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -0,0 +1,406 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { AuthState, UserInfo } from "@wso2/wso2-platform-core"; +import { + type AuthenticationProvider, + type AuthenticationProviderAuthenticationSessionsChangeEvent, + type AuthenticationProviderSessionOptions, + type AuthenticationSession, + Disposable, + EventEmitter, + type SecretStorage, + window, +} from "vscode"; +import { ext } from "../extensionVariables"; +import { getLogger } from "../logger/logger"; +import { contextStore } from "../stores/context-store"; +import { dataCacheStore } from "../stores/data-cache-store"; + +export const WSO2_AUTH_PROVIDER_ID = "wso2-platform"; +const WSO2_SESSIONS_SECRET_KEY = `${WSO2_AUTH_PROVIDER_ID}.sessions`; + +interface SessionData { + id: string; + accessToken: string; + account: { + id: string; + label: string; + }; + scopes: string[]; + userInfo: UserInfo; + region: "US" | "EU"; +} + +export class WSO2AuthenticationProvider implements AuthenticationProvider, Disposable { + private _sessionChangeEmitter = new EventEmitter(); + private _stateChangeEmitter = new EventEmitter<{ state: AuthState }>(); + private _disposable: Disposable; + private _state: AuthState = { userInfo: null, region: "US" }; + + constructor(private readonly secretStorage: SecretStorage) { + this._disposable = Disposable.from(this._sessionChangeEmitter, this._stateChangeEmitter); + } + + get onDidChangeSessions() { + return this._sessionChangeEmitter.event; + } + + /** + * Subscribe to auth state changes + */ + public subscribe(callback: (store: { state: AuthState }) => void): () => void { + const disposable = this._stateChangeEmitter.event(callback); + // Call immediately with current state + callback({ state: this._state }); + return () => disposable.dispose(); + } + + /** + * Get the current state + */ + public getState() { + return { + state: this._state, + resetState: this.resetState.bind(this), + loginSuccess: this.loginSuccess.bind(this), + logout: this.logout.bind(this), + initAuth: this.initAuth.bind(this), + }; + } + + /** + * Get current auth state + */ + get state(): AuthState { + return this._state; + } + + /** + * Get the existing sessions + */ + public async getSessions(scopes: readonly string[] | undefined, options: AuthenticationProviderSessionOptions): Promise { + const allSessions = await this.readSessions(); + + if (scopes && scopes.length > 0) { + const sessions = allSessions.filter((session) => scopes.every((scope) => session.scopes.includes(scope))); + return sessions; + } + + return allSessions; + } + + /** + * Create a new auth session - NOT USED (auth is handled by RPC) + * This is required by the AuthenticationProvider interface but not called directly + */ + public async createSession(scopes: string[]): Promise { + throw new Error("Direct session creation not supported. Use RPC authentication flow."); + } + + /** + * Reset state to initial values + */ + public resetState() { + this._state = { userInfo: null, region: "US" }; + this._stateChangeEmitter.fire({ state: this._state }); + } + + /** + * Handle successful login - updates state and stores session + */ + public async loginSuccess(userInfo: UserInfo, region: "US" | "EU") { + // Update local state + this._state = { userInfo, region }; + + // Update related stores + dataCacheStore.getState().setOrgs(userInfo.organizations); + contextStore.getState().refreshState(); + + // Store session in secure storage + await this.storeSession(userInfo, region); + + // Notify subscribers + this._stateChangeEmitter.fire({ state: this._state }); + } + + /** + * Handle logout - signs out from RPC and clears all state + */ + public async logout(silent = false, skipClearSessions = false) { + getLogger().debug("Signing out from WSO2 Platform"); + + // Call RPC signOut first + try { + await ext.clients.rpcClient.signOut(); + } catch (error) { + getLogger().error("Error during RPC signOut", error); + } + + // Clear VS Code session storage (unless already cleared by removeSession) + if (!skipClearSessions) { + try { + await this.clearSessions(); + } catch (error) { + getLogger().error("Error clearing sessions", error); + } + } + + // Clear local state + this.resetState(); + + if (!silent) { + window.showInformationMessage("Successfully signed out from WSO2 Platform!"); + } + } + + /** + * Initialize authentication on startup + */ + public async initAuth() { + try { + // Try to get session from VS Code secure storage first + const sessionData = await this.getSessionData(); + if (sessionData?.userInfo) { + // We have a stored session, update the state + this._state = { userInfo: sessionData.userInfo, region: sessionData.region }; + dataCacheStore.getState().setOrgs(sessionData.userInfo.organizations); + contextStore.getState().refreshState(); + this._stateChangeEmitter.fire({ state: this._state }); + + const contextStoreState = contextStore.getState().state; + if (contextStoreState.selected?.org) { + ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); + } + return; + } + + // Fallback: check RPC for existing session + const userInfo = await ext.clients.rpcClient.getUserInfo(); + if (userInfo) { + const region = await ext.clients.rpcClient.getCurrentRegion(); + await this.loginSuccess(userInfo, region); + const contextStoreState = contextStore.getState().state; + if (contextStoreState.selected?.org) { + ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); + } + } else { + await this.logout(true); + } + } catch (err) { + getLogger().error("Error during auth initialization", err); + await this.logout(true); + } + } + + /** + * Store or update a session with user info and region + * Called internally after successful RPC authentication + */ + private async storeSession(userInfo: UserInfo, region: "US" | "EU"): Promise { + // Remove any existing sessions first (single account support) + const existingSessions = await this.readSessions(); + const removedSessions = [...existingSessions]; + + const sessionId = this.generateSessionId(); + const sessionData: SessionData = { + id: sessionId, + accessToken: "rpc-authenticated", // Placeholder since RPC handles auth + account: { + label: userInfo.displayName || userInfo.userEmail, + id: userInfo.userId, + }, + scopes: [], + userInfo, + region, + }; + + const session: AuthenticationSession = { + id: sessionData.id, + accessToken: sessionData.accessToken, + account: sessionData.account, + scopes: sessionData.scopes, + }; + + await this.storeSessions([session], sessionData); + + this._sessionChangeEmitter.fire({ + added: [session], + removed: removedSessions, + changed: [] + }); + + return session; + } + + /** + * Remove an existing session + * This is called when user signs out from VS Code's Accounts menu + */ + public async removeSession(sessionId: string): Promise { + const allSessions = await this.readSessions(); + const sessionIdx = allSessions.findIndex((s) => s.id === sessionId); + const session = allSessions[sessionIdx]; + if (!session) { + return; + } + + allSessions.splice(sessionIdx, 1); + await this.storeSessions(allSessions); + this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); + + // Trigger full logout flow (skipClearSessions=true to avoid loop) + await this.logout(false, true); + } + + /** + * Remove all sessions + */ + public async clearSessions(): Promise { + const allSessions = await this.readSessions(); + if (allSessions.length === 0) { + return; + } + + await this.secretStorage.delete(WSO2_SESSIONS_SECRET_KEY); + + this._sessionChangeEmitter.fire({ added: [], removed: allSessions, changed: [] }); + } + + /** + * Get session data including userInfo and region + */ + public async getSessionData(sessionId?: string): Promise { + const sessions = await this.readSessionsData(); + if (sessionId) { + return sessions.find((s) => s.id === sessionId); + } + // Return the first session if no ID is provided (single account support) + return sessions[0]; + } + + /** + * Dispose the provider + */ + public async dispose() { + this._disposable.dispose(); + } + + /** + * Get the user info from stored session (for backward compatibility) + */ + public getUserInfo(): UserInfo | null { + return this._state.userInfo; + } + + /** + * Get the region from stored session (for backward compatibility) + */ + public getRegion(): "US" | "EU" { + return this._state.region; + } + + /** + * Read sessions from secret storage + */ + private async readSessions(): Promise { + try { + const sessionsJson = await this.secretStorage.get(WSO2_SESSIONS_SECRET_KEY); + if (!sessionsJson) { + return []; + } + + const sessionData: SessionData[] = JSON.parse(sessionsJson); + return sessionData.map((data) => ({ + id: data.id, + accessToken: data.accessToken, + account: data.account, + scopes: data.scopes, + })); + } catch (e) { + getLogger().error("Error reading sessions", e); + return []; + } + } + + /** + * Store sessions to secret storage + */ + private async storeSessions(sessions: readonly AuthenticationSession[], newSessionData?: SessionData): Promise { + try { + const existingSessions = await this.readSessionsData(); + let updatedSessions: SessionData[]; + + if (newSessionData) { + // Add or update session + const existingIndex = existingSessions.findIndex((s) => s.id === newSessionData.id); + if (existingIndex >= 0) { + updatedSessions = [...existingSessions]; + updatedSessions[existingIndex] = newSessionData; + } else { + updatedSessions = [...existingSessions, newSessionData]; + } + } else { + // Filter out removed sessions + const sessionIds = sessions.map((s) => s.id); + updatedSessions = existingSessions.filter((s) => sessionIds.includes(s.id)); + } + + await this.secretStorage.store(WSO2_SESSIONS_SECRET_KEY, JSON.stringify(updatedSessions)); + } catch (e) { + getLogger().error("Error storing sessions", e); + } + } + + /** + * Read full session data including userInfo and region + */ + private async readSessionsData(): Promise { + try { + const sessionsJson = await this.secretStorage.get(WSO2_SESSIONS_SECRET_KEY); + if (!sessionsJson) { + return []; + } + + return JSON.parse(sessionsJson); + } catch (e) { + getLogger().error("Error reading session data", e); + return []; + } + } + + /** + * Generate a session ID + */ + private generateSessionId(): string { + return `wso2-${Date.now()}-${Math.random().toString(36).substring(2)}`; + } +} + +/** + * Helper function to wait for user login + */ +export const waitForLogin = async (): Promise => { + return new Promise((resolve) => { + ext.authProvider?.subscribe(({ state }) => { + if (state.userInfo) { + resolve(state.userInfo); + } + }); + }); +}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts index 41880c2f6e8..18ebf65df0a 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts @@ -31,7 +31,6 @@ import { import { type ExtensionContext, ProgressLocation, type QuickPickItem, QuickPickItemKind, Uri, commands, window } from "vscode"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; -import { authStore } from "../stores/auth-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; import { createDirectory, openDirectory } from "../utils"; @@ -168,7 +167,7 @@ export function cloneRepoCommand(context: ExtensionContext) { ]); // set context.yaml - updateContextFile(clonedResp[0].clonedPath, authStore.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + updateContextFile(clonedResp[0].clonedPath, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); const subDir = params?.component?.spec?.source ? getComponentKindRepoSource(params?.component?.spec?.source)?.path || "" : ""; const subDirFullPath = join(clonedResp[0].clonedPath, subDir); if (params?.technology === "ballerina") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts index da900aeedcf..01f4863135d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/cmd-utils.ts @@ -19,7 +19,7 @@ import { CommandIds, type ComponentKind, type ExtensionName, type Organization, type Project, type UserInfo } from "@wso2/wso2-platform-core"; import { ProgressLocation, type QuickPickItem, QuickPickItemKind, type WorkspaceFolder, commands, window, workspace } from "vscode"; import { type ExtensionVariables, ext } from "../extensionVariables"; -import { authStore, waitForLogin } from "../stores/auth-store"; +import { waitForLogin } from "../auth/wso2-auth-provider"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; @@ -326,7 +326,7 @@ export async function quickPickWithLoader(params: { } export const getUserInfoForCmd = async (message: string): Promise => { - let userInfo = authStore.getState().state.userInfo; + let userInfo = ext.authProvider?.getState().state.userInfo; const extensionName = webviewStateStore.getState().state.extensionName; if (!userInfo) { const loginSelection = await window.showInformationMessage( @@ -351,7 +351,7 @@ export const getUserInfoForCmd = async (message: string): Promise { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index ed5d6337a67..e7dd6fac0a8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -20,15 +20,12 @@ import { existsSync, readFileSync } from "fs"; import * as os from "os"; import * as path from "path"; import { - ChoreoBuildPackNames, ChoreoComponentType, CommandIds, type ComponentKind, DevantScopes, type ExtensionName, type ICreateComponentCmdParams, - type Organization, - type Project, type SubmitComponentCreateReq, type WorkspaceConfig, getComponentKindRepoSource, @@ -37,16 +34,15 @@ import { getTypeOfIntegrationType, parseGitURL, } from "@wso2/wso2-platform-core"; -import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, env, window, workspace } from "vscode"; +import { type ExtensionContext, ProgressLocation, type QuickPickItem, Uri, commands, window, workspace } from "vscode"; import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; import { getGitRemotes, getGitRoot } from "../git/util"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { contextStore, waitForContextStoreToLoad } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; -import { convertFsPathToUriPath, delay, isSamePath, isSubpath, openDirectory } from "../utils"; +import { convertFsPathToUriPath, isSamePath, isSubpath, openDirectory } from "../utils"; import { showComponentDetailsView } from "../webviews/ComponentDetailsView"; import { ComponentFormView, type IComponentCreateFormParams } from "../webviews/ComponentFormView"; import { getUserInfoForCmd, isRpcActive, selectOrg, selectProjectWithCreateNew, setExtensionName } from "./cmd-utils"; @@ -221,7 +217,7 @@ export function createNewComponentCommand(context: ExtensionContext) { ); if (resp !== "Proceed") { const projectCache = dataCacheStore.getState().getProjects(selectedOrg?.handle); - updateContextFile(gitRoot, authStore.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); contextStore.getState().refreshState(); return; } @@ -362,7 +358,7 @@ export const submitCreateComponentHandler = async ({ createParams, org, project } } } else { - updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); + updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); contextStore.getState().refreshState(); } } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts index 3903645d3f0..b41e159c173 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/refresh-directory-context-cmd.ts @@ -19,7 +19,6 @@ import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, commands, window } from "vscode"; import { ext } from "../extensionVariables"; -import { authStore } from "../stores/auth-store"; import { contextStore } from "../stores/context-store"; import { isRpcActive, setExtensionName } from "./cmd-utils"; @@ -29,7 +28,7 @@ export function refreshContextCommand(context: ExtensionContext) { try { isRpcActive(ext); setExtensionName(params?.extName); - const userInfo = authStore.getState().state.userInfo; + const userInfo = ext.authProvider?.getState().state.userInfo; if (!userInfo) { throw new Error("You are not logged in. Please log in and retry."); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 589900bb5b1..239cc43e218 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -1,21 +1,3 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, commands, window } from "vscode"; import * as vscode from "vscode"; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts index 1825dba26d6..159f6a2da11 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-with-code-cmd.ts @@ -23,7 +23,6 @@ import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "../choreo-rpc/constants"; import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { isRpcActive, setExtensionName } from "./cmd-utils"; export function signInWithAuthCodeCommand(context: ExtensionContext) { @@ -45,7 +44,7 @@ export function signInWithAuthCodeCommand(context: ExtensionContext) { ext.clients.rpcClient.signInWithAuthCode(authCode).then(async (userInfo) => { if (userInfo) { const region = await ext.clients.rpcClient.getCurrentRegion(); - authStore.getState().loginSuccess(userInfo, region); + ext.authProvider?.getState().loginSuccess(userInfo, region); } }); } else { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts index ccd9bf49dfb..61e0a42b46d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-out-cmd.ts @@ -20,7 +20,6 @@ import { CommandIds } from "@wso2/wso2-platform-core"; import { type ExtensionContext, commands, window } from "vscode"; import { ext } from "../extensionVariables"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { isRpcActive } from "./cmd-utils"; export function signOutCommand(context: ExtensionContext) { @@ -28,9 +27,7 @@ export function signOutCommand(context: ExtensionContext) { commands.registerCommand(CommandIds.SignOut, async () => { try { isRpcActive(ext); - getLogger().debug("Signing out from WSO2 Platform"); - authStore.getState().logout(); - window.showInformationMessage("Successfully signed out from WSO2 Platform!"); + ext.authProvider?.getState().logout(); } catch (error: any) { getLogger().error(`Error while signing out from WSO2 Platform. ${error?.message}${error?.cause ? `\nCause: ${error.cause.message}` : ""}`); if (error instanceof Error) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts index cde75f4fc3b..c8f83763278 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/error-utils.ts @@ -21,7 +21,6 @@ import { ResponseError } from "vscode-jsonrpc"; import { ErrorCode } from "./choreo-rpc/constants"; import { ext } from "./extensionVariables"; import { getLogger } from "./logger/logger"; -import { authStore } from "./stores/auth-store"; import { webviewStateStore } from "./stores/webview-state-store"; export function handlerError(err: any) { @@ -44,20 +43,20 @@ export function handlerError(err: any) { getLogger().error("InternalError", err); break; case ErrorCode.UnauthorizedError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Unauthorized. Please sign in again."); } break; case ErrorCode.TokenNotFoundError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Token not found. Please sign in again."); } break; case ErrorCode.InvalidTokenError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Invalid token. Please sign in again."); } break; @@ -65,8 +64,8 @@ export function handlerError(err: any) { getLogger().error("ForbiddenError", err); break; case ErrorCode.RefreshTokenError: - if (authStore.getState().state?.userInfo) { - authStore.getState().logout(); + if (ext.authProvider?.getState().state?.userInfo) { + ext.authProvider?.getState().logout(); w.showErrorMessage("Failed to refresh user session. Please sign in again."); } break; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts index 93d2cf5d1eb..fc41f6841ab 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extension.ts @@ -17,7 +17,8 @@ */ import * as vscode from "vscode"; -import { type ConfigurationChangeEvent, commands, window, workspace } from "vscode"; +import { type ConfigurationChangeEvent, authentication, commands, window, workspace } from "vscode"; +import { WSO2AuthenticationProvider, WSO2_AUTH_PROVIDER_ID } from "./auth/wso2-auth-provider"; import { PlatformExtensionApi } from "./PlatformExtensionApi"; import { ChoreoRPCClient } from "./choreo-rpc"; import { initRPCServer } from "./choreo-rpc/activate"; @@ -30,7 +31,6 @@ import { ext } from "./extensionVariables"; import { getLogger, initLogger } from "./logger/logger"; import { activateChoreoMcp } from "./mcp"; import { activateStatusbar } from "./status-bar"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; import { dataCacheStore } from "./stores/data-cache-store"; import { locationStore } from "./stores/location-store"; @@ -53,15 +53,12 @@ export async function activate(context: vscode.ExtensionContext) { getLogger().info(`CLI version: ${getCliVersion()}`); // Initialize stores - await authStore.persist.rehydrate(); await contextStore.persist.rehydrate(); await dataCacheStore.persist.rehydrate(); await locationStore.persist.rehydrate(); // Set context values - authStore.subscribe(({ state }) => { - vscode.commands.executeCommand("setContext", "isLoggedIn", !!state.userInfo); - }); + // Note: authProvider will be set up below, so we'll subscribe to it in initAuth contextStore.subscribe(({ state }) => { vscode.commands.executeCommand("setContext", "isLoadingContextDirs", state.loading); vscode.commands.executeCommand("setContext", "hasSelectedProject", !!state.selected); @@ -74,9 +71,23 @@ export async function activate(context: vscode.ExtensionContext) { const rpcClient = new ChoreoRPCClient(); ext.clients = { rpcClient: rpcClient }; - await initRPCServer() + // Initialize and register authentication provider + const authProvider = new WSO2AuthenticationProvider(context.secrets); + ext.authProvider = authProvider; + context.subscriptions.push( + authentication.registerAuthenticationProvider(WSO2_AUTH_PROVIDER_ID, "WSO2 Platform", authProvider, { + supportsMultipleAccounts: false, + }), + ); + + // Subscribe to auth state changes + authProvider.subscribe(({ state }) => { + vscode.commands.executeCommand("setContext", "isLoggedIn", !!state.userInfo); + }); + + await initRPCServer(); await ext.clients.rpcClient.init(); - authStore.getState().initAuth(); + authProvider.getState().initAuth(); continueCreateComponent(); if (ext.isChoreoExtInstalled) { addTerminalHandlers(); @@ -116,7 +127,7 @@ function registerPreInitHandlers(): any { ); if (selection === "Restart Now") { if (affectsConfiguration("WSO2.WSO2-Platform.Advanced.ChoreoEnvironment")) { - authStore.getState().logout(); + ext.authProvider?.getState().logout(); } commands.executeCommand("workbench.action.reloadWindow"); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts index ac693995485..8002472c69e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/extensionVariables.ts @@ -18,6 +18,7 @@ import type { GetCliRpcResp } from "@wso2/wso2-platform-core"; import { type ExtensionContext, type StatusBarItem, extensions } from "vscode"; +import type { WSO2AuthenticationProvider } from "./auth/wso2-auth-provider"; import type { PlatformExtensionApi } from "./PlatformExtensionApi"; import type { ChoreoRPCClient } from "./choreo-rpc"; @@ -30,6 +31,7 @@ export class ExtensionVariables { public choreoEnv: string; public isChoreoExtInstalled: boolean; public isDevantCloudEditor: boolean; + public authProvider?: WSO2AuthenticationProvider; public constructor() { this.choreoEnv = "prod"; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts b/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts index 9c7a9b95a61..fa7baa8059e 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/status-bar.ts @@ -1,6 +1,6 @@ import { type AuthState, CommandIds, type ContextStoreState, type WebviewState } from "@wso2/wso2-platform-core"; import { type ExtensionContext, StatusBarAlignment, type StatusBarItem, window } from "vscode"; -import { authStore } from "./stores/auth-store"; +import { ext } from "./extensionVariables"; import { contextStore } from "./stores/context-store"; import { webviewStateStore } from "./stores/webview-state-store"; @@ -12,14 +12,14 @@ export function activateStatusbar({ subscriptions }: ExtensionContext) { subscriptions.push(statusBarItem); let webviewState: WebviewState = webviewStateStore.getState()?.state; - let authState: AuthState | null = authStore.getState()?.state; + let authState: AuthState | undefined = ext.authProvider?.getState()?.state; let contextStoreState: ContextStoreState | null = contextStore.getState()?.state; webviewStateStore.subscribe((state) => { webviewState = state.state; updateStatusBarItem(webviewState, authState, contextStoreState); }); - authStore.subscribe((state) => { + ext.authProvider?.subscribe((state) => { authState = state.state; updateStatusBarItem(webviewState, authState, contextStoreState); }); diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts deleted file mode 100644 index 923cbdda230..00000000000 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/auth-store.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import type { AuthState, Organization, UserInfo } from "@wso2/wso2-platform-core"; -import { createStore } from "zustand"; -import { persist } from "zustand/middleware"; -import { ext } from "../extensionVariables"; -import { contextStore } from "./context-store"; -import { dataCacheStore } from "./data-cache-store"; -import { getGlobalStateStore } from "./store-utils"; - -interface AuthStore { - state: AuthState; - resetState: () => void; - loginSuccess: (userInfo: UserInfo, region: "US" | "EU") => void; - logout: () => Promise; - initAuth: () => Promise; -} - -const initialState: AuthState = { userInfo: null, region: "US" }; - -export const authStore = createStore( - persist( - (set, get) => ({ - state: initialState, - resetState: () => set(() => ({ state: initialState })), - loginSuccess: (userInfo, region) => { - dataCacheStore.getState().setOrgs(userInfo.organizations); - set(({ state }) => ({ state: { ...state, userInfo, region } })); - contextStore.getState().refreshState(); - }, - logout: async () => { - get().resetState(); - ext.clients.rpcClient.signOut().catch(() => { - // ignore error - }); - }, - initAuth: async () => { - try { - const userInfo = await ext.clients.rpcClient.getUserInfo(); - if (userInfo) { - const region = await ext.clients.rpcClient.getCurrentRegion(); - get().loginSuccess(userInfo, region); - const contextStoreState = contextStore.getState().state; - if (contextStoreState.selected?.org) { - ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); - } - } else { - get().logout(); - } - } catch (err) { - get().logout(); - } - }, - }), - getGlobalStateStore("auth-zustand-storage"), - ), -); - -export const waitForLogin = async (): Promise => { - return new Promise((resolve) => { - authStore.subscribe(({ state }) => { - if (state.userInfo) { - resolve(state.userInfo); - } - }); - }); -}; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts index 2120c34ceb9..ff59fef387d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/context-store.ts @@ -37,7 +37,6 @@ import { persist } from "zustand/middleware"; import { ext } from "../extensionVariables"; import { getGitRemotes, getGitRoot } from "../git/util"; import { isSamePath, isSubpath } from "../utils"; -import { authStore } from "./auth-store"; import { dataCacheStore } from "./data-cache-store"; import { locationStore } from "./location-store"; import { getWorkspaceStateStore } from "./store-utils"; @@ -60,7 +59,7 @@ export const contextStore = createStore( resetState: () => set(() => ({ state: initialState })), refreshState: async () => { try { - if (authStore.getState().state?.userInfo) { + if (ext.authProvider?.getState().state?.userInfo) { set(({ state }) => ({ state: { ...state, loading: true } })); let items = await getAllContexts(get().state?.items); let selected = await getSelected(items, get().state?.selected); @@ -153,7 +152,7 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche } else if (previousItems?.[key]?.org && previousItems?.[key].project) { contextItems[key] = { ...previousItems?.[key], contextDirs: [contextDir] }; } else { - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const matchingOrg = userOrgs?.find((item) => item.handle === contextItem.org); const projectsOfOrg = dataCacheStore.getState().getProjects(contextItem.org); @@ -199,7 +198,7 @@ const getAllContexts = async (previousItems: { [key: string]: ContextItemEnriche const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSelected?: ContextItemEnriched) => { if (ext.isDevantCloudEditor && process.env.CLOUD_INITIAL_ORG_ID && process.env.CLOUD_INITIAL_PROJECT_ID) { // Give priority to project provided as env variable, when running in the cloud editor - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const matchingOrg = userOrgs?.find( (item) => item.uuid === process.env.CLOUD_INITIAL_ORG_ID || item.id?.toString() === process.env.CLOUD_INITIAL_ORG_ID, ); @@ -261,7 +260,7 @@ const getSelected = async (items: { [key: string]: ContextItemEnriched }, prevSe }; const getEnrichedContexts = async (items: { [key: string]: ContextItemEnriched }) => { - const userOrgs = authStore.getState().state.userInfo?.organizations; + const userOrgs = ext.authProvider?.getState().state.userInfo?.organizations; const orgsSet = new Set(); Object.values(items).forEach((item) => { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts index d57723e2db3..c4e35b6b9a7 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/stores/data-cache-store.ts @@ -20,7 +20,6 @@ import type { CommitHistory, ComponentKind, DataCacheState, Environment, Organiz import { createStore } from "zustand"; import { persist } from "zustand/middleware"; import { ext } from "../extensionVariables"; -import { authStore } from "./auth-store"; import { getGlobalStateStore } from "./store-utils"; interface DataCacheStore { @@ -161,7 +160,7 @@ export const dataCacheStore = createStore( ); const getRootKey = (orgHandle: string) => { - const region = authStore.getState().state.region; + const region = ext.authProvider?.getState().state.region; const env = ext.choreoEnv; let orgRegionHandle = `${region}-${orgHandle}`; if (env !== "prod") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts index 4963fea8654..f1763451667 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/tarminal-handlers.ts @@ -20,10 +20,9 @@ import { CommandIds, type ComponentKind } from "@wso2/wso2-platform-core"; import type vscode from "vscode"; import { commands, window, workspace } from "vscode"; import { getChoreoExecPath } from "./choreo-rpc/cli-install"; -import { authStore } from "./stores/auth-store"; import { contextStore } from "./stores/context-store"; -import { dataCacheStore } from "./stores/data-cache-store"; import { delay, getSubPath } from "./utils"; +import { ext } from "./extensionVariables"; export class ChoreoConfigurationProvider implements vscode.DebugConfigurationProvider { resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration): vscode.DebugConfiguration | undefined { @@ -65,7 +64,7 @@ export function addTerminalHandlers() { let cliCommand = e.name.split("[choreo-shell]").pop()?.replaceAll(")", ""); const terminalPath = (e.creationOptions as any)?.cwd; const rpcPath = getChoreoExecPath(); - const userInfo = authStore.getState().state?.userInfo; + const userInfo = ext.authProvider?.getState().state?.userInfo; if (terminalPath) { if (!e.name?.includes("--project")) { window diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts index 35b4aa7a182..60a1b700c3c 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts @@ -16,7 +16,7 @@ * under the License. */ -import { authStore } from "../stores/auth-store"; +import { ext } from "../extensionVariables"; import { getTelemetryReporter } from "./telemetry"; // export async function sendProjectTelemetryEvent(eventName: string, properties?: { [key: string]: string; }, measurements?: { [key: string]: number; }) { @@ -46,8 +46,8 @@ export function sendTelemetryException(error: Error, properties?: { [key: string // Create common properties for all events export function getCommonProperties(): { [key: string]: string } { return { - idpId: authStore.getState().state?.userInfo?.userId!, + idpId: ext.authProvider?.getState().state?.userInfo?.userId!, // check if the email ends with "@wso2.com" - isWSO2User: authStore.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", + isWSO2User: ext.authProvider?.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", }; } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts index b413bfe0b4c..592804e8f49 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts @@ -36,7 +36,6 @@ import { updateContextFile } from "./cmds/create-directory-context-cmd"; import { ext } from "./extensionVariables"; import { getGitRemotes, getGitRoot } from "./git/util"; import { getLogger } from "./logger/logger"; -import { authStore } from "./stores/auth-store"; import { contextStore, getContextKey, waitForContextStoreToLoad } from "./stores/context-store"; import { dataCacheStore } from "./stores/data-cache-store"; import { locationStore } from "./stores/location-store"; @@ -81,9 +80,9 @@ export function activateURIHandlers() { contextStore.getState().resetState(); } } - const region = await ext.clients.rpcClient.getCurrentRegion(); - authStore.getState().loginSuccess(userInfo, region); - window.showInformationMessage(`Successfully signed into ${extName}`); + const region = await ext.clients.rpcClient.getCurrentRegion(); + await ext.authProvider?.getState().loginSuccess(userInfo, region); + window.showInformationMessage(`Successfully signed into ${extName}`); } } catch (error: any) { if (!(error instanceof ResponseError) || ![ErrorCode.NoOrgsAvailable, ErrorCode.NoAccountAvailable].includes(error.code)) { @@ -265,7 +264,7 @@ const switchContextAndOpenDir = async (selectedPath: string, org: Organization, return; } const projectCache = dataCacheStore.getState().getProjects(org?.handle); - const contextFilePath = updateContextFile(gitRoot, authStore.getState().state.userInfo!, project, org, projectCache); + const contextFilePath = updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSamePath(item.uri?.fsPath, selectedPath)); if (isWithinWorkspace) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts index 762dc2973fa..3e181a7a8ea 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/webviews/WebviewRPC.ts @@ -123,7 +123,6 @@ import { ext } from "../extensionVariables"; import { initGit } from "../git/main"; import { getGitHead, getGitRemotes, getGitRoot, hasDirtyRepo, removeCredentialsFromGitURL } from "../git/util"; import { getLogger } from "../logger/logger"; -import { authStore } from "../stores/auth-store"; import { contextStore } from "../stores/context-store"; import { dataCacheStore } from "../stores/data-cache-store"; import { webviewStateStore } from "../stores/webview-state-store"; @@ -132,11 +131,11 @@ import { getConfigFileDrifts, getNormalizedPath, getSubPath, goTosource, readLoc // Register handlers function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | WebviewView) { - authStore.subscribe((store) => messenger.sendNotification(AuthStoreChangedNotification, BROADCAST, store.state)); + ext.authProvider?.subscribe((store) => messenger.sendNotification(AuthStoreChangedNotification, BROADCAST, store.state)); webviewStateStore.subscribe((store) => messenger.sendNotification(WebviewStateChangedNotification, BROADCAST, store.state)); contextStore.subscribe((store) => messenger.sendNotification(ContextStoreChangedNotification, BROADCAST, store.state)); - messenger.onRequest(GetAuthState, () => authStore.getState().state); + messenger.onRequest(GetAuthState, () => ext.authProvider?.getState().state); messenger.onRequest(GetWebviewStoreState, async () => webviewStateStore.getState().state); messenger.onRequest(GetContextState, async () => contextStore.getState().state); @@ -399,7 +398,7 @@ function registerWebviewRPCHandlers(messenger: Messenger, view: WebviewPanel | W rmSync(join(params.componentDir, ".choreo", "component-config.yaml")); } - const org = authStore?.getState().state?.userInfo?.organizations?.find((item) => item.uuid === params.marketplaceItem?.organizationId); + const org = ext.authProvider?.getState().state?.userInfo?.organizations?.find((item) => item.uuid === params.marketplaceItem?.organizationId); if (!org) { return; } From fc1cccbf2870b20b1abd1b26bf4eb6000e8ba985 Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 00:25:38 +0530 Subject: [PATCH 053/102] Add license header --- .../src/cmds/sign-in-cmd.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 239cc43e218..589900bb5b1 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -1,3 +1,21 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { CommandIds, type ICmdParamsBase } from "@wso2/wso2-platform-core"; import { type ExtensionContext, ProgressLocation, commands, window } from "vscode"; import * as vscode from "vscode"; From 607be11ae2c5a5aa83fcc80d7834853e927e301c Mon Sep 17 00:00:00 2001 From: gigara Date: Sat, 13 Dec 2025 10:14:29 +0530 Subject: [PATCH 054/102] Enhance getAuthState method to provide default user info and region --- .../wso2-platform-extension/src/PlatformExtensionApi.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts index 2db5f700428..a3a2ae96902 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/PlatformExtensionApi.ts @@ -31,7 +31,7 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { ?.filter((item) => !!item) as ComponentKind[]) ?? [] } - public getAuthState = () => ext.authProvider?.getState().state; + public getAuthState = () => ext.authProvider?.getState().state ?? { userInfo: null, region: "US" as const }; public isLoggedIn = () => !!ext.authProvider?.getState().state?.userInfo; public getDirectoryComponents = (fsPath: string) => this.getComponentsOfDir(fsPath, contextStore.getState().state?.components); public localRepoHasChanges = (fsPath: string) => hasDirtyRepo(fsPath, ext.context, ["context.yaml"]); @@ -43,8 +43,8 @@ export class PlatformExtensionApi implements IWso2PlatformExtensionAPI { public getDevantConsoleUrl = async() => (await ext.clients.rpcClient.getConfigFromCli()).devantConsoleUrl; // Auth state subscriptions - public subscribeAuthState = (callback: (state: AuthState)=>void) => ext.authProvider?.subscribe((state)=>callback(state.state)); - public subscribeIsLoggedIn = (callback: (isLoggedIn: boolean)=>void) => ext.authProvider?.subscribe((state)=>callback(!!state.state?.userInfo)); + public subscribeAuthState = (callback: (state: AuthState)=>void) => ext.authProvider?.subscribe((state)=>callback(state.state)) ?? (() => {}); + public subscribeIsLoggedIn = (callback: (isLoggedIn: boolean)=>void) => ext.authProvider?.subscribe((state)=>callback(!!state.state?.userInfo)) ?? (() => {}); // Context state subscriptions public subscribeContextState = (callback: (state: ContextItemEnriched | undefined)=>void) => contextStore.subscribe((state)=>callback(state.state?.selected)); From f3f0e8d2e09d2adac3456a151f716f502510a5a8 Mon Sep 17 00:00:00 2001 From: Chamupathi Gigara Hettige Date: Sat, 13 Dec 2025 10:18:57 +0530 Subject: [PATCH 055/102] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/cmds/clone-project-cmd.ts | 6 +++++- .../src/cmds/create-component-cmd.ts | 17 ++++++++++++++--- .../src/telemetry/utils.ts | 2 +- .../wso2-platform-extension/src/uri-handlers.ts | 7 ++++++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts index 18ebf65df0a..4d7178bea0d 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/clone-project-cmd.ts @@ -167,7 +167,11 @@ export function cloneRepoCommand(context: ExtensionContext) { ]); // set context.yaml - updateContextFile(clonedResp[0].clonedPath, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + const userInfo = ext.authProvider?.getState()?.state?.userInfo; + if (!userInfo) { + throw new Error("User information is not available. Please ensure you are logged in."); + } + updateContextFile(clonedResp[0].clonedPath, userInfo, selectedProject, selectedOrg, projectCache); const subDir = params?.component?.spec?.source ? getComponentKindRepoSource(params?.component?.spec?.source)?.path || "" : ""; const subDirFullPath = join(clonedResp[0].clonedPath, subDir); if (params?.technology === "ballerina") { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts index e7dd6fac0a8..a65bd5732ed 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/create-component-cmd.ts @@ -217,7 +217,13 @@ export function createNewComponentCommand(context: ExtensionContext) { ); if (resp !== "Proceed") { const projectCache = dataCacheStore.getState().getProjects(selectedOrg?.handle); - updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, selectedProject, selectedOrg, projectCache); + const authProvider = ext.authProvider; + const userInfo = authProvider?.getState().state.userInfo; + if (!authProvider || !userInfo) { + window.showErrorMessage("User information is not available. Please sign in and try again."); + return; + } + updateContextFile(gitRoot, userInfo, selectedProject, selectedOrg, projectCache); contextStore.getState().refreshState(); return; } @@ -358,8 +364,13 @@ export const submitCreateComponentHandler = async ({ createParams, org, project } } } else { - updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); - contextStore.getState().refreshState(); + const userInfo = ext.authProvider?.getState().state.userInfo; + if (userInfo) { + updateContextFile(gitRoot, userInfo, project, org, projectCache); + contextStore.getState().refreshState(); + } else { + getLogger().error("Cannot update context file: userInfo is undefined."); + } } } } catch (err) { diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts index 60a1b700c3c..8a5f79e8407 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/telemetry/utils.ts @@ -46,7 +46,7 @@ export function sendTelemetryException(error: Error, properties?: { [key: string // Create common properties for all events export function getCommonProperties(): { [key: string]: string } { return { - idpId: ext.authProvider?.getState().state?.userInfo?.userId!, + idpId: ext.authProvider?.getState().state?.userInfo?.userId ?? "", // check if the email ends with "@wso2.com" isWSO2User: ext.authProvider?.getState().state?.userInfo?.userEmail?.endsWith("@wso2.com") ? "true" : "false", }; diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts index 592804e8f49..3aa77705b58 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/uri-handlers.ts @@ -264,7 +264,12 @@ const switchContextAndOpenDir = async (selectedPath: string, org: Organization, return; } const projectCache = dataCacheStore.getState().getProjects(org?.handle); - const contextFilePath = updateContextFile(gitRoot, ext.authProvider?.getState().state.userInfo!, project, org, projectCache); + const userInfo = ext.authProvider?.getState().state.userInfo; + if (!userInfo) { + window.showErrorMessage("User information is not available. Please sign in and try again."); + return; + } + const contextFilePath = updateContextFile(gitRoot, userInfo, project, org, projectCache); const isWithinWorkspace = workspace.workspaceFolders?.some((item) => isSamePath(item.uri?.fsPath, selectedPath)); if (isWithinWorkspace) { From c0a7d9f13147aea0de431b3140b23db09ff227ea Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Sat, 13 Dec 2025 16:24:00 +0530 Subject: [PATCH 056/102] Add bi api spec icon --- .../font-wso2-vscode/src/icons/bi-api-spec.svg | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 workspaces/common-libs/font-wso2-vscode/src/icons/bi-api-spec.svg diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-api-spec.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-api-spec.svg new file mode 100644 index 00000000000..20be541d2ba --- /dev/null +++ b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-api-spec.svg @@ -0,0 +1,18 @@ + + \ No newline at end of file From e10c66885b16e29226ec8a33b66f5edd3c4e8452 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Sat, 13 Dec 2025 16:24:34 +0530 Subject: [PATCH 057/102] Fix connector searching functinality --- .../Connection/AddConnectionPopup/index.tsx | 189 +++++++++++++----- 1 file changed, 136 insertions(+), 53 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 5b464585da3..7361ff99b58 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -322,7 +322,31 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { .then(async (model) => { console.log(">>> bi searched connectors", model); console.log(">>> bi filtered connectors", model.categories); - setConnectors(model.categories); + + // When searching, the API might return a flat array of connectors instead of categories + // Check if categories exist and have the proper structure (with items arrays) + let normalizedCategories: Category[] = []; + + if (model.categories && Array.isArray(model.categories)) { + // Check if the first item is a category (has items) or a connector (has codedata) + const firstItem = model.categories[0]; + if (firstItem && "items" in firstItem && Array.isArray(firstItem.items)) { + // Proper category structure - use as is + normalizedCategories = model.categories; + } else if (firstItem && "codedata" in firstItem) { + // Flat array of connectors - wrap in a category + normalizedCategories = [{ + metadata: { + label: "Search Results", + description: "" + }, + items: model.categories as unknown as AvailableNode[] + }]; + } + } + + console.log(">>> normalized categories for search", normalizedCategories); + setConnectors(normalizedCategories); }) .finally(() => { setIsSearching(false); @@ -399,9 +423,21 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { if (!category || !category.items) { return category; } + // Only apply client-side filtering if there's no search text (backend already filtered) + if (searchText) { + // When searching, show all items from backend results + return category; + } category.items = filterItems(category.items); return category; }).filter((category) => { + if (!category) { + return false; + } + // When searching, show all categories that have items + if (searchText) { + return category.items && category.items.length > 0; + } // Map filterType to category labels similar to ConnectorView // "Standard" maps to "StandardLibrary" (exclude Local and CurrentOrg) // "Organization" maps to "CurrentOrg" @@ -471,6 +507,47 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { }) }; + const getConnectorCreationOptions = () => { + if (!searchText || searchText.trim() === "") { + // No search - show both options + return { showApiSpec: true, showDatabase: true }; + } + + const lowerSearchText = searchText.toLowerCase().trim(); + + // Database-related keywords + const databaseKeywords = [ + "database", "db", "mysql", "postgresql", "postgres", "mssql", "sql server", + "sqlserver", "oracle", "sqlite", "mariadb", "mongodb", "cassandra", + "redis", "dynamodb", "table", "schema", "query", "sql" + ]; + + // API-related keywords + const apiKeywords = [ + "api", "http", "https", "rest", "graphql", "soap", "wsdl", "openapi", + "swagger", "endpoint", "service", "client", "request", "response", + "json", "xml", "yaml", "websocket", "rpc" + ]; + + const isDatabaseSearch = databaseKeywords.some(keyword => lowerSearchText.includes(keyword)); + const isApiSearch = apiKeywords.some(keyword => lowerSearchText.includes(keyword)); + + // If search matches database keywords, show only database option + if (isDatabaseSearch && !isApiSearch) { + return { showApiSpec: false, showDatabase: true }; + } + + // If search matches API keywords, show only API spec option + if (isApiSearch && !isDatabaseSearch) { + return { showApiSpec: true, showDatabase: false }; + } + + // If both or neither match, show both options + return { showApiSpec: true, showDatabase: true }; + }; + + const connectorOptions = getConnectorCreationOptions(); + return ( <> @@ -498,58 +575,64 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { /> -
- CREATE NEW CONNECTOR - - - - - - - Connect via API Specification - - Import an OpenAPI or WSDL file to create a connector - - - - OpenAPI - - - WSDL - - - - - - - - - - - - - Connect to a Database - - Enter credentials to introspect and discover database tables - - - - MySQL - - - MSSQL - - - PostgreSQL - - - - - - - - -
+ {(connectorOptions.showApiSpec || connectorOptions.showDatabase) && ( +
+ CREATE NEW CONNECTOR + + {connectorOptions.showApiSpec && ( + + + + + + Connect via API Specification + + Import an OpenAPI or WSDL file to create a connector + + + + OpenAPI + + + WSDL + + + + + + + + )} + {connectorOptions.showDatabase && ( + + + + + + Connect to a Database + + Enter credentials to introspect and discover database tables + + + + MySQL + + + MSSQL + + + PostgreSQL + + + + + + + + )} + +
+ )}
From e8e449e60b46948fcec9a518588e3717c986bc3f Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 13 Dec 2025 18:44:47 +0530 Subject: [PATCH 058/102] Update button label in ErrorScreen component for clarity --- .../src/components/DataMapper/ErrorBoundary/Error/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index f8d60afc17a..094da0ba681 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -62,7 +62,7 @@ export default function ErrorScreen(props: ErrorScreenProps) {
From 88e23347568c854ec0c80f2e97787d9b2f13e189 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 13 Dec 2025 19:49:48 +0530 Subject: [PATCH 059/102] Refactor goToSource function to goToSource of commonRpcClient instead of executeCommand --- .../src/views/DataMapper/index.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx index df282d8ae05..4eca03205b5 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/index.tsx @@ -41,9 +41,16 @@ export function DataMapper(props: DataMapperProps) { const {rpcClient} = useRpcContext(); const goToSource = () => { - rpcClient.getCommonRpcClient()?.executeCommand({ - commands: ["ballerina.show.source"] - }); + const lineRange = props.codedata.lineRange; + if (lineRange) { + const position: NodePosition = { + startLine: lineRange.startLine.line, + startColumn: lineRange.startLine.offset, + endLine: lineRange.endLine.line, + endColumn: lineRange.endLine.offset, + }; + rpcClient.getCommonRpcClient().goToSource({ position }); + } }; const onClose = props.onClose || (() => { From 099f1bc98eb4dd9837a6630c11d8d0cf8eef9d41 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 13 Dec 2025 19:51:02 +0530 Subject: [PATCH 060/102] Increase gap between action buttons in Error component for improved spacing --- .../src/components/DataMapper/ErrorBoundary/Error/style.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts index 51dc48d84d5..2b27e0dfb8c 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/style.ts @@ -67,7 +67,7 @@ export const useStyles = () => ({ actionButtons: css({ display: 'flex', - gap: '4px', + gap: '8px', alignItems: 'center', alignSelf: 'flex-end' }), From 7294d0dda8333f7dbadcae0c42091345b5a4f062 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Sat, 13 Dec 2025 20:08:23 +0530 Subject: [PATCH 061/102] Remove unnecessary empty line in actionButtons of ErrorScreen component --- .../src/components/DataMapper/ErrorBoundary/Error/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx index 094da0ba681..2792ed58a91 100644 --- a/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ b/workspaces/ballerina/data-mapper/src/components/DataMapper/ErrorBoundary/Error/index.tsx @@ -55,7 +55,6 @@ export default function ErrorScreen(props: ErrorScreenProps) {

- )} + {onSubmit && !hideSaveButton && footerActionButton && ( + + + {isValidatingForm ? ( + Validating... + ) : isSaving && savingButton === 'save' ? ( + {submitText || "Saving..."} + ) : ( + submitText || "Save" + )} + + + )} ); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index 61f323321ef..fcbf9a7e56d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -91,17 +91,35 @@ const StepperContainer = styled.div` border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; `; -const ContentContainer = styled.div` +const ContentContainer = styled.div<{ hasFooterButton?: boolean }>` flex: 1; - overflow-y: auto; + display: flex; + flex-direction: column; + overflow: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "hidden" : "auto"}; padding: 24px 32px; - background: ${ThemeColors.SURFACE_DIM}; + padding-bottom: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "0" : "24px"}; + min-height: 0; +`; + +const FooterContainer = styled.div` + position: sticky; + bottom: 0; + padding: 20px 32px; + display: flex; + justify-content: center; + align-items: center; + z-index: 10; `; -const StepContent = styled.div` +const StepContent = styled.div<{ fillHeight?: boolean }>` display: flex; flex-direction: column; gap: 20px; + ${(props: { fillHeight?: boolean }) => props.fillHeight && ` + flex: 1; + min-height: 0; + height: 100%; + `} `; const SectionTitle = styled(Typography)` @@ -120,7 +138,7 @@ const SectionSubtitle = styled(Typography)` const FormSection = styled.div` display: flex; flex-direction: column; - gap: 18px; + gap: 20px; background: transparent; border: none; padding: 0; @@ -129,7 +147,6 @@ const FormSection = styled.div` const FormField = styled.div` display: flex; flex-direction: column; - gap: 8px; `; const UploadCard = styled.div<{ hasFile?: boolean }>` @@ -189,7 +206,6 @@ const UploadSubtitle = styled(Typography)` const ActionButton = styled(Button)` - margin-top: 12px; width: 100% !important; min-width: 0 !important; display: flex !important; @@ -295,7 +311,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { const [selectedFlowNode, setSelectedFlowNode] = useState(undefined); const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); - const steps = useMemo(() => ["Import API Spec", "Create Connection"], []); + const steps = useMemo(() => ["Import API Specification", "Create Connection"], []); const apiSpecOptions = useMemo( () => [ @@ -484,7 +500,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { return ( <> - + ); @@ -493,7 +509,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { const renderImportStep = () => ( - + Connector Configuration @@ -511,19 +527,21 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { /> + + Connector Name + + + Name of the connector module to be generated + setConnectorName(value)} placeholder="Enter connector name" /> - - Name of the connector module to be generated - - + Import Specification File - - {isSavingConnector ? "Saving..." : "Save Connector"} - ); @@ -556,16 +567,17 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { const renderConnectionStep = () => { if (selectedFlowNode) { return ( - + setUpdatedExpressionField(undefined)} isPullingConnector={isSavingConnection} + footerActionButton={true} /> ); @@ -605,7 +617,19 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { {renderStepper()} - {renderStepContent()} + {renderStepContent()} + {currentStep === 0 && ( + + + {isSavingConnector ? "Saving..." : "Save Connector"} + + + )} ); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx index 64826d2127a..c577a7fc5aa 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigView/index.tsx @@ -26,9 +26,17 @@ import { SidePanelView } from "../../FlowDiagram/PanelManager"; import { ConnectionKind } from "../../../../components/ConnectionSelector"; import { FormSubmitOptions } from "../../FlowDiagram"; -const Container = styled.div` +const Container = styled.div<{ footerActionButton?: boolean }>` max-width: 800px; - height: calc(100% - 32px); + ${(props: { footerActionButton?: boolean }) => props.footerActionButton ? ` + flex: 1; + min-height: 0; + height: 100%; + display: flex; + flex-direction: column; + ` : ` + height: calc(100% - 32px); + `} `; export interface SidePanelProps { @@ -58,6 +66,7 @@ interface ConnectionConfigViewProps { isActiveSubPanel?: boolean; isPullingConnector?: boolean; navigateToPanel?: (targetPanel: SidePanelView, connectionKind?: ConnectionKind) => void; + footerActionButton?: boolean; // Render save button as footer action button } export function ConnectionConfigView(props: ConnectionConfigViewProps) { @@ -72,6 +81,7 @@ export function ConnectionConfigView(props: ConnectionConfigViewProps) { submitText, isSaving, navigateToPanel, + footerActionButton, } = props; const { rpcClient } = useRpcContext(); const [targetLineRange, setTargetLineRange] = useState(); @@ -101,7 +111,7 @@ export function ConnectionConfigView(props: ConnectionConfigViewProps) { }, [fileName, selectedNode, rpcClient]); return ( - + {targetLineRange && ( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx index ebf0414800f..1aeec2dbc08 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx @@ -197,10 +197,21 @@ const TagText = styled(Typography)` margin: 0; `; -const ConfigContent = styled.div` +const ConfigContent = styled.div<{ hasFooterButton?: boolean }>` flex: 1; - overflow-y: auto; - padding: 0 32px 24px 32px; + display: flex; + flex-direction: column; + overflow: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "hidden" : "auto"}; + padding: 0 32px ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "0" : "24px"} 32px; + min-height: 0; +`; + +const FormContainer = styled.div<{}>` + flex: 1; + min-height: 0; + height: 100%; + display: flex; + flex-direction: column; `; const ConnectionDetailsSection = styled.div` @@ -498,7 +509,7 @@ export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopup - + {pullingStatus && ( {pullingStatus === PullingStatus.FETCHING && ( @@ -559,17 +570,20 @@ export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopup Configure your connection settings - + + + ); })()} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index fd14716b649..4f672669d3d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -88,16 +88,35 @@ const StepperContainer = styled.div` border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; `; -const ContentContainer = styled.div` +const ContentContainer = styled.div<{ hasFooterButton?: boolean }>` flex: 1; - overflow-y: auto; + display: flex; + flex-direction: column; + overflow: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "hidden" : "auto"}; padding: 24px 32px; + padding-bottom: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "0" : "24px"}; + min-height: 0; `; -const StepContent = styled.div` +const StepContent = styled.div<{ fillHeight?: boolean }>` display: flex; flex-direction: column; gap: 24px; + ${(props: { fillHeight?: boolean }) => props.fillHeight && ` + flex: 1; + min-height: 0; + height: 100%; + `} +`; + +const FooterContainer = styled.div` + position: sticky; + bottom: 0; + padding: 20px 32px; + display: flex; + justify-content: center; + align-items: center; + z-index: 10; `; const SectionTitle = styled(Typography)` @@ -507,7 +526,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { switch (currentStep) { case 0: return ( - +
Database Credentials @@ -569,19 +588,13 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { onTextChange={(value) => handleCredentialsChange("password", value)} /> - - {isIntrospecting ? "Connecting..." : "Connect & Introspect Database"} - ); case 1: return ( - +
Select Tables @@ -612,18 +625,12 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { Select All - - Continue to Connection Details - ); case 2: return ( - +
Connection Details @@ -684,12 +691,6 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - - {isSaving ? "Saving..." : "Save Connection"} - ); @@ -719,7 +720,43 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - {renderStepContent()} + {renderStepContent()} + {currentStep === 0 && ( + + + {isIntrospecting ? "Connecting..." : "Connect & Introspect Database"} + + + )} + {currentStep === 1 && ( + + + Continue to Connection Details + + + )} + {currentStep === 2 && ( + + + {isSaving ? "Saving..." : "Save Connection"} + + + )} ); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx index d2ac8d8deb3..11aec401b33 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx @@ -168,10 +168,13 @@ const ConnectorInfoDescription = styled(Typography)` `; -const ContentContainer = styled.div` +const ContentContainer = styled.div<{ hasFooterButton?: boolean }>` flex: 1; - overflow-y: auto; + display: flex; + flex-direction: column; + overflow: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "hidden" : "auto"}; padding: 24px 32px; + padding-bottom: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "0" : "24px"}; `; const LoadingContainer = styled.div` @@ -375,7 +378,7 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { - + diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx index fbd8332cd34..bfc51e12328 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx @@ -143,6 +143,7 @@ interface FormProps { navigateToPanel?: (panel: SidePanelView, connectionKind?: ConnectionKind) => void; fieldPriority?: Record; // Map of field keys to priority numbers (lower = rendered first) fieldOverrides?: Record>; + footerActionButton?: boolean; // Render save button as footer action button } // Styled component for the action button description @@ -217,6 +218,7 @@ export const FormGenerator = forwardRef(func onChange, injectedComponents, fieldPriority, + footerActionButton, } = props; const { rpcClient } = useRpcContext(); @@ -1408,6 +1410,7 @@ export const FormGenerator = forwardRef(func visualizableField={visualizableField} infoLabel={infoLabel} disableSaveButton={disableSaveButton} + footerActionButton={footerActionButton} actionButton={actionButton} recordTypeFields={recordTypeFields} isInferredReturnType={!!node.codedata?.inferredReturnType} @@ -1525,6 +1528,7 @@ export const FormGenerator = forwardRef(func visualizableField={visualizableField} infoLabel={infoLabel} disableSaveButton={disableSaveButton} + footerActionButton={footerActionButton} actionButton={actionButton} recordTypeFields={recordTypeFields} isInferredReturnType={!!node.codedata?.inferredReturnType} From b5a3e7e106d376adaf9b1f2363414eab319bc703 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 12 Dec 2025 19:27:41 +0530 Subject: [PATCH 065/102] Fix agent tool diagnostics for undefined symbol --- .../src/rpc-types/ai-agent/interfaces.ts | 16 ++++++++ .../ballerina-visualizer/src/utils/bi.tsx | 38 +++++++++++++++++-- .../views/BI/AIChatAgent/AIAgentSidePanel.tsx | 22 ++++++++++- .../views/BI/Forms/FormGeneratorNew/index.tsx | 13 +++++-- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/interfaces.ts index add05a31504..0b47ae9eb0f 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/ai-agent/interfaces.ts @@ -47,6 +47,22 @@ export interface ToolParameterFormValues { } export interface ToolParameterItem { + id: number; + icon: string; + key: string; + value: string; + identifierEditable: boolean; + identifierRange?: { + fileName: string; + startLine: { + line: number; + offset: number; + }; + endLine: { + line: number; + offset: number; + }; + }; formValues: ToolParameterFormValues; } diff --git a/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx b/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx index e19d948b6fd..75717de3322 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/utils/bi.tsx @@ -481,7 +481,7 @@ export function enrichFormTemplatePropertiesWithValues( ) { // Copy the value from formProperties to formTemplateProperties enrichedFormTemplateProperties[key as NodePropertyKey].value = formProperty.value; - + if (formProperty.hasOwnProperty('editable')) { enrichedFormTemplateProperties[key as NodePropertyKey].editable = formProperty.editable; enrichedFormTemplateProperties[key as NodePropertyKey].codedata = formProperty?.codedata; @@ -489,7 +489,7 @@ export function enrichFormTemplatePropertiesWithValues( if (formProperty.diagnostics) { enrichedFormTemplateProperties[key as NodePropertyKey].diagnostics = formProperty.diagnostics; - } + } } } } @@ -752,7 +752,7 @@ export function convertToVisibleTypes(types: VisibleTypeItem[], isFetchingTypesF export function convertRecordTypeToCompletionItem(type: Type): CompletionItem { const label = type?.name ?? ""; - const value = label; + const value = label; const kind = "struct"; const description = type?.metadata?.description; const labelDetails = (() => { @@ -1042,6 +1042,38 @@ export function filterUnsupportedDiagnostics(diagnostics: Diagnostic[]): Diagnos }); } +/** + * Filter out "undefined symbol" diagnostics when the symbol is a known Tool Input parameter + * @param diagnostics - Array of diagnostics to filter + * @param toolInputParameterNames - Array of Tool Input parameter names to exclude from diagnostics + * @returns Filtered diagnostics array + */ +export function filterToolInputSymbolDiagnostics( + diagnostics: Diagnostic[], + toolInputs?: { type: string, variable: string }[] +): Diagnostic[] { + if (!toolInputs || toolInputs.length === 0) { + return diagnostics; + } + + return diagnostics.filter((diagnostic) => { + // Only filter "undefined symbol" diagnostics + if (!diagnostic.message.includes('undefined symbol')) { + return true; + } + + // Extract symbol name from message like "undefined symbol 'code'" + const match = diagnostic.message.match(/['"`]([^'"`]+)['"`]/); + if (!match) { + return true; // Keep diagnostic if we can't parse it + } + + const symbolName = match[1]; + // Filter out if symbol is a Tool Input parameter + return !toolInputs.some(input => input.variable === symbolName); + }); +} + /** * Check if the type is supported by the data mapper * diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx index d0f86c733ae..3f6d6ebc532 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx @@ -16,7 +16,7 @@ * under the License. */ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { NodeList, Category as PanelCategory, FormField, FormValues } from "@wso2/ballerina-side-panel"; import { @@ -40,12 +40,14 @@ import { Property, ToolParameterItem, NodeProperties, + Diagnostic, } from "@wso2/ballerina-core"; import { convertBICategoriesToSidePanelCategories, convertConfig, convertFunctionCategoriesToSidePanelCategories, + filterToolInputSymbolDiagnostics, } from "../../../utils/bi"; import FormGeneratorNew from "../Forms/FormGeneratorNew"; import { RelativeLoader } from "../../../components/RelativeLoader"; @@ -140,6 +142,17 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { const selectedNodeRef = useRef(undefined); const agentFilePath = useRef(Utils.joinPath(URI.file(projectPath), "agents.bal").fsPath); const functionFilePath = useRef(Utils.joinPath(URI.file(projectPath), "functions.bal").fsPath); + const parameterFieldsRef = useRef([]); + + // Create custom diagnostic filter for Tool Input parameters + const customDiagnosticFilter = useCallback((diagnostics: Diagnostic[]) => { + if (!parameterFieldsRef.current || parameterFieldsRef.current.length === 0) { + return diagnostics; + } + const toolInputs = parameterFieldsRef.current.map(param => ({ type: param.formValues.type, variable: param.formValues.variable })); + return filterToolInputSymbolDiagnostics(diagnostics, toolInputs); + }, []); + useEffect(() => { fetchNodes(); }, []); @@ -599,6 +612,13 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { concertRequired={concertRequired} description={description} helperPaneSide="left" + customDiagnosticFilter={customDiagnosticFilter} + onChange={(fieldKey, value) => { + if (fieldKey === "parameters") { + parameterFieldsRef.current = value as ToolParameterItem[]; + return; + } + }} injectedComponents={[ { component: ( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx index badf69eba60..7c17dce5a1e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx @@ -36,7 +36,8 @@ import { LinePosition, NodeProperties, ExpressionCompletionsRequest, - ExpressionCompletionsResponse + ExpressionCompletionsResponse, + Diagnostic } from "@wso2/ballerina-core"; import { FormField, @@ -115,6 +116,7 @@ interface FormProps { changeOptionalFieldTitle?: string; onChange?: (fieldKey: string, value: any, allValues: FormValues) => void; hideSaveButton?: boolean; + customDiagnosticFilter?: (diagnostics: Diagnostic[]) => Diagnostic[]; } export function FormGeneratorNew(props: FormProps) { @@ -147,7 +149,8 @@ export function FormGeneratorNew(props: FormProps) { injectedComponents, changeOptionalFieldTitle, onChange, - hideSaveButton + hideSaveButton, + customDiagnosticFilter } = props; const { rpcClient } = useRpcContext(); @@ -469,7 +472,7 @@ export function FormGeneratorNew(props: FormProps) { triggerCharacter: triggerCharacter as TriggerCharacter } }; - + let completions: ExpressionCompletionsResponse; if (!isDataMapperEditor) { completions = await rpcClient.getBIDiagramRpcClient().getExpressionCompletions(completionRequest); @@ -648,6 +651,10 @@ export function FormGeneratorNew(props: FormProps) { let uniqueDiagnostics = removeDuplicateDiagnostics(response.diagnostics); // HACK: filter unknown module and undefined type diagnostics for local connections uniqueDiagnostics = filterUnsupportedDiagnostics(uniqueDiagnostics); + // Apply custom diagnostic filter if provided + if (customDiagnosticFilter) { + uniqueDiagnostics = customDiagnosticFilter(uniqueDiagnostics); + } setDiagnosticsInfo({ key, diagnostics: uniqueDiagnostics }); } From d97973a818ec98a2a97d8918399262d918ef3b65 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 15 Dec 2025 09:40:51 +0530 Subject: [PATCH 066/102] Fix implementation info injected index in agent tool form --- .../src/views/BI/AIChatAgent/AIAgentSidePanel.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx index 3f6d6ebc532..dcc105c31ff 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AIAgentSidePanel.tsx @@ -102,7 +102,6 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { const [categories, setCategories] = useState([]); const [selectedNodeCodeData, setSelectedNodeCodeData] = useState(undefined); const [toolNodeId, setToolNodeId] = useState(undefined); - const [injectedComponentIndex, setInjectedComponentIndex] = useState(3); const functionNode = useRef(null); const flowNode = useRef(null); @@ -335,7 +334,6 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { if (functionNodeResponse.functionDefinition.properties) { toolInputFields = convertConfig(functionNodeResponse.functionDefinition.properties); } - setInjectedComponentIndex(2 + toolInputFields.length); console.log(">>> Tool input fields", { toolInputFields }); const functionNodeTemplate = await rpcClient.getBIDiagramRpcClient().getNodeTemplate({ @@ -390,7 +388,7 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { field.advanced = false; // hack: remove headers and additionalValues from the tool inputs and set default value to () if (["headers", "additionalValues"].includes(field.key)) { - field.value = "()"; + field.value = "{}"; return; } includedKeys.push(field.key); @@ -399,7 +397,6 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) { const filteredNodeParameterFields = nodeParameterFields.filter(field => includedKeys.includes(field.key)); toolInputFields = createToolInputFields(filteredNodeParameterFields); - setInjectedComponentIndex(2 + toolInputFields.length); console.log(">>> Tool input fields", { toolInputFields }); @@ -629,7 +626,7 @@ export function AIAgentSidePanel(props: BIFlowDiagramProps) {
), - index: injectedComponentIndex, + index: 3, }, ]} /> From 858f6c3d94f8b6283c6110d60dc5cae47ac61c1a Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 15 Dec 2025 09:47:32 +0530 Subject: [PATCH 067/102] Fix coderabbit suggestions --- .../src/views/BI/Forms/FormGeneratorNew/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx index 7c17dce5a1e..f5749d234ff 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGeneratorNew/index.tsx @@ -667,7 +667,7 @@ export function FormGeneratorNew(props: FormProps) { }, 250 ), - [rpcClient, fileName, targetLineRange] + [rpcClient, fileName, targetLineRange, customDiagnosticFilter] ); const handleGetHelperPane = ( From fdeee53522ae2d5d0a9a6f9f260203bbbdfafc3d Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 09:49:10 +0530 Subject: [PATCH 068/102] Fix UI styling and descriptions --- .../Connection/APIConnectionPopup/index.tsx | 28 ++++--- .../DatabaseConnectionPopup/index.tsx | 33 ++++---- .../Connection/EditConnectionPopup/index.tsx | 79 +++++++++++++------ 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index fcbf9a7e56d..b39a5052ebc 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -138,7 +138,7 @@ const SectionSubtitle = styled(Typography)` const FormSection = styled.div` display: flex; flex-direction: column; - gap: 20px; + gap: 30px; background: transparent; border: none; padding: 0; @@ -261,11 +261,11 @@ const StepBadge = styled.div<{ active?: boolean; completed?: boolean }>` : ThemeColors.SURFACE_CONTAINER}; border: 1px solid ${(props: { active?: boolean; completed?: boolean }) => - props.completed - ? ThemeColors.PRIMARY - : props.active - ? ThemeColors.OUTLINE - : ThemeColors.OUTLINE_VARIANT}; + props.completed + ? ThemeColors.PRIMARY + : props.active + ? ThemeColors.OUTLINE + : ThemeColors.OUTLINE_VARIANT}; color: ${(props: { active?: boolean; completed?: boolean }) => props.completed ? ThemeColors.PRIMARY : ThemeColors.ON_SURFACE_VARIANT}; font-weight: 600; @@ -346,9 +346,9 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { if (!rpcClient) { return { success: false, errorMessage: "RPC client not available" }; } - + const isOpenApi = specType.toLowerCase() === "openapi"; - + if (isOpenApi) { const response = await rpcClient.getBIDiagramRpcClient().generateOpenApiClient({ openApiContractPath: specFilePath, @@ -402,7 +402,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { try { // Small delay to ensure the connector is available await new Promise(resolve => setTimeout(resolve, 500)); - + const defaultPosition = target || { line: 0, offset: 0 }; const searchResponse = await rpcClient.getBIDiagramRpcClient().search({ position: { @@ -500,7 +500,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { return ( <> - + ); @@ -554,7 +554,7 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { - {selectedFilePath ? getFileName(selectedFilePath) : "Choose file to import"} + {selectedFilePath ? selectedFilePath : "Choose file to import"} Supports {supportedFileFormats} files @@ -568,6 +568,12 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { if (selectedFlowNode) { return ( +
+ Connection Details + + Configure connection settings + +
` flex: 1; display: flex; flex-direction: column; - overflow: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "hidden" : "auto"}; + overflow: auto; padding: 24px 32px; padding-bottom: ${(props: { hasFooterButton?: boolean }) => props.hasFooterButton ? "0" : "24px"}; min-height: 0; @@ -145,7 +145,6 @@ const FormField = styled.div` `; const ActionButton = styled(Button)` - margin-top: 24px; width: 100% !important; min-width: 0 !important; max-width: none !important; @@ -328,10 +327,10 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const [currentStep, setCurrentStep] = useState(0); const [credentials, setCredentials] = useState({ - databaseType: "PostgreSQL", + databaseType: "MySQL", host: "", - port: "5432", - databaseName: "mydb", + port: "3306", + databaseName: "", username: "", password: "", }); @@ -548,7 +547,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { handleCredentialsChange("host", value)} /> @@ -557,6 +556,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { handleCredentialsChange("port", value)} /> @@ -606,6 +606,9 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { {selectedTablesCount} of {totalTablesCount} selected + + Select All + {tables.map((table, index) => ( ))} - - Select All -
); case 2: return ( - +
Connection Details @@ -642,20 +642,19 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - - This name will be used to reference this connection in your code - +
Connection Configurables - - Host, port, username, password, and database name will be created as configurables. Define default values below. - + + Host, port, username, password, and database name will be created as configurables. Define their default values below. + +
` flex: 1; @@ -208,7 +229,7 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { try { const res = await rpcClient.getBIDiagramRpcClient().getModuleNodes(); console.log(">>> moduleNodes", { moduleNodes: res }); - + if (!res.flowModel.connections || res.flowModel.connections.length === 0) { console.error(">>> No connections found"); if (onClose) { @@ -234,10 +255,10 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { } const connectionFile = connector.codedata.lineRange.fileName; - const connectionFilePath = (await rpcClient.getVisualizerRpcClient().joinProjectPath({ - segments: [connectionFile] + const connectionFilePath = (await rpcClient.getVisualizerRpcClient().joinProjectPath({ + segments: [connectionFile] })).filePath; - + setFilePath(connectionFilePath); setConnection(connector); @@ -352,7 +373,7 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { Edit Connection - Update connection settings for this connector + Update connection details @@ -360,7 +381,7 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { - + {/* {getConnectorIcon() ? ( @@ -376,27 +397,35 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { {getConnectorDescription()} - + */} - { - // Remove description property from node before passing to form - // since it's already shown in the connector info card - const nodeWithoutDescription = cloneDeep(connection); - if (nodeWithoutDescription?.metadata?.description) { - delete nodeWithoutDescription.metadata.description; - } - return nodeWithoutDescription; - })()} - onSubmit={handleOnFormSubmit} - updatedExpressionField={updatedExpressionField} - resetUpdatedExpressionField={handleResetUpdatedExpressionField} - isSaving={isSaving} - footerActionButton={true} - /> + <> + + Connection Details + + Configure your connection settings + + + { + // Remove description property from node before passing to form + // since it's already shown in the connector info card + const nodeWithoutDescription = cloneDeep(connection); + if (nodeWithoutDescription?.metadata?.description) { + delete nodeWithoutDescription.metadata.description; + } + return nodeWithoutDescription; + })()} + onSubmit={handleOnFormSubmit} + updatedExpressionField={updatedExpressionField} + resetUpdatedExpressionField={handleResetUpdatedExpressionField} + isSaving={isSaving} + footerActionButton={true} + /> + From bf9be8a66e85bd8395ba9ae9468c0a9df446471b Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 09:57:42 +0530 Subject: [PATCH 069/102] Fix checkbox not clicking --- .../views/BI/Connection/DatabaseConnectionPopup/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 763e8c57eda..bb9a218e08a 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -565,6 +565,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { handleCredentialsChange("databaseName", value)} /> @@ -619,7 +620,11 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { handleTableToggle(index)} + onChange={(e) => { + e.stopPropagation(); + handleTableToggle(index); + }} + onClick={(e) => e.stopPropagation()} /> {table.name} From bf5484255b0035da8717f2eb1bbf04545c9c64f6 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 15 Dec 2025 11:02:58 +0530 Subject: [PATCH 070/102] Always check user status from choreo cli --- .../src/auth/wso2-auth-provider.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts index 7e68b2cb8c8..bad8b1f7a26 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -174,23 +174,6 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo */ public async initAuth() { try { - // Try to get session from VS Code secure storage first - const sessionData = await this.getSessionData(); - if (sessionData?.userInfo) { - // We have a stored session, update the state - this._state = { userInfo: sessionData.userInfo, region: sessionData.region }; - dataCacheStore.getState().setOrgs(sessionData.userInfo.organizations); - contextStore.getState().refreshState(); - this._stateChangeEmitter.fire({ state: this._state }); - - const contextStoreState = contextStore.getState().state; - if (contextStoreState.selected?.org) { - ext?.clients?.rpcClient?.changeOrgContext(contextStoreState.selected?.org?.id?.toString()); - } - return; - } - - // Fallback: check RPC for existing session const userInfo = await ext.clients.rpcClient.getUserInfo(); if (userInfo) { const region = await ext.clients.rpcClient.getCurrentRegion(); From fb207ab7d2d8965b4e51db9db5506566de3d5b8b Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 12:13:01 +0530 Subject: [PATCH 071/102] Update configurables view to readonly --- .../DatabaseConnectionPopup/index.tsx | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index bb9a218e08a..e2acb611a2b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -144,6 +144,17 @@ const FormField = styled.div` gap: 8px; `; +const ReadonlyValue = styled.div` + padding: 5px; + border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; + border-radius: 2px; + background: ${ThemeColors.SURFACE_CONTAINER}; + color: ${ThemeColors.ON_SURFACE}; + font-size: 14px; + line-height: 20px; + user-select: text; +`; + const ActionButton = styled(Button)` width: 100% !important; min-width: 0 !important; @@ -206,7 +217,6 @@ const SelectionInfo = styled.div` display: flex; justify-content: space-between; align-items: center; - margin-bottom: 16px; `; const ActionButtonsContainer = styled.div` @@ -595,7 +605,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { case 1: return ( - +
Select Tables @@ -655,43 +665,35 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) {
- Connection Configurables - - Host, port, username, password, and database name will be created as configurables. Define their default values below. + Connection Configurables + + Configurables will be generated for the connection host, port, username, password, and database name, with default values specified below.
- handleCredentialsChange("host", value)} - /> + + Host + + {credentials.host} - handleCredentialsChange("port", value)} - /> + + Port + + {credentials.port} - handleCredentialsChange("username", value)} - /> + + Username + + {credentials.username} - handleCredentialsChange("databaseName", value)} - /> + + Database Name + + {credentials.databaseName}
From 58a5a88f9f11ab1c5734d1244b6f6ec71bfd695e Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 15 Dec 2025 13:45:47 +0530 Subject: [PATCH 072/102] Enhance DataMapperView to manage view state and fetch sub-mapping codedata when codedata changes and already focused into sub mapping --- .../src/views/DataMapper/DataMapperView.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index 343b4a0fbc1..d26ee818fa9 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -84,6 +84,11 @@ export function DataMapperView(props: DataMapperViewProps) { codedata: codedata }); + const viewStateRef = useRef(viewState); + useEffect(() => { + viewStateRef.current = viewState; + }, [viewState]); + /* Completions related */ const [completions, setCompletions] = useState([]); const prevCompletionFetchText = useRef(""); @@ -108,14 +113,25 @@ export function DataMapperView(props: DataMapperViewProps) { const positionChanged = prevPositionRef.current?.line !== position?.line || prevPositionRef.current?.offset !== position?.offset; - - setViewState(prevState => ({ - viewId: positionChanged ? name : prevState.viewId || name, - codedata: codedata, - // Preserve subMappingName only if the position hasn't changed and there is an existing sub-mapping name. - // This ensures that changing the position resets the sub-mapping context. - subMappingName: !positionChanged && prevState.subMappingName - })); + + if (viewStateRef.current.subMappingName) { + const viewId = viewStateRef.current.viewId; + rpcClient.getDataMapperRpcClient() + .getSubMappingCodedata({ + filePath, + codedata: codedata, + view: viewId + }).then((resp) => { + console.log(">>> [Data Mapper] getSubMappingCodedata response:", resp); + setViewState({ viewId: viewId, codedata: resp.codedata, subMappingName: viewId }); + }); + } else { + setViewState(prevState => ({ + viewId: positionChanged ? name : prevState.viewId || name, + codedata: codedata, + subMappingName: undefined + })); + } prevPositionRef.current = position; }, [name, codedata, position]); From 36df313f5041a78acb1650018a0e43156917ad93 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 15 Dec 2025 14:06:51 +0530 Subject: [PATCH 073/102] Fix condition to fetch sub-mapping codedata only when position has not changed --- .../src/views/DataMapper/DataMapperView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx index d26ee818fa9..3e99bedf74c 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/DataMapper/DataMapperView.tsx @@ -114,7 +114,7 @@ export function DataMapperView(props: DataMapperViewProps) { prevPositionRef.current?.line !== position?.line || prevPositionRef.current?.offset !== position?.offset; - if (viewStateRef.current.subMappingName) { + if (viewStateRef.current.subMappingName && !positionChanged) { const viewId = viewStateRef.current.viewId; rpcClient.getDataMapperRpcClient() .getSubMappingCodedata({ From 6289a752b9efc17bc9230772759f9134962c81b4 Mon Sep 17 00:00:00 2001 From: ChamodA Date: Mon, 15 Dec 2025 14:38:33 +0530 Subject: [PATCH 074/102] Add "Group by" option to ClauseEditor component --- .../DataMapper/SidePanel/QueryClauses/ClauseEditor.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1fd5357823a..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 @@ -47,7 +47,8 @@ export function ClauseEditor(props: ClauseEditorProps) { { content: "Sort by", value: IntermediateClauseType.ORDER_BY }, { content: "Limit", value: IntermediateClauseType.LIMIT }, { content: "From", value: IntermediateClauseType.FROM }, - { content: "Join", value: IntermediateClauseType.JOIN } + { content: "Join", value: IntermediateClauseType.JOIN }, + { content: "Group by", value: IntermediateClauseType.GROUP_BY } ] const nameField: DMFormField = { From fb2b0ac80f9ff96c31a2a66740aab6bafadb5369 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 15:16:04 +0530 Subject: [PATCH 075/102] Update popup styling --- .../src/views/BI/Connection/APIConnectionPopup/index.tsx | 8 ++++---- .../src/views/BI/Connection/AddConnectionPopup/index.tsx | 8 ++++---- .../BI/Connection/ConnectionConfigurationPopup/index.tsx | 2 +- .../src/views/BI/Connection/EditConnectionPopup/index.tsx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index b39a5052ebc..7a4e181b698 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -36,12 +36,12 @@ const PopupContainer = styled.div` left: 50%; transform: translate(-50%, -50%); width: 80%; - max-width: 860px; - height: 82%; - max-height: 840px; + max-width: 800px; + height: 80%; + max-height: 800px; z-index: 2000; background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 20px; + border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 7361ff99b58..419d6e3e023 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -45,7 +45,7 @@ const PopupContainer = styled.div` max-height: 800px; z-index: 2000; background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 20px; + border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; @@ -103,7 +103,7 @@ const Section = styled.div` `; const SectionTitle = styled(Typography)` - font-size: 16px; + font-size: 14px; font-weight: 600; color: ${ThemeColors.ON_SURFACE}; margin: 0; @@ -577,7 +577,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { {(connectorOptions.showApiSpec || connectorOptions.showDatabase) && (
- CREATE NEW CONNECTOR + CREATE NEW CONNECTOR {connectorOptions.showApiSpec && ( @@ -636,7 +636,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) {
- PRE-BUILT CONNECTORS + PRE-BUILT CONNECTORS Date: Mon, 15 Dec 2025 15:41:51 +0530 Subject: [PATCH 076/102] Add tables search bar --- .../Connection/AddConnectionPopup/index.tsx | 1 - .../DatabaseConnectionPopup/index.tsx | 48 +++++++++++++++---- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 419d6e3e023..5be2813da88 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -171,7 +171,6 @@ const ConnectorOptionButtons = styled.div` const ConnectorTypeButton = styled(Button)` font-size: 12px; - padding: 4px 12px; height: auto; `; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index e2acb611a2b..2704eb98fbb 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -16,9 +16,9 @@ * under the License. */ -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import styled from "@emotion/styled"; -import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps, Icon } from "@wso2/ui-toolkit"; +import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps, Icon, SearchBox } from "@wso2/ui-toolkit"; import { Stepper } from "@wso2/ui-toolkit"; import { DIRECTORY_MAP, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -209,7 +209,6 @@ const TableName = styled(Typography)` `; const SelectAllButton = styled(Button)` - margin-top: 16px; align-self: flex-end; `; @@ -219,6 +218,13 @@ const SelectionInfo = styled.div` align-items: center; `; +const SearchRow = styled.div` + display: flex; + align-items: center; + gap: 12px; + justify-content: space-between; +`; + const ActionButtonsContainer = styled.div` display: flex; gap: 12px; @@ -349,6 +355,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const [connectionName, setConnectionName] = useState(""); const [isSaving, setIsSaving] = useState(false); const [connectionError, setConnectionError] = useState(null); + const [tableSearch, setTableSearch] = useState(""); const steps = ["Introspect Database", "Select Tables", "Create Connection"]; @@ -429,6 +436,14 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { setTables(updatedTables); }; + const handleTableToggleByName = (name: string) => { + setTables((prev) => + prev.map((t) => + t.name === name ? { ...t, selected: !t.selected } : t + ) + ); + }; + const handleSelectAll = () => { const allSelected = tables.every((table) => table.selected); setTables(tables.map((table) => ({ ...table, selected: !allSelected }))); @@ -507,6 +522,13 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const selectedTablesCount = tables.filter((t) => t.selected).length; const totalTablesCount = tables.length; + const filteredTables = useMemo( + () => + tables.filter((t) => + t.name.toLowerCase().includes(tableSearch.trim().toLowerCase()) + ), + [tables, tableSearch] + ); const renderErrorDisplay = () => { if (!connectionError) return null; @@ -617,22 +639,30 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { {selectedTablesCount} of {totalTablesCount} selected - - Select All - + + + + Select All + + - {tables.map((table, index) => ( + {filteredTables.map((table) => ( handleTableToggle(index)} + onClick={() => handleTableToggleByName(table.name)} > { e.stopPropagation(); - handleTableToggle(index); + handleTableToggleByName(table.name); }} onClick={(e) => e.stopPropagation()} /> From e0a6d7de040550e7f0ef8d358fb6e5268bd24faa Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 15:56:21 +0530 Subject: [PATCH 077/102] Update configurable names listed --- .../views/BI/Connection/DatabaseConnectionPopup/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 2704eb98fbb..f55021f6cec 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -703,25 +703,25 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - Host + {connectionName ? `${connectionName}Host` : "Host"} {credentials.host} - Port + {connectionName ? `${connectionName}Port` : "Port"} {credentials.port} - Username + {connectionName ? `${connectionName}User` : "Username"} {credentials.username} - Database Name + {connectionName ? `${connectionName}Database` : "Database Name"} {credentials.databaseName} From 68a032a43f09fb96b6805cde3a20d946877f0e7a Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 16:22:07 +0530 Subject: [PATCH 078/102] Show persist connection option if experimental enabled --- .../Connection/AddConnectionPopup/index.tsx | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 5be2813da88..ad0700aac68 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -246,12 +246,24 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { const [filterType, setFilterType] = useState<"All" | "Standard" | "Organization">("All"); const [wizardStep, setWizardStep] = useState<"database" | "api" | "connector" | null>(null); const [selectedConnector, setSelectedConnector] = useState(null); + const [experimentalEnabled, setExperimentalEnabled] = useState(false); useEffect(() => { setIsSearching(true); fetchConnectors(); }, []); + useEffect(() => { + rpcClient + ?.getCommonRpcClient() + .experimentalEnabled() + .then((enabled) => setExperimentalEnabled(enabled)) + .catch((err) => { + console.error(">>> error checking experimental flag", err); + setExperimentalEnabled(false); + }); + }, [rpcClient]); + useEffect(() => { setIsSearching(true); debouncedSearch(searchText); @@ -508,8 +520,8 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { const getConnectorCreationOptions = () => { if (!searchText || searchText.trim() === "") { - // No search - show both options - return { showApiSpec: true, showDatabase: true }; + // No search - show both options (database only if experimental) + return { showApiSpec: true, showDatabase: experimentalEnabled }; } const lowerSearchText = searchText.toLowerCase().trim(); @@ -533,7 +545,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { // If search matches database keywords, show only database option if (isDatabaseSearch && !isApiSearch) { - return { showApiSpec: false, showDatabase: true }; + return { showApiSpec: false, showDatabase: experimentalEnabled }; } // If search matches API keywords, show only API spec option @@ -542,7 +554,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { } // If both or neither match, show both options - return { showApiSpec: true, showDatabase: true }; + return { showApiSpec: true, showDatabase: experimentalEnabled }; }; const connectorOptions = getConnectorCreationOptions(); @@ -559,10 +571,21 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { - To establish your connection, first define a connector. You may create a custom connector using - an API specification or by introspecting a database. Alternatively, you can select one of the - pre-built connectors below. You will then be guided to provide the required details to complete - the connection setup. + {experimentalEnabled ? ( + <> + To establish your connection, first define a connector. You may create a custom connector using + an API specification or by introspecting a database. Alternatively, you can select one of the + pre-built connectors below. You will then be guided to provide the required details to complete + the connection setup. + + ) : ( + <> + To establish your connection, first define a connector. You may create a custom connector using + an API specification. Alternatively, you can select one of the pre-built connectors below. You will then be guided to provide the required details to complete + the connection setup. + + )} + From ec984b2bc9386bf3ec976817a7db3a61c6ab84a6 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 16:43:07 +0530 Subject: [PATCH 079/102] Refactor code --- .../ballerina-visualizer/src/PopupPanel.tsx | 1 - .../Connection/APIConnectionPopup/index.tsx | 74 ----------- .../Connection/AddConnectionPopup/index.tsx | 33 ++--- .../DatabaseConnectionPopup/index.tsx | 14 +- .../Connection/EditConnectionPopup/index.tsx | 120 +----------------- 5 files changed, 17 insertions(+), 225 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx index c0c20ba25f6..098ebe0223b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/PopupPanel.tsx @@ -89,7 +89,6 @@ const PopupPanel = (props: PopupPanelProps) => { connectionName={machineState?.identifier} onClose={onClose} /> - {/* */} ); }); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index 7a4e181b698..5ad67bcad6e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -23,7 +23,6 @@ import { AvailableNode, Category, DataMapperDisplayMode, DIRECTORY_MAP, FlowNode import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { ExpressionFormField } from "@wso2/ballerina-side-panel"; import ConnectionConfigView from "../ConnectionConfigView"; -import { getFormProperties } from "../../../../utils/bi"; import { FormSubmitOptions } from "../../FlowDiagram"; const PopupOverlay = styled(Overlay)` @@ -204,7 +203,6 @@ const UploadSubtitle = styled(Typography)` margin: 0; `; - const ActionButton = styled(Button)` width: 100% !important; min-width: 0 !important; @@ -213,82 +211,12 @@ const ActionButton = styled(Button)` align-items: center; `; -const SummaryCard = styled.div` - padding: 12px 14px; - border-radius: 10px; - background: ${ThemeColors.SURFACE_CONTAINER}; - border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; - color: ${ThemeColors.ON_SURFACE}; - font-size: 12px; -`; - -const InfoRow = styled.div` - display: flex; - flex-direction: column; - gap: 12px; -`; - const StepHeader = styled.div` display: flex; flex-direction: column; gap: 4px; `; -const DisabledActionButton = styled(Button)` - width: 100% !important; - min-width: 0 !important; - margin-top: 4px; -`; - -const FormGrid = styled.div` - display: flex; - flex-direction: column; - gap: 12px; -`; - -const StepBadge = styled.div<{ active?: boolean; completed?: boolean }>` - width: 28px; - height: 28px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - background: ${(props: { active?: boolean; completed?: boolean }) => - props.completed - ? ThemeColors.PRIMARY_CONTAINER - : props.active - ? ThemeColors.SURFACE_DIM - : ThemeColors.SURFACE_CONTAINER}; - border: 1px solid - ${(props: { active?: boolean; completed?: boolean }) => - props.completed - ? ThemeColors.PRIMARY - : props.active - ? ThemeColors.OUTLINE - : ThemeColors.OUTLINE_VARIANT}; - color: ${(props: { active?: boolean; completed?: boolean }) => - props.completed ? ThemeColors.PRIMARY : ThemeColors.ON_SURFACE_VARIANT}; - font-weight: 600; -`; - -const StepperLabel = styled.div` - display: flex; - flex-direction: column; - gap: 2px; -`; - -const StepperRow = styled.div` - display: flex; - align-items: center; - gap: 16px; -`; - -const StepperCopy = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - interface APIConnectionPopupProps { projectPath: string; fileName: string; @@ -398,7 +326,6 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { // Only proceed if there's no error message if (generateResponse?.success) { - // Wait a bit for the connector to be available, then search for it try { // Small delay to ensure the connector is available await new Promise(resolve => setTimeout(resolve, 500)); @@ -436,7 +363,6 @@ export function APIConnectionPopup(props: APIConnectionPopupProps) { setCurrentStep(1); } else { console.error(">>> Error generating connector:", generateResponse?.errorMessage); - // Optionally show error to user } setIsSavingConnector(false); }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index ad0700aac68..7b1258c311c 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -28,7 +28,6 @@ import APIConnectionPopup from "../APIConnectionPopup"; import ConnectionConfigurationPopup from "../ConnectionConfigurationPopup"; import DatabaseConnectionPopup from "../DatabaseConnectionPopup"; import { BodyTinyInfo } from "../../../styles"; -import AddConnectionWizard from "../AddConnectionWizard"; const PopupOverlay = styled(Overlay)` z-index: 1999; @@ -213,11 +212,6 @@ const FilterButton = styled.button<{ active?: boolean }>` color: ${(props: { active?: boolean }) => props.active ? ThemeColors.ON_PRIMARY : ThemeColors.ON_SURFACE}; } - - // &:focus { - // outline: 1px solid ${ThemeColors.PRIMARY}; - // outline-offset: 2px; - // } `; const ConnectorsGrid = styled.div` @@ -333,11 +327,11 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { .then(async (model) => { console.log(">>> bi searched connectors", model); console.log(">>> bi filtered connectors", model.categories); - + // When searching, the API might return a flat array of connectors instead of categories // Check if categories exist and have the proper structure (with items arrays) let normalizedCategories: Category[] = []; - + if (model.categories && Array.isArray(model.categories)) { // Check if the first item is a category (has items) or a connector (has codedata) const firstItem = model.categories[0]; @@ -347,7 +341,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { } else if (firstItem && "codedata" in firstItem) { // Flat array of connectors - wrap in a category normalizedCategories = [{ - metadata: { + metadata: { label: "Search Results", description: "" }, @@ -355,7 +349,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { }]; } } - + console.log(">>> normalized categories for search", normalizedCategories); setConnectors(normalizedCategories); }) @@ -372,7 +366,6 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { const handleDatabaseConnection = () => { // Navigate to database connection wizard - // This would need to be implemented based on your database connection flow setWizardStep("database"); }; @@ -525,14 +518,14 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { } const lowerSearchText = searchText.toLowerCase().trim(); - + // Database-related keywords const databaseKeywords = [ "database", "db", "mysql", "postgresql", "postgres", "mssql", "sql server", "sqlserver", "oracle", "sqlite", "mariadb", "mongodb", "cassandra", "redis", "dynamodb", "table", "schema", "query", "sql" ]; - + // API-related keywords const apiKeywords = [ "api", "http", "https", "rest", "graphql", "soap", "wsdl", "openapi", @@ -547,7 +540,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { if (isDatabaseSearch && !isApiSearch) { return { showApiSpec: false, showDatabase: experimentalEnabled }; } - + // If search matches API keywords, show only API spec option if (isApiSearch && !isDatabaseSearch) { return { showApiSpec: true, showDatabase: false }; @@ -579,13 +572,13 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { the connection setup. ) : ( - <> - To establish your connection, first define a connector. You may create a custom connector using - an API specification. Alternatively, you can select one of the pre-built connectors below. You will then be guided to provide the required details to complete - the connection setup. - + <> + To establish your connection, first define a connector. You may create a custom connector using + an API specification. Alternatively, you can select one of the pre-built connectors below. You will then be guided to provide the required details to complete + the connection setup. + )} - + diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index f55021f6cec..60e73d0448c 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -225,14 +225,6 @@ const SearchRow = styled.div` justify-content: space-between; `; -const ActionButtonsContainer = styled.div` - display: flex; - gap: 12px; - justify-content: center; - margin-top: 24px; - width: 100%; -`; - const ConfigurablesPanel = styled.div` display: flex; flex-direction: column; @@ -338,7 +330,7 @@ const DEFAULT_PORTS: Record = { }; export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { - const { fileName, target, onClose, onBack, onBrowseConnectors} = props; + const { fileName, target, onClose, onBack, onBrowseConnectors } = props; const { rpcClient } = useRpcContext(); const [currentStep, setCurrentStep] = useState(0); @@ -695,9 +687,9 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) {
- Connection Configurables + Connection Configurables - Configurables will be generated for the connection host, port, username, password, and database name, with default values specified below. + Configurables will be generated for the connection host, port, username, password, and database name, with default values specified below.
diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx index d1c7ee1b0b2..518002772d3 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx @@ -18,14 +18,12 @@ import React, { useEffect, useState } from "react"; import styled from "@emotion/styled"; -import { FlowNode, LinePosition, ParentPopupData, SubPanel, SubPanelView } from "@wso2/ballerina-core"; +import { FlowNode, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { Button, Codicon, ThemeColors, Typography, Overlay, ProgressRing } from "@wso2/ui-toolkit"; import ConnectionConfigView from "../ConnectionConfigView"; import { getFormProperties } from "../../../../utils/bi"; import { ExpressionFormField } from "@wso2/ballerina-side-panel"; -import { HelperView } from "../../HelperView"; -import { ConnectorIcon } from "@wso2/bi-diagram"; import { cloneDeep } from "lodash"; const PopupOverlay = styled(Overlay)` @@ -88,85 +86,6 @@ const CloseButton = styled(Button)` padding: 4px; `; -const ConnectorInfoCard = styled.div` - display: flex; - align-items: center; - gap: 16px; - padding: 16px; - margin: 24px 32px; - border: 1px solid ${ThemeColors.OUTLINE_VARIANT}; - border-radius: 8px; - background-color: ${ThemeColors.SURFACE_DIM}; - position: relative; -`; - -const ConnectorInfoIcon = styled.div` - display: flex; - align-items: center; - justify-content: center; - width: 48px; - height: 48px; - border-radius: 8px; - background-color: ${ThemeColors.SURFACE_CONTAINER}; - flex-shrink: 0; - - & > img { - width: 32px; - height: 32px; - object-fit: contain; - } - - & > svg { - width: 32px; - height: 32px; - } -`; - -const StyledCodicon = styled(Codicon)` - font-size: 32px; - width: 32px; - height: 32px; -`; - -const StyledConnectorIcon = styled.div` - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - - & > img { - width: 32px; - height: 32px; - object-fit: contain; - } - - & > svg { - width: 32px; - height: 32px; - } -`; - -const ConnectorInfoContent = styled.div` - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -`; - -const ConnectorInfoName = styled(Typography)` - font-size: 16px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const ConnectorInfoDescription = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - const ConnectionDetailsSection = styled.div` display: flex; flex-direction: column; @@ -188,7 +107,6 @@ const ConnectionDetailsSubtitle = styled(Typography)` `; - const ContentContainer = styled.div<{ hasFooterButton?: boolean }>` flex: 1; display: flex; @@ -319,11 +237,6 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { }; - - const updateExpressionField = (data: ExpressionFormField) => { - setUpdatedExpressionField(data); - }; - const handleResetUpdatedExpressionField = () => { setUpdatedExpressionField(undefined); }; @@ -333,18 +246,6 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { handleClosePopup(); }; - const getConnectorName = () => { - return connection?.codedata?.module || connectionName || "Connection"; - }; - - const getConnectorDescription = () => { - return connection?.metadata?.description || ""; - }; - - const getConnectorIcon = () => { - return connection?.metadata?.icon; - }; - if (isLoading) { return ( <> @@ -380,25 +281,6 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { - - {/* - - {getConnectorIcon() ? ( - - - - ) : ( - - )} - - - {getConnectorName()} - - {getConnectorDescription()} - - - */} - <> From 0d87eb0f778177d875c87b6325cab17a4c5d0eef Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 17:21:03 +0530 Subject: [PATCH 080/102] Move common styles to a styles file --- .../Connection/APIConnectionPopup/index.tsx | 63 +------------- .../Connection/AddConnectionPopup/index.tsx | 45 +--------- .../ConnectionConfigurationPopup/index.tsx | 63 +------------- .../DatabaseConnectionPopup/index.tsx | 63 +------------- .../Connection/EditConnectionPopup/index.tsx | 63 +------------- .../src/views/BI/Connection/styles.ts | 83 +++++++++++++++++++ 6 files changed, 93 insertions(+), 287 deletions(-) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/styles.ts diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx index 5ad67bcad6e..e5da97bd414 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/APIConnectionPopup/index.tsx @@ -18,72 +18,13 @@ import React, { useMemo, useState } from "react"; import styled from "@emotion/styled"; -import { Button, Codicon, Dropdown, Overlay, Stepper, TextField, ThemeColors, Typography } from "@wso2/ui-toolkit"; +import { Button, Codicon, Dropdown, Stepper, TextField, ThemeColors, Typography } from "@wso2/ui-toolkit"; import { AvailableNode, Category, DataMapperDisplayMode, DIRECTORY_MAP, FlowNode, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { ExpressionFormField } from "@wso2/ballerina-side-panel"; import ConnectionConfigView from "../ConnectionConfigView"; import { FormSubmitOptions } from "../../FlowDiagram"; - -const PopupOverlay = styled(Overlay)` - z-index: 1999; -`; - -const PopupContainer = styled.div` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - max-width: 800px; - height: 80%; - max-height: 800px; - z-index: 2000; - background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 10px; - overflow: hidden; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -`; - -const PopupHeader = styled.div` - display: flex; - align-items: center; - gap: 16px; - padding: 24px 32px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -`; - -const BackButton = styled(Button)` - min-width: auto; - padding: 4px; -`; - -const HeaderTitleContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -`; - -const PopupTitle = styled(Typography)` - font-size: 20px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const PopupSubtitle = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - -const CloseButton = styled(Button)` - min-width: auto; - padding: 4px; -`; +import { PopupOverlay, PopupContainer, PopupHeader, BackButton, HeaderTitleContainer, PopupTitle, PopupSubtitle, CloseButton } from "../styles"; const StepperContainer = styled.div` padding: 20px 32px 18px 32px; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index 7b1258c311c..cb3a9d1b87d 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -20,7 +20,7 @@ import React, { useEffect, useState } from "react"; import styled from "@emotion/styled"; import { AvailableNode, Category, Item, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; -import { Button, Codicon, Icon, SearchBox, ThemeColors, Typography, Overlay, ProgressRing } from "@wso2/ui-toolkit"; +import { Button, Codicon, Icon, SearchBox, ThemeColors, Typography, ProgressRing } from "@wso2/ui-toolkit"; import { cloneDeep, debounce } from "lodash"; import ButtonCard from "../../../../components/ButtonCard"; import { ConnectorIcon } from "@wso2/bi-diagram"; @@ -28,48 +28,7 @@ import APIConnectionPopup from "../APIConnectionPopup"; import ConnectionConfigurationPopup from "../ConnectionConfigurationPopup"; import DatabaseConnectionPopup from "../DatabaseConnectionPopup"; import { BodyTinyInfo } from "../../../styles"; - -const PopupOverlay = styled(Overlay)` - z-index: 1999; -`; - -const PopupContainer = styled.div` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - max-width: 800px; - height: 80%; - max-height: 800px; - z-index: 2000; - background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 10px; - overflow: hidden; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -`; - -const PopupHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - padding: 24px 32px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -`; - -const PopupTitle = styled(Typography)` - font-size: 20px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const CloseButton = styled(Button)` - min-width: auto; - padding: 4px; -`; +import { PopupOverlay, PopupContainer, PopupHeader, PopupTitle, CloseButton } from "../styles"; const PopupContent = styled.div` flex: 1; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx index 502d67fe28a..1b14ede540f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx @@ -30,7 +30,7 @@ import { SubPanelView, } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; -import { Button, Codicon, Icon, ThemeColors, Typography, Overlay } from "@wso2/ui-toolkit"; +import { Codicon, Icon, ThemeColors, Typography } from "@wso2/ui-toolkit"; import { ConnectorIcon } from "@wso2/bi-diagram"; import ConnectionConfigView from "../ConnectionConfigView"; import { getFormProperties } from "../../../../utils/bi"; @@ -41,66 +41,7 @@ import { DownloadIcon } from "../../../../components/DownloadIcon"; import { FormSubmitOptions } from "../../FlowDiagram"; import { cloneDeep } from "lodash"; import { URI, Utils } from "vscode-uri"; - -const PopupOverlay = styled(Overlay)` - z-index: 1999; -`; - -const PopupContainer = styled.div` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - max-width: 800px; - height: 80%; - max-height: 800px; - z-index: 2000; - background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 10px; - overflow: hidden; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -`; - -const ConfigHeader = styled.div` - display: flex; - align-items: center; - gap: 16px; - padding: 24px 32px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -`; - -const BackButton = styled(Button)` - min-width: auto; - padding: 4px; -`; - -const ConfigTitleContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -`; - -const PopupTitle = styled(Typography)` - font-size: 20px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const ConfigSubtitle = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - -const CloseButton = styled(Button)` - min-width: auto; - padding: 4px; -`; +import { PopupOverlay, PopupContainer, PopupHeader as ConfigHeader, BackButton, HeaderTitleContainer as ConfigTitleContainer, PopupTitle, PopupSubtitle as ConfigSubtitle, CloseButton } from "../styles"; const ConnectorInfoCard = styled.div` display: flex; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 60e73d0448c..ed8ab88a0d2 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -18,70 +18,11 @@ import React, { useMemo, useState } from "react"; import styled from "@emotion/styled"; -import { Button, Codicon, ThemeColors, Typography, Overlay, TextField, Dropdown, OptionProps, Icon, SearchBox } from "@wso2/ui-toolkit"; +import { Button, Codicon, ThemeColors, Typography, TextField, Dropdown, OptionProps, Icon, SearchBox } from "@wso2/ui-toolkit"; import { Stepper } from "@wso2/ui-toolkit"; import { DIRECTORY_MAP, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; - -const PopupOverlay = styled(Overlay)` - z-index: 1999; -`; - -const PopupContainer = styled.div` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - max-width: 800px; - height: 80%; - max-height: 800px; - z-index: 2000; - background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 20px; - overflow: hidden; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -`; - -const PopupHeader = styled.div` - display: flex; - align-items: center; - gap: 16px; - padding: 24px 32px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -`; - -const BackButton = styled(Button)` - min-width: auto; - padding: 4px; -`; - -const HeaderTitleContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -`; - -const PopupTitle = styled(Typography)` - font-size: 20px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const PopupSubtitle = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - -const CloseButton = styled(Button)` - min-width: auto; - padding: 4px; -`; +import { PopupOverlay, PopupContainer, PopupHeader, BackButton, HeaderTitleContainer, PopupTitle, PopupSubtitle, CloseButton } from "../styles"; const StepperContainer = styled.div` padding: 24px 32px; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx index 518002772d3..bf0dad10c80 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx @@ -20,71 +20,12 @@ import React, { useEffect, useState } from "react"; import styled from "@emotion/styled"; import { FlowNode, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; -import { Button, Codicon, ThemeColors, Typography, Overlay, ProgressRing } from "@wso2/ui-toolkit"; +import { Codicon, ThemeColors, Typography, ProgressRing } from "@wso2/ui-toolkit"; import ConnectionConfigView from "../ConnectionConfigView"; import { getFormProperties } from "../../../../utils/bi"; import { ExpressionFormField } from "@wso2/ballerina-side-panel"; import { cloneDeep } from "lodash"; - -const PopupOverlay = styled(Overlay)` - z-index: 1999; -`; - -const PopupContainer = styled.div` - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - max-width: 800px; - height: 80%; - max-height: 800px; - z-index: 2000; - background-color: ${ThemeColors.SURFACE_BRIGHT}; - border-radius: 10px; - overflow: hidden; - display: flex; - flex-direction: column; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -`; - -const ConfigHeader = styled.div` - display: flex; - align-items: center; - gap: 16px; - padding: 24px 32px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; -`; - -const BackButton = styled(Button)` - min-width: auto; - padding: 4px; -`; - -const ConfigTitleContainer = styled.div` - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -`; - -const PopupTitle = styled(Typography)` - font-size: 20px; - font-weight: 600; - color: ${ThemeColors.ON_SURFACE}; - margin: 0; -`; - -const ConfigSubtitle = styled(Typography)` - font-size: 12px; - color: ${ThemeColors.ON_SURFACE_VARIANT}; - margin: 0; -`; - -const CloseButton = styled(Button)` - min-width: auto; - padding: 4px; -`; +import { PopupOverlay, PopupContainer, PopupHeader as ConfigHeader, BackButton, HeaderTitleContainer as ConfigTitleContainer, PopupTitle, PopupSubtitle as ConfigSubtitle, CloseButton } from "../styles"; const ConnectionDetailsSection = styled.div` display: flex; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/styles.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/styles.ts new file mode 100644 index 00000000000..b16dbcf935a --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/styles.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import styled from "@emotion/styled"; +import { Button, Overlay, ThemeColors, Typography } from "@wso2/ui-toolkit"; + +export const PopupOverlay = styled(Overlay)` + z-index: 1999; +`; + +export const PopupContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 80%; + max-width: 800px; + height: 80%; + max-height: 800px; + z-index: 2000; + background-color: ${ThemeColors.SURFACE_BRIGHT}; + border-radius: 10px; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +`; + +export const PopupHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 32px; + gap: 16px; + border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; +`; + +export const BackButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + +export const HeaderTitleContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; +`; + +export const PopupTitle = styled(Typography)` + font-size: 20px; + font-weight: 600; + color: ${ThemeColors.ON_SURFACE}; + margin: 0; +`; + +export const PopupSubtitle = styled(Typography)` + font-size: 12px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin: 0; +`; + +export const CloseButton = styled(Button)` + min-width: auto; + padding: 4px; +`; + + From 66784521e426b913371303de192373c7033d60a3 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 17:30:31 +0530 Subject: [PATCH 081/102] OnSave close up all the popups --- .../src/views/BI/Connection/AddConnectionPopup/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index cb3a9d1b87d..a972678b022 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -348,14 +348,19 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { }; const handleCloseWizard = (parent?: ParentPopupData) => { - setWizardStep(null); - setSelectedConnector(null); + // If a parent payload is provided, we are done with the entire flow. + // Close this popup (and navigate back) without resetting internal state first, if (parent) { onClose?.(parent); if (onNavigateToOverview) { onNavigateToOverview(); } + return; } + + // Otherwise, just close the inner wizard and go back to the connector list. + setWizardStep(null); + setSelectedConnector(null); }; const filterItems = (items: Item[]): Item[] => { From a917820db4182b8f30eaaa6a47bab2486a8944cd Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 18:32:07 +0530 Subject: [PATCH 082/102] Update port type in introspect --- .../rpc-types/connector-wizard/interfaces.ts | 2 +- .../DatabaseConnectionPopup/index.tsx | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts index f8edbed01cd..b2a7fce447f 100644 --- a/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts +++ b/workspaces/ballerina/ballerina-core/src/rpc-types/connector-wizard/interfaces.ts @@ -47,7 +47,7 @@ export interface IntrospectDatabaseRequest { projectPath: string; dbSystem: string; host: string; - port: string; + port: number; database: string; user: string; password: string; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index ed8ab88a0d2..2ff9a1753a8 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -247,7 +247,7 @@ type DatabaseType = "PostgreSQL" | "MySQL" | "MSSQL"; interface DatabaseCredentials { databaseType: DatabaseType; host: string; - port: string; + port: number; databaseName: string; username: string; password: string; @@ -264,10 +264,10 @@ const DATABASE_TYPES: OptionProps[] = [ { id: "mssql", value: "MSSQL", content: "MSSQL" }, ]; -const DEFAULT_PORTS: Record = { - PostgreSQL: "5432", - MySQL: "3306", - MSSQL: "1433", +const DEFAULT_PORTS: Record = { + PostgreSQL: 5432, + MySQL: 3306, + MSSQL: 1433, }; export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { @@ -278,7 +278,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const [credentials, setCredentials] = useState({ databaseType: "MySQL", host: "", - port: "3306", + port: 3306, databaseName: "", username: "", password: "", @@ -304,7 +304,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { const handleCredentialsChange = (field: keyof DatabaseCredentials, value: string) => { setCredentials({ ...credentials, - [field]: value, + [field]: field === "port" ? Number(value) : value, }); // Clear error when user modifies credentials if (connectionError) { @@ -410,7 +410,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { name: connectionName, dbSystem: dbSystemMap[credentials.databaseType], host: credentials.host, - port: parseInt(credentials.port, 10), + port: credentials.port, user: credentials.username, password: credentials.password, database: credentials.databaseName, @@ -522,7 +522,7 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { id="port" label="Port" placeholder="Database port" - value={credentials.port} + value={String(credentials.port)} onTextChange={(value) => handleCredentialsChange("port", value)} /> From 8cf2881bd4e8fd0dbfb7d94a738bfb5d93c78405 Mon Sep 17 00:00:00 2001 From: madushajg Date: Mon, 15 Dec 2025 19:18:42 +0530 Subject: [PATCH 083/102] Remove unnecessary error message for empty package selection in 'run' activator --- .../ballerina-extension/src/features/bi/activator.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts index b83183eb239..ba06c56a1c7 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/bi/activator.ts @@ -64,11 +64,7 @@ export function activate(context: BallerinaExtension) { const needsPackageSelection = requiresPackageSelection( workspacePath, view, projectPath, isWebviewOpen, hasActiveTextEditor ); - - if (needsPackageSelection && projectInfo?.children.length === 0) { - window.showErrorMessage("No packages found in the workspace."); - return; - } + prepareAndGenerateConfig(context, projectPath, false, true, true, needsPackageSelection); }); From d5aab2135ab646335ad654e5465728caa53251f4 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Mon, 15 Dec 2025 19:20:05 +0530 Subject: [PATCH 084/102] Fix review suggestions --- .../Connection/AddConnectionPopup/index.tsx | 63 ++++++++++--------- .../ConnectionConfigurationPopup/index.tsx | 32 ++-------- .../DatabaseConnectionPopup/index.tsx | 6 -- .../Connection/EditConnectionPopup/index.tsx | 2 +- 4 files changed, 38 insertions(+), 65 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx index a972678b022..c6f9946ae97 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/AddConnectionPopup/index.tsx @@ -16,7 +16,7 @@ * under the License. */ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import styled from "@emotion/styled"; import { AvailableNode, Category, Item, LinePosition, ParentPopupData } from "@wso2/ballerina-core"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; @@ -201,11 +201,6 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { const [selectedConnector, setSelectedConnector] = useState(null); const [experimentalEnabled, setExperimentalEnabled] = useState(false); - useEffect(() => { - setIsSearching(true); - fetchConnectors(); - }, []); - useEffect(() => { rpcClient ?.getCommonRpcClient() @@ -217,26 +212,7 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { }); }, [rpcClient]); - useEffect(() => { - setIsSearching(true); - debouncedSearch(searchText); - return () => debouncedSearch.cancel(); - }, [searchText]); - - useEffect(() => { - if (filterType !== "All") { - setIsSearching(true); - fetchConnectors(); - } - }, [filterType]); - - rpcClient?.onProjectContentUpdated((state: boolean) => { - if (state) { - fetchConnectors(); - } - }); - - const fetchConnectors = (filter?: boolean) => { + const fetchConnectors = useCallback((filter?: boolean) => { setFetchingInfo(true); const defaultPosition: LinePosition = { line: 0, offset: 0 }; const position = target || defaultPosition; @@ -263,9 +239,14 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { setIsSearching(false); setFetchingInfo(false); }); - }; + }, [rpcClient, target, fileName, filterType]); + + useEffect(() => { + setIsSearching(true); + fetchConnectors(); + }, []); - const handleSearch = (text: string) => { + const handleSearch = useCallback((text: string) => { const defaultPosition: LinePosition = { line: 0, offset: 0 }; const position = target || defaultPosition; rpcClient @@ -315,9 +296,31 @@ export function AddConnectionPopup(props: AddConnectionPopupProps) { .finally(() => { setIsSearching(false); }); - }; + }, [rpcClient, target, fileName, filterType]); + + const debouncedSearch = useMemo( + () => debounce(handleSearch, 1100), + [handleSearch] + ); + + useEffect(() => { + setIsSearching(true); + debouncedSearch(searchText); + return () => debouncedSearch.cancel(); + }, [searchText, debouncedSearch]); - const debouncedSearch = debounce(handleSearch, 1100); + useEffect(() => { + setIsSearching(true); + fetchConnectors(); + }, [filterType, fetchConnectors]); + + useEffect(() => { + rpcClient?.onProjectContentUpdated((state: boolean) => { + if (state) { + fetchConnectors(); + } + }); + }, [rpcClient, fetchConnectors]); const handleOnSearch = (text: string) => { setSearchText(text); diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx index 1b14ede540f..166642f5056 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/ConnectionConfigurationPopup/index.tsx @@ -233,7 +233,6 @@ export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopup const [pullingStatus, setPullingStatus] = useState(undefined); const [savingFormStatus, setSavingFormStatus] = useState(undefined); const selectedNodeRef = useRef(); - const [subPanel, setSubPanel] = useState({ view: SubPanelView.UNDEFINED }); const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); useEffect(() => { @@ -364,40 +363,18 @@ export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopup console.error(">>> Error updating source code", response); setSavingFormStatus(SavingFormStatus.ERROR); } + }) + .catch((error) => { + console.error(">>> Error updating source code", error); + setSavingFormStatus(SavingFormStatus.ERROR); }); } }; - const handleSubPanel = (subPanel: SubPanel) => { - setSubPanel(subPanel); - }; - - const updateExpressionField = (data: ExpressionFormField) => { - setUpdatedExpressionField(data); - }; - const handleResetUpdatedExpressionField = () => { setUpdatedExpressionField(undefined); }; - const findSubPanelComponent = (subPanel: SubPanel) => { - switch (subPanel.view) { - case SubPanelView.HELPER_PANEL: - return ( - - ); - default: - return null; - } - }; - const getConnectorTag = () => { if (selectedConnector.codedata?.org === "ballerinax") { return "Standard"; @@ -520,7 +497,6 @@ export function ConnectionConfigurationPopup(props: ConnectionConfigurationPopup onSubmit={handleOnFormSubmit} updatedExpressionField={updatedExpressionField} resetUpdatedExpressionField={handleResetUpdatedExpressionField} - openSubPanel={handleSubPanel} isPullingConnector={savingFormStatus === SavingFormStatus.SAVING} footerActionButton={true} /> diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx index 2ff9a1753a8..c8547e1d288 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/DatabaseConnectionPopup/index.tsx @@ -363,12 +363,6 @@ export function DatabaseConnectionPopup(props: DatabaseConnectionPopupProps) { } }; - const handleTableToggle = (index: number) => { - const updatedTables = [...tables]; - updatedTables[index].selected = !updatedTables[index].selected; - setTables(updatedTables); - }; - const handleTableToggleByName = (name: string) => { setTables((prev) => prev.map((t) => diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx index bf0dad10c80..0050ab700f4 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionPopup/index.tsx @@ -136,7 +136,7 @@ export function EditConnectionPopup(props: EditConnectionPopupProps) { }; fetchConnection(); - }, [connectionName, rpcClient, onClose]); + }, [connectionName, rpcClient]); const handleClosePopup = () => { if (onClose) { From badc0966d508f47c31b126100486bd05cf567ffa Mon Sep 17 00:00:00 2001 From: madushajg Date: Mon, 15 Dec 2025 19:29:48 +0530 Subject: [PATCH 085/102] Improve 'try-it' command to work with workspaces --- .../src/features/tryit/activator.ts | 145 ++++++++++++++---- 1 file changed, 116 insertions(+), 29 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts index 0f740e5a862..5ab94f6f43f 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts @@ -17,20 +17,22 @@ */ import { commands, window, workspace, FileSystemWatcher, Disposable, Uri } from "vscode"; -import { clearTerminal, PALETTE_COMMANDS } from "../project/cmds/cmd-runner"; +import { clearTerminal, MESSAGES, PALETTE_COMMANDS } from "../project/cmds/cmd-runner"; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { BallerinaExtension } from "src/core"; import Handlebars from "handlebars"; import { clientManager, findRunningBallerinaProcesses, handleError, HTTPYAC_CONFIG_TEMPLATE, TRYIT_TEMPLATE, waitForBallerinaService } from "./utils"; -import { BIDesignModelResponse, OpenAPISpec } from "@wso2/ballerina-core"; +import { BIDesignModelResponse, EVENT_TYPE, MACHINE_VIEW, OpenAPISpec, ProjectInfo } from "@wso2/ballerina-core"; import { getProjectWorkingDirectory } from "../../utils/file-utils"; import { startDebugging } from "../editor-support/activator"; import { v4 as uuidv4 } from "uuid"; import { createGraphqlView } from "../../views/graphql"; -import { StateMachine } from "../../stateMachine"; +import { openView, StateMachine } from "../../stateMachine"; import { getCurrentProjectRoot } from "../../utils/project-utils"; +import { requiresPackageSelection, selectPackageOrPrompt } from "../../utils/command-utils"; +import { VisualizerWebview } from "../../views/visualizer/webview"; // File constants const FILE_NAMES = { @@ -68,33 +70,14 @@ async function openTryItView(withNotice: boolean = false, resourceMetadata?: Res throw new Error('Ballerina Language Server is not connected'); } - const currentProjectRoot = await getCurrentProjectRoot(); - if (!currentProjectRoot) { - throw new Error('Please open a workspace first'); - } - - // If currentProjectRoot is a file (single file project), use its directory - // Otherwise, use the current project root - let projectPath: string; - try { - projectPath = getProjectWorkingDirectory(currentProjectRoot); - } catch (error) { - throw new Error(`Failed to determine working directory`); - } - - let services: ServiceInfo[] | null = await getAvailableServices(projectPath); - - // if the getDesignModel() LS API is unavailable, create a ServiceInfo from ServiceMetadata to support Try It functionality. (a fallback logic for Ballerina versions prior to 2201.12.x) - if (services == null && serviceMetadata && filePath) { - const service = createServiceInfoFromMetadata(serviceMetadata, projectPath, filePath); - services = [service]; - } - - if (!services || services.length === 0) { - vscode.window.showInformationMessage('No services found in the project'); + const projectAndServices = await getProjectPathAndServices(serviceMetadata, filePath); + + if (!projectAndServices) { return; } + const { projectPath, services } = projectAndServices; + if (withNotice) { const selection = await vscode.window.showInformationMessage( `${services.length} service${services.length === 1 ? '' : 's'} found in the integration. Test with Try It Client?`, @@ -303,7 +286,6 @@ async function findServiceForResource(services: ServiceInfo[], resourceMetadata: async function getAvailableServices(projectDir: string): Promise { try { - // const langClient = clientManager.getClient(); const langClient = StateMachine.langClient(); const response: BIDesignModelResponse = await langClient.getDesignModel({ @@ -566,13 +548,30 @@ async function checkBallerinaProcessRunning(projectDir: string): Promise 0) { + const project = projectInfo.children.find((child: ProjectInfo) => child.projectPath === projectDir); + projectName = project?.title || project?.name || ''; + } + const selection = await vscode.window.showWarningMessage( - 'The "Try It" feature requires a running Ballerina service. Would you like to run the integration first?', + `The "Try It" feature requires a running Ballerina service. + Would you like to run ${projectName ? `'${projectName}'` : 'the integration'} now?`, 'Run Integration', 'Cancel' ); if (selection === 'Run Integration') { + const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; + const needsPackageSelection = requiresPackageSelection(workspacePath, webviewType, projectDir, isWebviewOpen, false); + + // Open the package overview view if the command is executed from workspace overview + if (isWebviewOpen && needsPackageSelection) { + openView(EVENT_TYPE.OPEN_VIEW, { view: MACHINE_VIEW.PackageOverview, projectPath: projectDir }); + } + // Execute the run command clearTerminal(); await startDebugging(Uri.file(projectDir), false, false, true); @@ -1121,3 +1120,91 @@ function createServiceInfoFromMetadata(serviceMetadata: ServiceMetadata, workspa } }; } + +async function getProjectRoots(): Promise { + const context = StateMachine.context(); + const { workspacePath, view: webviewType, projectPath, projectInfo } = context; + const isWebviewOpen = VisualizerWebview.currentPanel !== undefined; + const hasActiveTextEditor = !!window.activeTextEditor; + + if (requiresPackageSelection(workspacePath, webviewType, projectPath, isWebviewOpen, hasActiveTextEditor)) { + return projectInfo?.children.map((child: any) => child.projectPath) ?? []; + } + + return [await getCurrentProjectRoot()]; +} + +async function getProjectPathAndServices( + serviceMetadata?: ServiceMetadata, + filePath?: string +): Promise<{ projectPath: string, services: ServiceInfo[] } | undefined> { + const currentProjectRoots = await getProjectRoots(); + if (!currentProjectRoots || currentProjectRoots.length === 0) { + throw new Error(MESSAGES.NO_PROJECT_FOUND); + } + + let projectPath: string; + const serviceInfos: Record = {}; + + if (currentProjectRoots.length === 1) { + // If currentProjectRoot is a file (single file project), use its directory + // Otherwise, use the current project root + try { + projectPath = getProjectWorkingDirectory(currentProjectRoots[0]); + const services = await getServiceInfo(projectPath, serviceMetadata, filePath); + if (!services || services.length === 0) { + vscode.window.showInformationMessage('No services found in the integration'); + return; + } + serviceInfos[currentProjectRoots[0]] = services; + } catch (error) { + throw new Error(`Failed to determine working directory`); + } + } else { + for (const projectRoot of currentProjectRoots) { + const services = await getServiceInfo(projectRoot, serviceMetadata, filePath); + if (services && services.length > 0) { + serviceInfos[projectRoot] = services; + } + } + + if (Object.keys(serviceInfos).length === 0) { + vscode.window.showInformationMessage('None of the integrations contain services'); + return; + } + if (Object.keys(serviceInfos).length === 1) { + projectPath = Object.keys(serviceInfos)[0]; + } + if (Object.keys(serviceInfos).length > 1) { + const selectedProjectRoot = await selectPackageOrPrompt( + Object.keys(serviceInfos), + "Multiple integrations contain services. Please select one." + ); + if (!selectedProjectRoot) { + return; + } + + await StateMachine.updateProjectRootAndInfo(selectedProjectRoot, StateMachine.context().projectInfo); + projectPath = selectedProjectRoot; + } + } + + return { projectPath: projectPath, services: serviceInfos[projectPath] }; +} + +async function getServiceInfo( + projectPath: string, + serviceMetadata?: ServiceMetadata, + filePath?: string +): Promise { + let services: ServiceInfo[] | null = await getAvailableServices(projectPath); + + // if the getDesignModel() LS API is unavailable, create a ServiceInfo from ServiceMetadata + // to support Try It functionality. (a fallback logic for Ballerina versions prior to 2201.12.x) + if (services == null && serviceMetadata && filePath) { + const service = createServiceInfoFromMetadata(serviceMetadata, projectPath, filePath); + services = [service]; + } + + return services; +} From 9bec62668b5c37eabfed7a5276227bfce2191ed9 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 15 Dec 2025 22:25:26 +0530 Subject: [PATCH 086/102] Add cancel sign-in command and handle pending session creation --- .../wso2-platform-core/src/constants.ts | 1 + .../src/auth/wso2-auth-provider.ts | 76 +++++++++++++++---- .../src/cmds/sign-in-cmd.ts | 7 ++ 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-core/src/constants.ts b/workspaces/wso2-platform/wso2-platform-core/src/constants.ts index 06f83669ce6..1c6e84d0d5f 100644 --- a/workspaces/wso2-platform/wso2-platform-core/src/constants.ts +++ b/workspaces/wso2-platform/wso2-platform-core/src/constants.ts @@ -22,6 +22,7 @@ export const CommandIds = { SignIn: "wso2.wso2-platform.sign.in", SignInWithAuthCode: "wso2.wso2-platform.sign.in.with.authCode", SignOut: "wso2.wso2-platform.sign.out", + CancelSignIn: "wso2.wso2-platform.cancel.sign.in", AddComponent: "wso2.wso2-platform.add.component", CreateNewComponent: "wso2.wso2-platform.create.component", DeleteComponent: "wso2.wso2-platform.delete.component", diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts index bad8b1f7a26..a360a4f4171 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -16,12 +16,13 @@ * under the License. */ -import type { AuthState, UserInfo } from "@wso2/wso2-platform-core"; +import { CommandIds, type AuthState, type UserInfo } from "@wso2/wso2-platform-core"; import { type AuthenticationProvider, type AuthenticationProviderAuthenticationSessionsChangeEvent, type AuthenticationProviderSessionOptions, type AuthenticationSession, + commands, Disposable, EventEmitter, type SecretStorage, @@ -52,9 +53,14 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo private _stateChangeEmitter = new EventEmitter<{ state: AuthState }>(); private _disposable: Disposable; private _state: AuthState = { userInfo: null, region: "US" }; + private _pendingSessionCreation: { resolve: (session: AuthenticationSession) => void; reject: (error: Error) => void; timeout: NodeJS.Timeout } | undefined; constructor(private readonly secretStorage: SecretStorage) { - this._disposable = Disposable.from(this._sessionChangeEmitter, this._stateChangeEmitter); + console.log("WSO2AuthenticationProvider initialized"); + this._disposable = Disposable.from( + this._sessionChangeEmitter, + this._stateChangeEmitter + ); } get onDidChangeSessions() { @@ -106,11 +112,48 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo } /** - * Create a new auth session - NOT USED (auth is handled by RPC) - * This is required by the AuthenticationProvider interface but not called directly + * Cancel any pending session creation + * This is called when user initiates a new sign-in while one is already pending */ - public async createSession(scopes: string[]): Promise { - throw new Error("Direct session creation not supported. Use RPC authentication flow."); + public cancelPendingSessionCreation() { + if (this._pendingSessionCreation) { + console.log("Cancelling pending session creation"); + clearTimeout(this._pendingSessionCreation.timeout); + const oldPending = this._pendingSessionCreation; + this._pendingSessionCreation = undefined; + oldPending.reject(new Error("Sign-in cancelled")); + } + } + + /** + * Create a new auth session by triggering the sign-in flow + * This is called when user clicks "Sign in" in VS Code's Accounts menu + */ + public async createSession(scopes: string[], options?: AuthenticationProviderSessionOptions): Promise { + console.log("Creating new auth session via VS Code Accounts menu"); + const customOptions = options as any; + const platform = customOptions?.platform; + + commands.executeCommand(CommandIds.SignIn, { + extName: platform, + }).then(undefined, (error) => { + console.error("Sign-in command failed:", error); + }); + + // Return a promise that will be resolved when login succeeds or timeout occurs + return new Promise((resolve, reject) => { + // Set up timeout + const timeout = setTimeout(() => { + console.log("Sign-in timeout reached"); + if (this._pendingSessionCreation) { + this._pendingSessionCreation = undefined; + reject(new Error("Sign-in timeout: User did not complete authentication within 2 minutes")); + } + }, 2 * 60 * 1000); // 2 minutes + + // Store the promise handlers so we can resolve/reject from loginSuccess + this._pendingSessionCreation = { resolve, reject, timeout }; + }); } /** @@ -133,10 +176,17 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo contextStore.getState().refreshState(); // Store session in secure storage - await this.storeSession(userInfo, region); + const session = await this.storeSession(userInfo, region); // Notify subscribers this._stateChangeEmitter.fire({ state: this._state }); + + // Resolve pending session creation if there is one + if (this._pendingSessionCreation) { + clearTimeout(this._pendingSessionCreation.timeout); + this._pendingSessionCreation.resolve(session); + this._pendingSessionCreation = undefined; + } } /** @@ -144,7 +194,7 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo */ public async logout(silent = false, skipClearSessions = false) { getLogger().debug("Signing out from WSO2 Platform"); - + // Call RPC signOut first try { await ext.clients.rpcClient.signOut(); @@ -222,10 +272,10 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo await this.storeSessions([session], sessionData); - this._sessionChangeEmitter.fire({ - added: [session], - removed: removedSessions, - changed: [] + this._sessionChangeEmitter.fire({ + added: [session], + removed: removedSessions, + changed: [] }); return session; @@ -246,7 +296,7 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo allSessions.splice(sessionIdx, 1); await this.storeSessions(allSessions); this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); - + // Trigger full logout flow (skipClearSessions=true to avoid loop) await this.logout(false, true); } diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts index 589900bb5b1..306f4e58ff8 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/cmds/sign-in-cmd.ts @@ -30,6 +30,8 @@ export function signInCommand(context: ExtensionContext) { setExtensionName(params?.extName); try { isRpcActive(ext); + // Cancel any pending session creation from accounts menu + ext.authProvider?.cancelPendingSessionCreation(); getLogger().debug("Signing in to WSO2 Platform"); const callbackUrl = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://wso2.wso2-platform/signin`)); @@ -54,5 +56,10 @@ export function signInCommand(context: ExtensionContext) { } } }), + // Register cancellation command + commands.registerCommand(CommandIds.CancelSignIn, () => { + console.log("Cancelling pending session creation via command"); + ext.authProvider?.cancelPendingSessionCreation(); + }) ); } From c2defdb3f34461a82efd4c2d6ed3bf84622202d9 Mon Sep 17 00:00:00 2001 From: gigara Date: Mon, 15 Dec 2025 22:38:13 +0530 Subject: [PATCH 087/102] Reorder sign-in command execution to ensure proper session handling --- .../src/auth/wso2-auth-provider.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts index a360a4f4171..f7bf750fb79 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts +++ b/workspaces/wso2-platform/wso2-platform-extension/src/auth/wso2-auth-provider.ts @@ -134,14 +134,14 @@ export class WSO2AuthenticationProvider implements AuthenticationProvider, Dispo const customOptions = options as any; const platform = customOptions?.platform; - commands.executeCommand(CommandIds.SignIn, { - extName: platform, - }).then(undefined, (error) => { - console.error("Sign-in command failed:", error); - }); - // Return a promise that will be resolved when login succeeds or timeout occurs return new Promise((resolve, reject) => { + commands.executeCommand(CommandIds.SignIn, { + extName: platform, + }).then(undefined, (error) => { + console.error("Sign-in command failed:", error); + }); + // Set up timeout const timeout = setTimeout(() => { console.log("Sign-in timeout reached"); From 6baf7238a61af266d20767e0ef3c9ed2cff387aa Mon Sep 17 00:00:00 2001 From: madushajg Date: Mon, 15 Dec 2025 23:42:36 +0530 Subject: [PATCH 088/102] Address review suggestions --- .../ballerina-extension/src/features/tryit/activator.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts index 5ab94f6f43f..0dbd81a42cd 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/tryit/activator.ts @@ -1131,7 +1131,8 @@ async function getProjectRoots(): Promise { return projectInfo?.children.map((child: any) => child.projectPath) ?? []; } - return [await getCurrentProjectRoot()]; + const currentRoot = await getCurrentProjectRoot(); + return currentRoot ? [currentRoot] : []; } async function getProjectPathAndServices( @@ -1150,13 +1151,14 @@ async function getProjectPathAndServices( // If currentProjectRoot is a file (single file project), use its directory // Otherwise, use the current project root try { - projectPath = getProjectWorkingDirectory(currentProjectRoots[0]); + const root = currentProjectRoots[0]; + projectPath = getProjectWorkingDirectory(root); const services = await getServiceInfo(projectPath, serviceMetadata, filePath); if (!services || services.length === 0) { vscode.window.showInformationMessage('No services found in the integration'); return; } - serviceInfos[currentProjectRoots[0]] = services; + serviceInfos[projectPath] = services; } catch (error) { throw new Error(`Failed to determine working directory`); } From 6fdb17e1da34865b6b195f804400ea4a9e08b7a8 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 8 Dec 2025 15:50:56 +0530 Subject: [PATCH 089/102] Update MCP tool form to allow selecting auth and included tools --- .../ballerina-core/src/interfaces/bi.ts | 1 + .../src/views/BI/AIChatAgent/AddMcpServer.tsx | 93 ++++++--- .../{ => Mcp}/McpToolsSelection.tsx | 176 ++++++++++-------- .../AIChatAgent/Mcp/RequiresAuthCheckbox.tsx | 76 ++++++++ .../src/views/BI/AIChatAgent/utils.ts | 5 +- .../views/BI/Forms/FormGenerator/index.tsx | 37 ++-- 6 files changed, 276 insertions(+), 112 deletions(-) rename workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/{ => Mcp}/McpToolsSelection.tsx (72%) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts index cda639857b3..31cda081952 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts @@ -336,6 +336,7 @@ export type DiagramLabel = "On Fail" | "Body"; export type NodePropertyKey = | "agentType" + | "auth" | "checkError" | "client" | "collection" diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx index 51fb1774a2f..1c143780047 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx @@ -13,7 +13,8 @@ import { debounce } from "lodash"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { RelativeLoader } from "../../../components/RelativeLoader"; import FormGenerator from "../Forms/FormGenerator"; -import { McpToolsSelection } from "./McpToolsSelection"; +import { McpToolsSelection } from "./Mcp/McpToolsSelection"; +import { RequiresAuthCheckbox } from "./Mcp/RequiresAuthCheckbox"; import { cleanServerUrl } from "./formUtils"; import { Container, LoaderContainer } from "./styles"; import { extractAccessToken, findAgentNodeFromAgentCallNode, getAgentFilePath, getEndOfFileLineRange, parseToolsString, resolveVariableValue, resolveAuthConfig } from "./utils"; @@ -40,6 +41,8 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const [serverUrl, setServerUrl] = useState(""); const [auth, setAuth] = useState(""); + const [requiresAuth, setRequiresAuth] = useState(false); + const [toolsInclude, setToolsInclude] = useState("all"); const [availableMcpTools, setAvailableMcpTools] = useState([]); const [selectedMcpTools, setSelectedMcpTools] = useState>(new Set()); @@ -210,8 +213,36 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { ); useEffect(() => { - debouncedFetchTools(serverUrl, auth); - }, [serverUrl, auth, debouncedFetchTools]); + // Only fetch tools when "Selected" mode is active + if (toolsInclude === "selected") { + debouncedFetchTools(serverUrl, auth); + } else { + // Clear tools and reset state when "All" is selected + setAvailableMcpTools([]); + setSelectedMcpTools(new Set()); + setLoadingMcpTools(false); + setMcpToolsError(""); + } + }, [serverUrl, auth, toolsInclude, debouncedFetchTools, requiresAuth]); + + useEffect(() => { + if (!isLoading && editMode && mcpToolKitNodeRef.current) { + const authValue = mcpToolKitNodeRef.current.properties?.auth?.value; + if (authValue && typeof authValue === 'string' && authValue.trim()) { + setRequiresAuth(true); + } + } + }, [isLoading, editMode]); + + useEffect(() => { + // Clear auth field value when requiresAuth is unchecked + if (!requiresAuth && auth) { + setAuth(""); + if (formRef.current?.setFieldValue) { + formRef.current.setFieldValue(AUTH_FIELD_KEY, ""); + } + } + }, [requiresAuth, auth]); const handleToolSelectionChange = useCallback((toolName: string, isSelected: boolean) => { setSelectedMcpTools(prev => { @@ -257,23 +288,35 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { }, [availableMcpTools.length, selectedMcpTools.size]); const injectedComponents = useMemo(() => { - return [{ - component: ( - - ), - index: 1 - }]; - }, [availableMcpTools, selectedMcpTools, loadingMcpTools, mcpToolsError, serverUrl, handleToolSelectionChange, handleSelectAllTools, isSaveDisabled]); + return [ + { + component: ( + + ), + index: 1 + }, + { + component: ( + + ), + index: 2 + }]; + }, [availableMcpTools, selectedMcpTools, loadingMcpTools, mcpToolsError, serverUrl, handleToolSelectionChange, handleSelectAllTools, isSaveDisabled, requiresAuth, toolsInclude]); return ( @@ -303,7 +346,15 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { showProgressIndicator={isSaving} disableSaveButton={isSaveDisabled} injectedComponents={injectedComponents} - fieldPriority={{ auth: 1 }} + fieldOverrides={useMemo(() => ({ + auth: { + advanced: false, + hidden: !requiresAuth + }, + toolKitName: { + advanced: true, + } + }), [requiresAuth])} /> )} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/McpToolsSelection.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx similarity index 72% rename from workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/McpToolsSelection.tsx rename to workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx index 71f1212cf1b..cd8bb963ba3 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/McpToolsSelection.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx @@ -16,10 +16,11 @@ * under the License. */ -import React, { useState, useMemo } from "react"; +import React, { useState, useMemo, useEffect } from "react"; import { createPortal } from "react-dom"; import styled from "@emotion/styled"; -import { Button, CheckBox, ThemeColors, SearchBox, Codicon, Divider, Typography } from "@wso2/ui-toolkit"; +import { Button, CheckBox, ThemeColors, SearchBox, Codicon, Divider, Typography, Dropdown } from "@wso2/ui-toolkit"; +import type { OptionProps } from "@wso2/ui-toolkit"; export interface McpTool { name: string; @@ -29,7 +30,7 @@ export interface McpTool { // Utility function to clean up error messages const formatErrorMessage = (error: string): string => { if (!error) return error; - + // Check if it's an HTML response (GitHub 404, etc.) if (error.includes('')) { // Try to extract meaningful info from HTML @@ -46,17 +47,17 @@ const formatErrorMessage = (error: string): string => { } return 'The server returned an HTML page instead of MCP tools. Please verify the URL is correct.'; } - + // Check for network errors if (error.includes('Network error') || error.includes('Failed to fetch')) { return 'Network error. Please check your connection and the server URL.'; } - + // Truncate very long error messages if (error.length > 500) { return error.substring(0, 500) + '...'; } - + return error; }; @@ -69,6 +70,8 @@ interface McpToolsSelectionProps { onSelectAll: () => void; serviceUrl?: string; showValidationError?: boolean; + toolsInclude?: string; + onToolsIncludeChange?: (value: string) => void; } interface ToolsListProps { @@ -418,80 +421,107 @@ export const McpToolsSelection: React.FC = ({ onToolSelectionChange, onSelectAll, serviceUrl, - showValidationError = false + showValidationError = false, + toolsInclude = "all", + onToolsIncludeChange }) => { const [isModalOpen, setIsModalOpen] = useState(false); const formattedError = useMemo(() => formatErrorMessage(error), [error]); + const toolsIncludeOptions: OptionProps[] = [ + { id: "all", content: "All", value: "all" }, + { id: "selected", content: "Selected", value: "selected" } + ]; + + useEffect(() => { + if (toolsInclude === "all" && selectedTools.size > 0) { + // Clear all selections + selectedTools.forEach(toolName => { + onToolSelectionChange(toolName, false); + }); + } + }, [toolsInclude, selectedTools, onToolSelectionChange]); + return ( <> - - - Available Tools -
- {tools.length > 0 && ( - <> - setIsModalOpen(true)} - title="Expand view" - aria-label="Expand tools selection" - > - - - - - )} -
-
- {loading && ( - - - Loading tools from MCP server... - - )} - {error && ( - <> - - Unable to load tools from MCP server. - - {formattedError} - - )} - {!loading && tools.length > 0 && ( - <> - {showValidationError && selectedTools.size === 0 ? ( - - Select at least one tool to continue - - ) : ( - - {selectedTools.size} of {tools.length} selected + onToolsIncludeChange?.(value)} + containerSx={{ width: "100%" }} + /> + + {toolsInclude === "selected" && ( + + + Available Tools +
+ {tools.length > 0 && ( + <> + setIsModalOpen(true)} + title="Expand view" + aria-label="Expand tools selection" + > + + + + + )} +
+
+ {loading && ( + + + Loading tools from MCP server... + + )} + {error && ( + <> + + Unable to load tools from MCP server. - )} - - - )} - {!loading && !error && tools.length === 0 && serviceUrl?.trim() && ( - - No tools available from this MCP server - - )} - {!loading && !error && tools.length === 0 && !serviceUrl?.trim() && ( - - Enter a server URL to view available tools - - )} -
+ {formattedError} + + )} + {!loading && tools.length > 0 && ( + <> + {showValidationError && selectedTools.size === 0 ? ( + + Select at least one tool to continue + + ) : ( + + {selectedTools.size} of {tools.length} selected + + )} + + + )} + {!loading && !error && tools.length === 0 && serviceUrl?.trim() && ( + + No tools available from this MCP server + + )} + {!loading && !error && tools.length === 0 && !serviceUrl?.trim() && ( + + Enter a server URL to view available tools + + )} +
+ )} void; +} + +export const RequiresAuthCheckbox: React.FC = ({ checked, onChange }) => { + const handleToggle = () => { + onChange(!checked); + }; + + return ( + +
+ Requires Authentication + + Enable if the server requires authentication + +
+ + + +
+ ); +}; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts index 2470e39b8e1..00f3c190033 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts @@ -566,7 +566,7 @@ export const getEndOfFileLineRange = async ( */ export const isStringLiteral = (value: string): boolean => { const trimmed = value.trim(); - return trimmed.startsWith('"') && trimmed.endsWith('"'); + return (trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("string `") && trimmed.endsWith("`")); }; /** @@ -575,6 +575,9 @@ export const isStringLiteral = (value: string): boolean => { export const removeQuotes = (value: string): string => { const trimmed = value.trim(); if (isStringLiteral(trimmed)) { + if (trimmed.startsWith("string `") && trimmed.endsWith("`")) { + return trimmed.substring(8, trimmed.length - 1); + } return trimmed.substring(1, trimmed.length - 1); } return trimmed; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx index bfc51e12328..a36076393ef 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx @@ -222,13 +222,26 @@ export const FormGenerator = forwardRef(func } = props; const { rpcClient } = useRpcContext(); - const [fields, setFields] = useState([]); + const [baseFields, setBaseFields] = useState([]); const [formImports, setFormImports] = useState({}); const [typeEditorState, setTypeEditorState] = useState({ isOpen: false, newTypeValue: "" }); const [visualizableField, setVisualizableField] = useState(); const [recordTypeFields, setRecordTypeFields] = useState([]); const [valueTypeConstraints, setValueTypeConstraints] = useState(); + const fields = useMemo(() => { + if (!props.fieldOverrides || baseFields.length === 0) { + return baseFields; + } + return baseFields.map(field => { + const override = props.fieldOverrides[field.key]; + if (override) { + return { ...field, ...override }; + } + return field; + }); + }, [baseFields, props.fieldOverrides]); + /* Expression editor related state and ref variables */ const prevCompletionFetchText = useRef(""); const [completions, setCompletions] = useState([]); @@ -357,6 +370,7 @@ export const FormGenerator = forwardRef(func }; }, [node]); + const handleFormOpen = () => { rpcClient .getBIDiagramRpcClient() @@ -437,21 +451,10 @@ export const FormGenerator = forwardRef(func setRecordTypeFields(recordTypeFields); // get node properties - let fields = convertNodePropertiesToFormFields(enrichedNodeProperties || formProperties, connections, clientName); - - // Apply field overrides if provided - if (props.fieldOverrides) { - fields = fields.map(field => { - const override = props.fieldOverrides[field.key]; - if (override) { - return { ...field, ...override }; - } - return field; - }); - } + const fields = convertNodePropertiesToFormFields(enrichedNodeProperties || formProperties, connections, clientName); const sortedFields = sortFieldsByPriority(fields); - setFields(sortedFields); + setBaseFields(sortedFields); setFormImports(getImportsForFormFields(sortedFields)); }; @@ -474,7 +477,7 @@ export const FormGenerator = forwardRef(func } return updatedField; }); - setFields(updatedFields); + setBaseFields(updatedFields); } const handleOnSubmit = (data: FormValues, dirtyFields: any) => { @@ -526,7 +529,7 @@ export const FormGenerator = forwardRef(func } return updatedField; }); - setFields(updatedFields); + setBaseFields(updatedFields); setTypeEditorState({ isOpen, fieldKey: editingField?.key, newTypeValue: f[editingField?.key] }); }; @@ -844,7 +847,7 @@ export const FormGenerator = forwardRef(func if (type.codedata.node === "RECORD") { handleSelectedTypeChange(convertRecordTypeToCompletionItem(type)); } - setFields(updatedFields); + setBaseFields(updatedFields); }; const handleValueTypeConstChange = async (valueTypeConstraint: string) => { From 06670180eeb5df522353d7ad0ff5bc87505162b7 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Tue, 9 Dec 2025 13:56:15 +0530 Subject: [PATCH 090/102] Update stepper styles --- .../src/components/Stepper/CompletedStepCard.tsx | 8 ++++---- .../src/components/Stepper/IncompleteStepCard.tsx | 12 ++++++------ .../ui-toolkit/src/components/Stepper/Stepper.tsx | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/workspaces/common-libs/ui-toolkit/src/components/Stepper/CompletedStepCard.tsx b/workspaces/common-libs/ui-toolkit/src/components/Stepper/CompletedStepCard.tsx index c1e79ac5ae4..94a7083f916 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/Stepper/CompletedStepCard.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/Stepper/CompletedStepCard.tsx @@ -33,10 +33,10 @@ export const CompletedStepCard: React.FC = (props: StepCardProps) {props.titleAlignment === "right" ? ( <> - + - + {props.step.title} {props.totalSteps === props.step.id + 1 ? null : } @@ -44,11 +44,11 @@ export const CompletedStepCard: React.FC = (props: StepCardProps) ) : <> - + {props.totalSteps === props.step.id + 1 ? null : } - + {props.step.title} diff --git a/workspaces/common-libs/ui-toolkit/src/components/Stepper/IncompleteStepCard.tsx b/workspaces/common-libs/ui-toolkit/src/components/Stepper/IncompleteStepCard.tsx index 1587dc2831a..fc9ff246eb3 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/Stepper/IncompleteStepCard.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/Stepper/IncompleteStepCard.tsx @@ -21,28 +21,28 @@ import styled from "@emotion/styled"; import { colors } from "../Commons/Colors"; const StepNumber = styled.div` - color: var(--vscode-editor-background); + color: ${(props: { color?: string; }) => props.color}; `; export const InCompletedStepCard: React.FC = (props: StepCardProps) => ( {props.titleAlignment === "right" ? ( <> - - + + {props.step.id + 1} {props.step.title} - {(props.totalSteps === props.step.id + 1) ? null : } + {(props.totalSteps === props.step.id + 1) ? null : } ) : <> - - + + {props.step.id + 1} diff --git a/workspaces/common-libs/ui-toolkit/src/components/Stepper/Stepper.tsx b/workspaces/common-libs/ui-toolkit/src/components/Stepper/Stepper.tsx index 096801a4e08..cea5d0ba06d 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/Stepper/Stepper.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/Stepper/Stepper.tsx @@ -113,6 +113,7 @@ export const StepCircle = styled.div` min-height: 24px; border-radius: 50%; flex-shrink: 0; + border: 1px solid var(--button-border); `; export const HorizontalBar = styled.div` @@ -159,7 +160,7 @@ export const Stepper: React.FC = (props: StepperProps) => { }; if (id < currentStep) { return ; - } + } return ; })} From df711ed69b429e8cf346196b8d061219b03dc277 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Wed, 10 Dec 2025 18:23:00 +0530 Subject: [PATCH 091/102] Add discover tools feature to Agent MCP form --- .../src/views/BI/AIChatAgent/AddMcpServer.tsx | 232 +++++++-- .../BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx | 477 ++++++++++++++++++ .../BI/AIChatAgent/Mcp/McpToolsSelection.tsx | 89 +++- .../AIChatAgent/Mcp/RequiresAuthCheckbox.tsx | 1 + .../BI/AIChatAgent/Mcp/mcpEditModeUtils.ts | 109 ++++ .../src/views/BI/AIChatAgent/formUtils.ts | 1 + .../src/views/BI/AIChatAgent/utils.ts | 22 +- .../src/icons/bi-ai-search.svg | 6 + .../src/icons/bi-expand-modal.svg | 6 + 9 files changed, 861 insertions(+), 82 deletions(-) create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx create mode 100644 workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts create mode 100644 workspaces/common-libs/font-wso2-vscode/src/icons/bi-ai-search.svg create mode 100644 workspaces/common-libs/font-wso2-vscode/src/icons/bi-expand-modal.svg diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx index 1c143780047..b9eba74ffa9 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx @@ -14,10 +14,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { RelativeLoader } from "../../../components/RelativeLoader"; import FormGenerator from "../Forms/FormGenerator"; import { McpToolsSelection } from "./Mcp/McpToolsSelection"; +import { DiscoverToolsModal } from "./Mcp/DiscoverToolsModal"; import { RequiresAuthCheckbox } from "./Mcp/RequiresAuthCheckbox"; +import { attemptValueResolution, createMockTools, extractOriginalValues } from "./Mcp/mcpEditModeUtils"; import { cleanServerUrl } from "./formUtils"; import { Container, LoaderContainer } from "./styles"; -import { extractAccessToken, findAgentNodeFromAgentCallNode, getAgentFilePath, getEndOfFileLineRange, parseToolsString, resolveVariableValue, resolveAuthConfig } from "./utils"; +import { extractAccessToken, findAgentNodeFromAgentCallNode, getAgentFilePath, getEndOfFileLineRange, resolveVariableValue, resolveAuthConfig } from "./utils"; interface Tool { name: string; @@ -51,6 +53,15 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); + const [showDiscoverModal, setShowDiscoverModal] = useState(false); + + // Edit mode tracking + // Guards edit mode initialization - prevents fetch effect from running too early + const [resolutionAttempted, setResolutionAttempted] = useState(false); + // Tracks resolution errors - used to show "Discover Tools" button when variables can't be resolved + const [resolutionError, setResolutionError] = useState(""); + // Tracks where tools came from - used by McpToolsSelection for UI logic + const [toolSource, setToolSource] = useState<'auto-fetched' | 'manual-discovery' | 'saved-mock' | null>(null); const mcpToolKitNodeTemplateRef = useRef(null); const mcpToolKitNodeRef = useRef(null); @@ -121,35 +132,48 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { setIsLoading(false); }, [editMode, rpcClient]); - const fetchToolListFromMcpServer = useCallback(async (url: string, authValue: string = "") => { - // Resolve the URL variable if needed - const resolvedUrl = await resolveVariableValue( - url, - moduleVariablesRef.current, - rpcClient, - projectPathUriRef.current - ); + const fetchToolsFromServer = useCallback(async ( + url: string, + authValue: string = "", + options?: { + preselectTools?: string[], // If provided, only these tools will be selected; otherwise all tools are selected + skipResolution?: boolean // If true, skip variable resolution (already resolved) + } + ) => { + // Resolve the URL variable if needed (unless already resolved) + const resolvedUrl = options?.skipResolution + ? url + : await resolveVariableValue(url, moduleVariablesRef.current, rpcClient, projectPathUriRef.current); const cleanUrl = cleanServerUrl(resolvedUrl); - if (!cleanUrl) { + if (cleanUrl === null) { + setMcpToolsError(""); + if (url === resolvedUrl && url.trim()) { + setResolutionError("Unable to resolve Server URL at design time. Tools cannot be auto-fetched with the current configuration."); + } + setLoadingMcpTools(false); + return []; + } + + // Resolve auth config variables if needed (unless already resolved) + const resolvedAuthValue = options?.skipResolution + ? authValue + : await resolveAuthConfig(authValue, moduleVariablesRef.current, rpcClient, projectPathUriRef.current); + + const accessToken = extractAccessToken(resolvedAuthValue); + + if (requiresAuth && accessToken === null) { + setMcpToolsError(""); + setResolutionError("Unable to resolve authentication configuration at design time. Tools cannot be auto-fetched with the current configuration."); + setLoadingMcpTools(false); return []; } - // Reset state setAvailableMcpTools([]); setSelectedMcpTools(new Set()); setLoadingMcpTools(true); setMcpToolsError(""); - - // Resolve auth config variables if needed - const resolvedAuthValue = await resolveAuthConfig( - authValue, - moduleVariablesRef.current, - rpcClient, - projectPathUriRef.current - ); - - const accessToken = extractAccessToken(resolvedAuthValue); + setResolutionError(""); try { const response = await rpcClient.getAIAgentRpcClient().getMcpTools({ @@ -172,18 +196,20 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { setAvailableMcpTools(response.tools); - // Restore previously selected tools if in edit mode and URL matches - const shouldRestoreTools = editMode && url === mcpToolKitNodeRef.current?.properties?.serverUrl?.value; - const permittedToolsValue = mcpToolKitNodeRef.current?.properties?.permittedTools?.value; - if (shouldRestoreTools && permittedToolsValue) { - const permittedTools = parseToolsString(permittedToolsValue as string, true); - setSelectedMcpTools(new Set(permittedTools)); + // Select tools based on options + if (options?.preselectTools) { + // Only select tools that match the preselected names and exist in the fetched tools + const toolsToSelect = response.tools + .filter(tool => options.preselectTools.includes(tool.name)) + .map(tool => tool.name); + setSelectedMcpTools(new Set(toolsToSelect)); } else { - // Select all tools by default when not in edit mode or when URL changed + // Select all tools by default setSelectedMcpTools(new Set(response.tools.map(tool => tool.name))); } setLoadingMcpTools(false); + setToolSource('auto-fetched'); return response.tools; } catch (error) { console.error(`Failed to fetch tools from MCP server: ${error || 'Unknown error'}`); @@ -192,7 +218,7 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { setLoadingMcpTools(false); return []; } - }, [editMode, rpcClient]); + }, [rpcClient, requiresAuth]); useEffect(() => { initPanel(); @@ -201,7 +227,7 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const debouncedFetchTools = useMemo( () => debounce((url: string, authValue: string) => { if (url.trim()) { - fetchToolListFromMcpServer(url, authValue); + fetchToolsFromServer(url, authValue); } else { setAvailableMcpTools([]); setSelectedMcpTools(new Set()); @@ -209,30 +235,36 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { setMcpToolsError(""); } }, 500), - [fetchToolListFromMcpServer] + [fetchToolsFromServer] ); useEffect(() => { - // Only fetch tools when "Selected" mode is active - if (toolsInclude === "selected") { - debouncedFetchTools(serverUrl, auth); - } else { + // Guard 1: Only when "selected" mode + if (toolsInclude !== "selected") { // Clear tools and reset state when "All" is selected setAvailableMcpTools([]); setSelectedMcpTools(new Set()); setLoadingMcpTools(false); setMcpToolsError(""); + setResolutionError(""); + setToolSource(null); + return; } - }, [serverUrl, auth, toolsInclude, debouncedFetchTools, requiresAuth]); - useEffect(() => { - if (!isLoading && editMode && mcpToolKitNodeRef.current) { - const authValue = mcpToolKitNodeRef.current.properties?.auth?.value; - if (authValue && typeof authValue === 'string' && authValue.trim()) { - setRequiresAuth(true); - } + // Guard 2: Skip during edit mode initialization (before resolution is attempted) + if (editMode && !resolutionAttempted) { + return; + } + + // Guard 3: Skip if tools were just loaded from initialization (user hasn't modified form yet) + // This prevents re-fetching when edit mode loads existing tools or tools were manually discovered + if (toolSource === 'saved-mock' || toolSource === 'auto-fetched' || toolSource === 'manual-discovery') { + return; } - }, [isLoading, editMode]); + + // If all guards pass, fetch tools based on current form values + debouncedFetchTools(serverUrl, auth); + }, [serverUrl, auth, toolsInclude, requiresAuth, debouncedFetchTools, editMode, resolutionAttempted, toolSource]); useEffect(() => { // Clear auth field value when requiresAuth is unchecked @@ -244,6 +276,84 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { } }, [requiresAuth, auth]); + // Edit mode initialization - comprehensive loading of saved values + useEffect(() => { + if (isLoading || !editMode || !mcpToolKitNodeRef.current || resolutionAttempted) { + return; + } + + initializeEditMode(); + }, [isLoading, editMode, resolutionAttempted]); + + const initializeEditMode = async () => { + setResolutionAttempted(true); + + const node = mcpToolKitNodeRef.current; + if (!node) return; + + const { serverUrl: savedUrl, auth: savedAuth, permittedTools, requiresAuth: savedRequiresAuth } = extractOriginalValues(node); + + // Step 1: Update form state so FormGenerator displays values + setServerUrl(savedUrl); + setAuth(savedAuth); + setRequiresAuth(savedRequiresAuth); + + // Step 2: If no tools saved, exit early + if (permittedTools.length === 0) { + return; + } + + // Step 3: Attempt to resolve variables + const resolution = await attemptValueResolution( + savedUrl, + savedAuth, + moduleVariablesRef.current, + rpcClient, + projectPathUriRef.current + ); + + // Set toolSource BEFORE setToolsInclude to prevent the useEffect from triggering a duplicate fetch + setToolSource(resolution.canResolve ? 'auto-fetched' : 'saved-mock'); + setToolsInclude("selected"); + + if (resolution.canResolve) { + // Case 1: Values CAN be resolved - fetch tools from server and preselect saved tools + const tools = await fetchToolsFromServer(resolution.resolvedUrl, resolution.resolvedAuth, { + preselectTools: permittedTools, + skipResolution: true // Already resolved + }); + + // If fetch failed, fall back to mock tools + if (!tools || tools.length === 0) { + if (resolution.error) { + setResolutionError(resolution.error); + } + displayMockTools(permittedTools); + } + } else { + // Case 2: Values CANNOT be resolved - show mock tools + if (resolution.error) { + setResolutionError(resolution.error); + } + displayMockTools(permittedTools); + } + }; + + const displayMockTools = (toolNames: string[]) => { + if (toolNames.length === 0) { + setAvailableMcpTools([]); + setSelectedMcpTools(new Set()); + setToolSource(null); + return; + } + + const mockTools = createMockTools(toolNames); + setAvailableMcpTools(mockTools); + setSelectedMcpTools(new Set(toolNames)); // All ticked + setToolSource('saved-mock'); + setMcpToolsError(""); // Clear any connection errors when showing mock tools + }; + const handleToolSelectionChange = useCallback((toolName: string, isSelected: boolean) => { setSelectedMcpTools(prev => { const newSet = new Set(prev); @@ -264,6 +374,15 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { } }, [selectedMcpTools.size, availableMcpTools]); + const handleDiscoveredTools = useCallback((tools: Tool[], selectedNames: Set) => { + setAvailableMcpTools(tools); + setSelectedMcpTools(selectedNames); + setToolSource('manual-discovery'); + setShowDiscoverModal(false); + setMcpToolsError(""); + setResolutionError(""); // Clear resolution error + }, []); + const handleSave = async (node?: FlowNode) => { setIsSaving(true); try { @@ -288,12 +407,15 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { }, [availableMcpTools.length, selectedMcpTools.size]); const injectedComponents = useMemo(() => { + const shouldShowDiscoverButton = toolsInclude === "selected" + && !loadingMcpTools && (resolutionError !== "" || mcpToolsError !== ""); + return [ { component: ( setRequiresAuth(prev => !prev)} /> ), index: 1 @@ -312,11 +434,15 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { showValidationError={isSaveDisabled} toolsInclude={toolsInclude} onToolsIncludeChange={setToolsInclude} + showDiscoverButton={shouldShowDiscoverButton} + onDiscoverClick={() => setShowDiscoverModal(true)} + resolutionError={resolutionError} + toolSource={toolSource} /> ), index: 2 }]; - }, [availableMcpTools, selectedMcpTools, loadingMcpTools, mcpToolsError, serverUrl, handleToolSelectionChange, handleSelectAllTools, isSaveDisabled, requiresAuth, toolsInclude]); + }, [availableMcpTools, selectedMcpTools, loadingMcpTools, mcpToolsError, serverUrl, handleToolSelectionChange, handleSelectAllTools, isSaveDisabled, requiresAuth, toolsInclude, editMode, toolSource, resolutionError]); return ( @@ -338,9 +464,17 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { onChange={(fieldKey, value) => { if (fieldKey === SERVER_URL_FIELD_KEY) { setServerUrl(value); + // Clear tool source when user manually changes URL to allow re-fetching + if (toolSource === 'saved-mock' || toolSource === 'auto-fetched' || toolSource === 'manual-discovery') { + setToolSource(null); + } } if (fieldKey === AUTH_FIELD_KEY) { setAuth(value); + // Clear tool source when user manually changes auth to allow re-fetching + if (toolSource === 'saved-mock' || toolSource === 'auto-fetched' || toolSource === 'manual-discovery') { + setToolSource(null); + } } }} showProgressIndicator={isSaving} @@ -357,6 +491,14 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { }), [requiresAuth])} /> )} + + setShowDiscoverModal(false)} + onToolsSelected={handleDiscoveredTools} + rpcClient={rpcClient} + existingToolNames={selectedMcpTools} + /> ); } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx new file mode 100644 index 00000000000..200cc7208fa --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx @@ -0,0 +1,477 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useState, useMemo, useCallback } from "react"; +import { createPortal } from "react-dom"; +import styled from "@emotion/styled"; +import { Button, ThemeColors, SearchBox, Codicon, Divider, Typography, TextField, Stepper, Dropdown } from "@wso2/ui-toolkit"; +import type { OptionProps } from "@wso2/ui-toolkit"; +import type { BallerinaRpcClient } from "@wso2/ballerina-rpc-client"; +import { ToolsList, formatErrorMessage } from "./McpToolsSelection"; +import { cleanServerUrl } from "../formUtils"; + +// McpTool interface (same as in McpToolsSelection) +interface McpTool { + name: string; + description?: string; +} + +interface DiscoverToolsModalProps { + isOpen: boolean; + onClose: () => void; + onToolsSelected: (tools: McpTool[], selectedToolNames: Set) => void; + rpcClient: BallerinaRpcClient; + existingToolNames?: Set; +} + +const ModalContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 30000; + display: flex; + justify-content: center; + align-items: center; + background-color: color-mix(in srgb, ${ThemeColors.SECONDARY_CONTAINER} 70%, transparent); + font-family: GilmerRegular; +`; + +const ModalBox = styled.div` + width: 650px; + max-height: 85vh; + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + padding: 16px; + border-radius: 8px; + background-color: ${ThemeColors.SURFACE_DIM}; + box-shadow: 0 3px 8px rgb(0 0 0 / 0.2); + z-index: 30001; +`; + +const ModalHeaderSection = styled.header` + display: flex; + align-items: center; + justify-content: space-between; + padding-inline: 16px; + margin-bottom: 8px; +`; + +const ModalContent = styled.div` + margin-top: 8px; + flex: 1; + overflow-y: auto; + padding: 0 16px; + + .mcp-stepper { + margin: 8px 0 + }; +`; + +const FormSection = styled.div` + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 16px; +`; + +const ErrorMessage = styled.div` + color: ${ThemeColors.ERROR}; + font-size: 12px; + padding: 8px 0; + word-break: break-word; + white-space: pre-wrap; +`; + +const LoadingMessage = styled.div` + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-size: 12px; + display: flex; + align-items: center; + padding: 12px 0; + gap: 8px; +`; + +const InlineSpinner = styled.span` + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid ${ThemeColors.ON_SURFACE_VARIANT}; + border-top: 2px solid transparent; + border-radius: 50%; + animation: spin 0.8s linear infinite; + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } +`; + +const SearchContainer = styled.div` + padding: 12px 0; +`; + +const ToolsHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +`; + +const InfoMessage = styled.div` + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-size: 12px; + padding: 8px 0; +`; + +const ButtonContainer = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + padding: 8px 16px; + gap: 8px; +`; + +const BackArrow = styled.div` + cursor: pointer; + display: flex; + align-items: center; + padding: 4px; + margin-right: 8px; + &:hover { + opacity: 0.7; + } +`; + +const isValidUrl = (url: string): boolean => { + if (!url || !url.trim()) return false; + try { + // Allow localhost URLs + if (url.includes("localhost") || url.includes("127.0.0.1")) { + return url.startsWith("http://") || url.startsWith("https://"); + } + const urlObj = new URL(url); + return urlObj.protocol === "http:" || urlObj.protocol === "https:"; + } catch { + return false; + } +}; + +export const DiscoverToolsModal: React.FC = ({ + isOpen, + onClose, + onToolsSelected, + rpcClient, + existingToolNames +}) => { + const [currentStep, setCurrentStep] = useState<1 | 2>(1); + const [manualServerUrl, setManualServerUrl] = useState(""); + const [authType, setAuthType] = useState<"none" | "bearer">("none"); + const [authToken, setAuthToken] = useState(""); + const [discoveredTools, setDiscoveredTools] = useState([]); + const [selectedDiscoveredTools, setSelectedDiscoveredTools] = useState>(new Set()); + const [loadingDiscovery, setLoadingDiscovery] = useState(false); + const [discoveryError, setDiscoveryError] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [urlError, setUrlError] = useState(""); + + const formattedError = useMemo(() => formatErrorMessage(discoveryError), [discoveryError]); + + const authOptions: OptionProps[] = [ + { id: "none", content: "None", value: "none" }, + { id: "bearer", content: "Bearer Auth", value: "bearer" } + ]; + + const handleFetchTools = useCallback(async () => { + // Validate URL + const trimmedUrl = manualServerUrl.trim(); + if (!trimmedUrl) { + setUrlError("Server URL is required"); + return; + } + + const cleanUrl = cleanServerUrl(trimmedUrl); + if (!isValidUrl(cleanUrl)) { + setUrlError("Please enter a valid HTTP or HTTPS URL"); + return; + } + + setUrlError(""); + setDiscoveryError(""); + setLoadingDiscovery(true); + setDiscoveredTools([]); + setSelectedDiscoveredTools(new Set()); + + try { + // Use token directly if Bearer Auth is selected + const accessToken = authType === "bearer" && authToken.trim() + ? authToken.trim() + : ""; + + // Fetch tools from MCP server + const response = await rpcClient.getAIAgentRpcClient().getMcpTools({ + serviceUrl: cleanUrl, + accessToken + }); + + if (response.errorMsg) { + setDiscoveryError(response.errorMsg); + } else if (response.tools && response.tools.length > 0) { + setDiscoveredTools(response.tools); + + // Check if any tools match existing selections + const matchingTools = response.tools + .filter(t => existingToolNames?.has(t.name)) + .map(t => t.name); + + if (matchingTools.length > 0) { + // Pre-select matching tools + setSelectedDiscoveredTools(new Set(matchingTools)); + } else { + // No matches, select all (fallback to original behavior) + setSelectedDiscoveredTools(new Set(response.tools.map(t => t.name))); + } + + // Move to step 2 on successful fetch + setCurrentStep(2); + } else { + setDiscoveredTools([]); + } + } catch (error) { + setDiscoveryError(`Failed to fetch tools: ${error instanceof Error ? error.message : String(error)}`); + } finally { + setLoadingDiscovery(false); + } + }, [manualServerUrl, authToken, authType, rpcClient]); + + const handleToolSelectionChange = useCallback((toolName: string, isSelected: boolean) => { + setSelectedDiscoveredTools(prev => { + const newSet = new Set(prev); + if (isSelected) { + newSet.add(toolName); + } else { + newSet.delete(toolName); + } + return newSet; + }); + }, []); + + const handleSelectAll = useCallback(() => { + if (selectedDiscoveredTools.size === discoveredTools.length) { + // Deselect all + setSelectedDiscoveredTools(new Set()); + } else { + // Select all + setSelectedDiscoveredTools(new Set(discoveredTools.map(t => t.name))); + } + }, [discoveredTools, selectedDiscoveredTools.size]); + + const handleBack = useCallback(() => { + setCurrentStep(1); + setDiscoveryError(""); + setSearchQuery(""); + }, []); + + const handleAddSelectedTools = useCallback(() => { + if (selectedDiscoveredTools.size === 0) { + setDiscoveryError("Please select at least one tool to continue"); + return; + } + onToolsSelected(discoveredTools, selectedDiscoveredTools); + // Reset state + setCurrentStep(1); + setManualServerUrl(""); + setAuthType("none"); + setAuthToken(""); + setDiscoveredTools([]); + setSelectedDiscoveredTools(new Set()); + setDiscoveryError(""); + setSearchQuery(""); + setUrlError(""); + }, [discoveredTools, selectedDiscoveredTools, onToolsSelected]); + + const handleClose = useCallback(() => { + // Reset state when closing + setCurrentStep(1); + setManualServerUrl(""); + setAuthType("none"); + setAuthToken(""); + setDiscoveredTools([]); + setSelectedDiscoveredTools(new Set()); + setDiscoveryError(""); + setSearchQuery(""); + setUrlError(""); + onClose(); + }, [onClose]); + + if (!isOpen) return null; + + return createPortal( + + e.stopPropagation()}> + +
+ {currentStep === 2 && ( + + + + )} + + Discover MCP Tools + +
+
+ +
+
+ + + + + {currentStep === 1 ? ( + <> + + + Enter server details to discover available tools. This URL is for discovery only and won't update the Server URL field. + + + ) => { + setManualServerUrl(e.target.value); + setUrlError(""); + }} + disabled={loadingDiscovery} + errorMsg={urlError} + required + /> + + { + const newAuthType = value as "none" | "bearer"; + setAuthType(newAuthType); + if (newAuthType === "none") { + setAuthToken(""); + } + }} + disabled={loadingDiscovery} + containerSx={{ width: "100%" }} + /> + + {authType === "bearer" && ( + ) => setAuthToken(e.target.value)} + disabled={loadingDiscovery} + description="Enter your bearer token for authentication" + /> + )} + + + {loadingDiscovery && ( + + + Fetching tools from MCP server... + + )} + + {discoveryError && !loadingDiscovery && ( + {formattedError} + )} + + ) : ( + <> + + Select the tools you want to add. You can search and filter the available tools below. + + + + setSearchQuery(val)} + value={searchQuery} + iconPosition="end" + aria-label="search-tools" + sx={{ width: '100%' }} + /> + + + + + {selectedDiscoveredTools.size} of {discoveredTools.length} selected + + + + + + + {discoveryError && ( + {formattedError} + )} + + )} + + + + {currentStep === 1 ? ( + + ) : ( + + )} + +
+
, + document.body + ); +}; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx index cd8bb963ba3..3551465b82b 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx @@ -19,7 +19,7 @@ import React, { useState, useMemo, useEffect } from "react"; import { createPortal } from "react-dom"; import styled from "@emotion/styled"; -import { Button, CheckBox, ThemeColors, SearchBox, Codicon, Divider, Typography, Dropdown } from "@wso2/ui-toolkit"; +import { Button, CheckBox, ThemeColors, SearchBox, Codicon, Divider, Typography, Dropdown, Tooltip, Icon } from "@wso2/ui-toolkit"; import type { OptionProps } from "@wso2/ui-toolkit"; export interface McpTool { @@ -48,11 +48,6 @@ const formatErrorMessage = (error: string): string => { return 'The server returned an HTML page instead of MCP tools. Please verify the URL is correct.'; } - // Check for network errors - if (error.includes('Network error') || error.includes('Failed to fetch')) { - return 'Network error. Please check your connection and the server URL.'; - } - // Truncate very long error messages if (error.length > 500) { return error.substring(0, 500) + '...'; @@ -72,6 +67,10 @@ interface McpToolsSelectionProps { showValidationError?: boolean; toolsInclude?: string; onToolsIncludeChange?: (value: string) => void; + showDiscoverButton?: boolean; + onDiscoverClick?: () => void; + toolSource?: 'auto-fetched' | 'manual-discovery' | 'saved-mock' | null; + resolutionError?: string; } interface ToolsListProps { @@ -239,7 +238,6 @@ const ModalContent = styled.div` const SearchContainer = styled.div` padding: 12px 16px; - border-bottom: 1px solid ${ThemeColors.OUTLINE_VARIANT}; `; const ExpandButton = styled.button` @@ -247,7 +245,7 @@ const ExpandButton = styled.button` border: none; color: ${ThemeColors.ON_SURFACE}; cursor: pointer; - padding: 4px; + padding: 2px; display: flex; align-items: center; justify-content: center; @@ -413,6 +411,9 @@ const ToolsSelectionModal: React.FC<{ ); }; +export { ToolsList, formatErrorMessage }; +export type { ToolsListProps }; + export const McpToolsSelection: React.FC = ({ tools, selectedTools, @@ -423,7 +424,11 @@ export const McpToolsSelection: React.FC = ({ serviceUrl, showValidationError = false, toolsInclude = "all", - onToolsIncludeChange + onToolsIncludeChange, + showDiscoverButton = false, + onDiscoverClick, + resolutionError = "", + toolSource = null }) => { const [isModalOpen, setIsModalOpen] = useState(false); const formattedError = useMemo(() => formatErrorMessage(error), [error]); @@ -457,25 +462,32 @@ export const McpToolsSelection: React.FC = ({ Available Tools -
- {tools.length > 0 && ( - <> + {tools.length > 0 && ( +
+ {showDiscoverButton && toolSource === 'saved-mock' && ( setIsModalOpen(true)} - title="Expand view" - aria-label="Expand tools selection" + onClick={onDiscoverClick} + title="Discover Tools" + aria-label="Discover tools" > - + - - - )} -
+ )} + setIsModalOpen(true)} + title="Expand view" + aria-label="Expand tools selection" + > + + + +
+ )}
{loading && ( @@ -483,6 +495,22 @@ export const McpToolsSelection: React.FC = ({ Loading tools from MCP server... )} + {showDiscoverButton && toolSource !== 'saved-mock' && toolSource !== 'manual-discovery' && toolSource !== 'auto-fetched' && !error && ( + <> + + {resolutionError || "Tools cannot be loaded. Server URL or authentication configuration cannot be resolved."} + +
+ + +
+ + )} {error && ( <> @@ -491,7 +519,12 @@ export const McpToolsSelection: React.FC = ({ {formattedError} )} - {!loading && tools.length > 0 && ( + {resolutionError && toolSource === 'saved-mock' && !error && ( + + {resolutionError}. Using saved tool selections. + + )} + {!loading && tools.length > 0 && (!showDiscoverButton || toolSource === 'saved-mock' || toolSource === 'manual-discovery' || toolSource === 'auto-fetched') && ( <> {showValidationError && selectedTools.size === 0 ? ( @@ -510,12 +543,12 @@ export const McpToolsSelection: React.FC = ({ /> )} - {!loading && !error && tools.length === 0 && serviceUrl?.trim() && ( + {!loading && !error && !showDiscoverButton && tools.length === 0 && serviceUrl?.trim() && ( No tools available from this MCP server )} - {!loading && !error && tools.length === 0 && !serviceUrl?.trim() && ( + {!loading && !error && !showDiscoverButton && tools.length === 0 && !serviceUrl?.trim() && ( Enter a server URL to view available tools diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx index 4d635280e36..226e9018b16 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx @@ -43,6 +43,7 @@ const AuthCheckboxLabel = styled.div` const AuthCheckboxDescription = styled.div` color: ${ThemeColors.ON_SURFACE_VARIANT}; + margin-top: 4px; `; export interface RequiresAuthCheckboxProps { diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts new file mode 100644 index 00000000000..626db8d9b4e --- /dev/null +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * This software is the property of WSO2 LLC. and its suppliers, if any. + * Dissemination of any information or reproduction of any material contained + * herein in any form is strictly forbidden, unless permitted by WSO2 expressly. + * You may not alter or remove any copyright or other notice from copies of this content. + */ + +import { FlowNode } from "@wso2/ballerina-core"; +import type { BallerinaRpcClient } from "@wso2/ballerina-rpc-client"; +import { resolveVariableValue, resolveAuthConfig, parseToolsString } from "../utils"; +import { cleanServerUrl } from "../formUtils"; + +export interface Tool { + name: string; + description?: string; +} + +export interface ResolutionResult { + canResolve: boolean; + resolvedUrl: string; + resolvedAuth: string; + error: string; +} + +export async function attemptValueResolution( + serverUrl: string, + auth: string, + moduleVariables: FlowNode[], + rpcClient: BallerinaRpcClient, + projectPathUri: string +): Promise { + let error = ""; + + // Try to resolve server URL + let resolvedUrl = ""; + try { + resolvedUrl = await resolveVariableValue( + serverUrl, + moduleVariables, + rpcClient, + projectPathUri + ); + + // Check if resolution actually worked (not just returned the variable name) + const cleanUrl = cleanServerUrl(resolvedUrl); + if (cleanUrl === null || resolvedUrl === serverUrl) { + // Resolution failed or returned same value (likely a variable) + error = "Server URL contains unresolvable variables"; + } + } catch (e) { + error = `Failed to resolve server URL: ${e instanceof Error ? e.message : String(e)}`; + } + + // Try to resolve auth + let resolvedAuthValue = ""; + if (auth && auth.trim()) { + try { + resolvedAuthValue = await resolveAuthConfig( + auth, + moduleVariables, + rpcClient, + projectPathUri + ); + if (resolvedAuthValue === null) { + error = "Authentication configuration contains unresolvable variables"; + } + } catch (e) { + if (!error) { + error = `Failed to resolve auth: ${e instanceof Error ? e.message : String(e)}`; + } + } + } + + return { + canResolve: !error && resolvedAuthValue !== null && resolvedUrl !== null && resolvedUrl !== serverUrl, + resolvedUrl: resolvedUrl, + resolvedAuth: resolvedAuthValue, + error: error + }; +} + +export function createMockTools(toolNames: string[]): Tool[] { + return toolNames.map(name => ({ + name: name, + description: undefined as string | undefined + })); +} + +export function extractOriginalValues(node: FlowNode): { + serverUrl: string; + auth: string; + permittedTools: string[]; + requiresAuth: boolean; +} { + const serverUrl = (node.properties?.serverUrl?.value as string) || ""; + const auth = (node.properties?.auth?.value as string) || ""; + const permittedToolsValue = (node.properties?.permittedTools?.value as string) || ""; + const permittedTools = parseToolsString(permittedToolsValue, true); + const requiresAuth = Boolean(auth && auth.trim()); + + return { + serverUrl, + auth, + permittedTools, + requiresAuth + }; +} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts index 02da2f0289c..e5877a63f63 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts @@ -225,5 +225,6 @@ export function createToolParameters(): ToolParameters { } export const cleanServerUrl = (url: string): string => { + if (url === null || url === undefined) return null; return url.replace(/^"|"$/g, '').trim(); }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts index 00f3c190033..1cf7bb2f480 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts @@ -515,8 +515,8 @@ export const parseToolsString = (toolsStr: string, removeQuotes: boolean = false * Extracts access token from auth value string. * Expected format: {token: "..."} */ -export const extractAccessToken = (authValue: string): string => { - if (!authValue) return ""; +export const extractAccessToken = (authValue: string): string | null => { + if (authValue === null) return null; try { const tokenMatch = authValue.match(/token:\s*"([^"]*)"/); @@ -641,8 +641,8 @@ export const findValueInConfigVariables = async ( if (variable) { // Return the value from configValue or defaultValue const configValue = variable.properties?.configValue?.value as string; - const defaultValue = variable.properties?.defaultValue?.value as string; - return configValue || defaultValue || null; + if (configValue === "" || configValue === null) return null; + return configValue; } } } @@ -674,9 +674,9 @@ export const resolveVariableValue = async ( moduleVariables: FlowNode[], rpcClient?: BallerinaRpcClient, projectPathUri?: string -): Promise => { +): Promise => { if (!value) { - return ""; + return null; } const trimmed = value.trim(); @@ -705,8 +705,7 @@ export const resolveVariableValue = async ( } } - // Treat as literal value - return trimmed; + return null; }; /** @@ -718,7 +717,7 @@ export const resolveAuthConfig = async ( moduleVariables: FlowNode[], rpcClient?: BallerinaRpcClient, projectPathUri?: string -): Promise => { +): Promise => { if (!authValue) { return ""; } @@ -746,6 +745,11 @@ export const resolveAuthConfig = async ( projectPathUri ); + // Return null if the variable cannot be resolved + if (resolvedValue === null) { + return null; + } + // Replace in the auth string with quoted value resolvedAuth = resolvedAuth.replace( fullMatch, diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-ai-search.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-ai-search.svg new file mode 100644 index 00000000000..38153933a18 --- /dev/null +++ b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-ai-search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-expand-modal.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-expand-modal.svg new file mode 100644 index 00000000000..b3264a5a077 --- /dev/null +++ b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-expand-modal.svg @@ -0,0 +1,6 @@ + + + From 3087864b99feb7f7905866eee4cfea9b6646af35 Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 11 Dec 2025 07:53:15 +0530 Subject: [PATCH 092/102] Update MCP form to auto generate toolkit name based on variable name --- .../ballerina-core/src/interfaces/bi.ts | 1 + .../src/components/Form/index.tsx | 57 ++++++++++++++++++- .../src/components/Form/types.ts | 7 +++ .../src/views/BI/AIChatAgent/AddMcpServer.tsx | 12 +++- .../Mcp/{mcpEditModeUtils.ts => utils.ts} | 46 +++++++++++++-- .../views/BI/Forms/FormGenerator/index.tsx | 3 + 6 files changed, 116 insertions(+), 10 deletions(-) rename workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/{mcpEditModeUtils.ts => utils.ts} (65%) diff --git a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts index 31cda081952..ed44a15fef7 100644 --- a/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts +++ b/workspaces/ballerina/ballerina-core/src/interfaces/bi.ts @@ -376,6 +376,7 @@ export type NodePropertyKey = | "store" | "systemPrompt" | "targetType" + | "toolKitName" | "tools" | "type" | "typeDescription" diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx b/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx index 3e0e175984f..8cd3684ecb6 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx +++ b/workspaces/ballerina/ballerina-side-panel/src/components/Form/index.tsx @@ -16,7 +16,7 @@ * under the License. */ -import React, { forwardRef, useMemo, useEffect, useImperativeHandle, useState, useRef } from "react"; +import React, { forwardRef, useMemo, useEffect, useState, useRef } from "react"; import { useForm } from "react-hook-form"; import ReactMarkdown from "react-markdown"; import { @@ -31,7 +31,7 @@ import { } from "@wso2/ui-toolkit"; import styled from "@emotion/styled"; -import { ExpressionFormField, FormExpressionEditorProps, FormField, FormImports, FormValues } from "./types"; +import { ExpressionFormField, FieldDerivation, FormExpressionEditorProps, FormField, FormImports, FormValues } from "./types"; import { EditorFactory } from "../editors/EditorFactory"; import { getValueForDropdown, isDropdownField } from "../editors/utils"; import { @@ -401,6 +401,7 @@ export interface FormProps { onValidityChange?: (isValid: boolean) => void; // Callback for form validity status changeOptionalFieldTitle?: string; // Option to change the title of optional fields openFormTypeEditor?: (open: boolean, newType?: string, editingField?: FormField) => void; + derivedFields?: FieldDerivation[]; // Configuration for auto-deriving field values from other fields } export const Form = forwardRef((props: FormProps) => { @@ -441,7 +442,8 @@ export const Form = forwardRef((props: FormProps) => { footerActionButton = false, onValidityChange, changeOptionalFieldTitle = undefined, - openFormTypeEditor + openFormTypeEditor, + derivedFields = [] } = props; const { @@ -463,6 +465,7 @@ export const Form = forwardRef((props: FormProps) => { const [diagnosticsInfo, setDiagnosticsInfo] = useState(undefined); const [isMarkdownExpanded, setIsMarkdownExpanded] = useState(false); const [isIdentifierEditing, setIsIdentifierEditing] = useState(false); + const [manuallyEditedFields, setManuallyEditedFields] = useState>(new Set()); const [isSubComponentEnabled, setIsSubComponentEnabled] = useState(false); const [optionalFieldsTitle, setOptionalFieldsTitle] = useState("Advanced Configurations"); @@ -756,6 +759,54 @@ export const Form = forwardRef((props: FormProps) => { } }, [watchedValues]); + // Handle derived fields: auto-generate target field values from source fields + useEffect(() => { + if (derivedFields.length === 0) return; + + derivedFields.forEach(({ sourceField, targetField, deriveFn, breakOnManualEdit = true }) => { + const sourceValue = watchedValues[sourceField]; + const currentTargetValue = watchedValues[targetField]; + + // Skip if this field has been manually edited and breakOnManualEdit is true + if (breakOnManualEdit && manuallyEditedFields.has(targetField)) { + return; + } + + // Derive the new target value + const derivedValue = deriveFn(sourceValue); + + // Only update if the value has actually changed + if (derivedValue !== currentTargetValue) { + setValue(targetField, derivedValue); + } + }); + }, [watchedValues, derivedFields, manuallyEditedFields, setValue]); + + // Track manual edits to derived target fields + useEffect(() => { + if (derivedFields.length === 0) return; + + const prevValues = prevValuesRef.current; + derivedFields.forEach(({ targetField, breakOnManualEdit = true }) => { + if (!breakOnManualEdit) return; + + const currentValue = watchedValues[targetField]; + const prevValue = prevValues[targetField]; + + if (currentValue !== prevValue && prevValue !== undefined) { + // Mark this field as manually edited + setManuallyEditedFields(prev => { + if (!prev.has(targetField)) { + const newSet = new Set(prev); + newSet.add(targetField); + return newSet; + } + return prev; + }); + } + }); + }, [watchedValues, derivedFields]); + const handleOnOpenInDataMapper = () => { setSavingButton('dataMapper'); handleSubmit((data) => { diff --git a/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts b/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts index d74111308e8..a0e57742389 100644 --- a/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts +++ b/workspaces/ballerina/ballerina-side-panel/src/components/Form/types.ts @@ -26,6 +26,13 @@ export type FormValues = { [key: string]: any; }; +export type FieldDerivation = { + sourceField: string; + targetField: string; + deriveFn: (sourceValue: any) => any; + breakOnManualEdit?: boolean; +}; + export type FormField = { key: string; label: string; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx index b9eba74ffa9..4f14e6a5731 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx @@ -16,7 +16,7 @@ import FormGenerator from "../Forms/FormGenerator"; import { McpToolsSelection } from "./Mcp/McpToolsSelection"; import { DiscoverToolsModal } from "./Mcp/DiscoverToolsModal"; import { RequiresAuthCheckbox } from "./Mcp/RequiresAuthCheckbox"; -import { attemptValueResolution, createMockTools, extractOriginalValues } from "./Mcp/mcpEditModeUtils"; +import { attemptValueResolution, createMockTools, extractOriginalValues, generateToolKitName } from "./Mcp/utils"; import { cleanServerUrl } from "./formUtils"; import { Container, LoaderContainer } from "./styles"; import { extractAccessToken, findAgentNodeFromAgentCallNode, getAgentFilePath, getEndOfFileLineRange, resolveVariableValue, resolveAuthConfig } from "./utils"; @@ -36,6 +36,8 @@ interface AddMcpServerProps { const SERVER_URL_FIELD_KEY = "serverUrl"; const AUTH_FIELD_KEY = "auth"; +const RESULT_FIELD_KEY = "variable"; +const TOOLKIT_NAME_FIELD_KEY = "toolKitName"; export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const { agentCallNode, onSave, editMode = false } = props; @@ -477,6 +479,14 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { } } }} + derivedFields={editMode ? [] : [ + { + sourceField: RESULT_FIELD_KEY, + targetField: TOOLKIT_NAME_FIELD_KEY, + deriveFn: generateToolKitName, + breakOnManualEdit: true + } + ]} showProgressIndicator={isSaving} disableSaveButton={isSaveDisabled} injectedComponents={injectedComponents} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts similarity index 65% rename from workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts rename to workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts index 626db8d9b4e..f556574dc37 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/mcpEditModeUtils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts @@ -1,10 +1,19 @@ /** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. * - * This software is the property of WSO2 LLC. and its suppliers, if any. - * Dissemination of any information or reproduction of any material contained - * herein in any form is strictly forbidden, unless permitted by WSO2 expressly. - * You may not alter or remove any copyright or other notice from copies of this content. + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { FlowNode } from "@wso2/ballerina-core"; @@ -93,17 +102,42 @@ export function extractOriginalValues(node: FlowNode): { auth: string; permittedTools: string[]; requiresAuth: boolean; + result: string; + toolKitName: string; } { const serverUrl = (node.properties?.serverUrl?.value as string) || ""; const auth = (node.properties?.auth?.value as string) || ""; const permittedToolsValue = (node.properties?.permittedTools?.value as string) || ""; const permittedTools = parseToolsString(permittedToolsValue, true); const requiresAuth = Boolean(auth && auth.trim()); + const result = (node.properties?.variable?.value as string) || ""; + const toolKitName = (node.properties?.toolKitName?.value as string) || ""; return { serverUrl, auth, permittedTools, - requiresAuth + requiresAuth, + result, + toolKitName }; } + +export const generateToolKitName = (resultValue: string): string => { + const trimmed = resultValue?.trim(); + if (!trimmed) return ""; + const pascalCase = convertToPascalCase(trimmed); + return /toolkit/i.test(trimmed) ? pascalCase : `${pascalCase}Toolkit`; +}; + +const convertToPascalCase = (input: string): string => { + if (!input) return ""; + + const words = input.split(/[_\-\s]+/).filter(word => word.length > 0); + if (words.length > 1) { + return words + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(""); + } + return input.charAt(0).toUpperCase() + input.slice(1); +}; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx index a36076393ef..6d0a59d836a 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Forms/FormGenerator/index.tsx @@ -42,6 +42,7 @@ import { DataMapperDisplayMode } from "@wso2/ballerina-core"; import { + FieldDerivation, FormField, FormValues, Form, @@ -144,6 +145,7 @@ interface FormProps { fieldPriority?: Record; // Map of field keys to priority numbers (lower = rendered first) fieldOverrides?: Record>; footerActionButton?: boolean; // Render save button as footer action button + derivedFields?: FieldDerivation[]; // Configuration for auto-deriving field values from other fields } // Styled component for the action button description @@ -1545,6 +1547,7 @@ export const FormGenerator = forwardRef(func scopeFieldAddon={scopeFieldAddon} onChange={onChange} injectedComponents={injectedComponents} + derivedFields={props.derivedFields} /> )} {stack.map((item, i) => ( From fab1e6f936845afddea4d5375b5bb2c23659012a Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Thu, 11 Dec 2025 09:58:48 +0530 Subject: [PATCH 093/102] Show discover tools button after manual discovery in MCP form --- .../src/views/BI/AIChatAgent/AddMcpServer.tsx | 2 +- .../src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx index 4f14e6a5731..af5b836fc14 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx @@ -382,7 +382,7 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { setToolSource('manual-discovery'); setShowDiscoverModal(false); setMcpToolsError(""); - setResolutionError(""); // Clear resolution error + setResolutionError("Loaded tools via manual discovery."); }, []); const handleSave = async (node?: FlowNode) => { diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx index 3551465b82b..19175acc1be 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/McpToolsSelection.tsx @@ -464,7 +464,7 @@ export const McpToolsSelection: React.FC = ({ Available Tools {tools.length > 0 && (
- {showDiscoverButton && toolSource === 'saved-mock' && ( + {showDiscoverButton && (toolSource === 'saved-mock' || toolSource === 'manual-discovery') && ( Date: Thu, 11 Dec 2025 12:55:07 +0530 Subject: [PATCH 094/102] Refactor styles in mcp tools modal --- .../BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx | 139 ++++-------------- .../BI/AIChatAgent/Mcp/McpToolsSelection.tsx | 42 +++--- 2 files changed, 54 insertions(+), 127 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx index 200cc7208fa..6201ac27b4f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx @@ -22,7 +22,20 @@ import styled from "@emotion/styled"; import { Button, ThemeColors, SearchBox, Codicon, Divider, Typography, TextField, Stepper, Dropdown } from "@wso2/ui-toolkit"; import type { OptionProps } from "@wso2/ui-toolkit"; import type { BallerinaRpcClient } from "@wso2/ballerina-rpc-client"; -import { ToolsList, formatErrorMessage } from "./McpToolsSelection"; +import { + ToolsList, + formatErrorMessage, + ModalContainer, + ModalBox, + ModalHeaderSection, + ModalContent, + SearchContainer, + ErrorMessage, + LoadingMessage, + InlineSpinner, + ToolsHeader, + InfoMessage +} from "./McpToolsSelection"; import { cleanServerUrl } from "../formUtils"; // McpTool interface (same as in McpToolsSelection) @@ -39,53 +52,7 @@ interface DiscoverToolsModalProps { existingToolNames?: Set; } -const ModalContainer = styled.div` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 30000; - display: flex; - justify-content: center; - align-items: center; - background-color: color-mix(in srgb, ${ThemeColors.SECONDARY_CONTAINER} 70%, transparent); - font-family: GilmerRegular; -`; - -const ModalBox = styled.div` - width: 650px; - max-height: 85vh; - position: relative; - display: flex; - flex-direction: column; - overflow: hidden; - padding: 16px; - border-radius: 8px; - background-color: ${ThemeColors.SURFACE_DIM}; - box-shadow: 0 3px 8px rgb(0 0 0 / 0.2); - z-index: 30001; -`; - -const ModalHeaderSection = styled.header` - display: flex; - align-items: center; - justify-content: space-between; - padding-inline: 16px; - margin-bottom: 8px; -`; - -const ModalContent = styled.div` - margin-top: 8px; - flex: 1; - overflow-y: auto; - padding: 0 16px; - - .mcp-stepper { - margin: 8px 0 - }; -`; - +// Unique styled components for DiscoverToolsModal const FormSection = styled.div` display: flex; flex-direction: column; @@ -93,54 +60,6 @@ const FormSection = styled.div` margin-bottom: 16px; `; -const ErrorMessage = styled.div` - color: ${ThemeColors.ERROR}; - font-size: 12px; - padding: 8px 0; - word-break: break-word; - white-space: pre-wrap; -`; - -const LoadingMessage = styled.div` - color: ${ThemeColors.ON_SURFACE_VARIANT}; - font-size: 12px; - display: flex; - align-items: center; - padding: 12px 0; - gap: 8px; -`; - -const InlineSpinner = styled.span` - display: inline-block; - width: 16px; - height: 16px; - border: 2px solid ${ThemeColors.ON_SURFACE_VARIANT}; - border-top: 2px solid transparent; - border-radius: 50%; - animation: spin 0.8s linear infinite; - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } -`; - -const SearchContainer = styled.div` - padding: 12px 0; -`; - -const ToolsHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -`; - -const InfoMessage = styled.div` - color: ${ThemeColors.ON_SURFACE_VARIANT}; - font-size: 12px; - padding: 8px 0; -`; - const ButtonContainer = styled.div` display: flex; justify-content: flex-end; @@ -160,6 +79,12 @@ const BackArrow = styled.div` } `; +const StyledModalContent = styled(ModalContent)` + .mcp-stepper { + margin: 8px 0; + } +`; + const isValidUrl = (url: string): boolean => { if (!url || !url.trim()) return false; try { @@ -325,7 +250,7 @@ export const DiscoverToolsModal: React.FC = ({ return createPortal( - e.stopPropagation()}> + e.stopPropagation()}>
{currentStep === 2 && ( @@ -343,7 +268,7 @@ export const DiscoverToolsModal: React.FC = ({ - + = ({ {currentStep === 1 ? ( <> - + Enter server details to discover available tools. This URL is for discovery only and won't update the Server URL field. @@ -398,23 +323,23 @@ export const DiscoverToolsModal: React.FC = ({ {loadingDiscovery && ( - + Fetching tools from MCP server... )} {discoveryError && !loadingDiscovery && ( - {formattedError} + {formattedError} )} ) : ( <> - + Select the tools you want to add. You can search and filter the available tools below. - + setSearchQuery(val)} @@ -425,8 +350,8 @@ export const DiscoverToolsModal: React.FC = ({ /> - - + + {selectedDiscoveredTools.size} of {discoveredTools.length} selected +
+ )} )} {resolutionError && toolSource === 'saved-mock' && !error && ( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts index b4f1b16388c..985e9a61050 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts @@ -520,7 +520,7 @@ export const extractAccessToken = (authValue: string): string | null => { try { const tokenMatch = authValue.match(/token:\s*"([^"]*)"/); - return tokenMatch?.[1] ?? ""; + return tokenMatch?.[1] ?? null; } catch (error) { console.error("Failed to parse auth token:", error); return ""; From 4609ab4f3bc809ac56e84ddabc1253e5f43aa8fb Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Fri, 12 Dec 2025 12:32:40 +0530 Subject: [PATCH 099/102] Fix coderabbit suggestions --- .../src/views/BI/AIChatAgent/AddMcpServer.tsx | 126 ++++++++++-------- .../BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx | 8 +- .../AIChatAgent/Mcp/RequiresAuthCheckbox.tsx | 4 +- .../src/views/BI/AIChatAgent/Mcp/utils.ts | 4 +- .../src/views/BI/AIChatAgent/formUtils.ts | 2 +- .../src/views/BI/AIChatAgent/utils.ts | 2 +- 6 files changed, 80 insertions(+), 66 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx index cad195000b3..93ac021025f 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/AddMcpServer.tsx @@ -58,9 +58,9 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const [showDiscoverModal, setShowDiscoverModal] = useState(false); // Edit mode tracking - const [resolutionAttempted, setResolutionAttempted] = useState(false); const [resolutionError, setResolutionError] = useState(""); const [toolSource, setToolSource] = useState<'auto-fetched' | 'manual-discovery' | 'saved-mock' | null>(null); + const isInitializingEditModeRef = useRef(false); const mcpToolKitNodeTemplateRef = useRef(null); const mcpToolKitNodeRef = useRef(null); @@ -145,9 +145,10 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { const cleanUrl = cleanServerUrl(resolvedUrl); if (cleanUrl === null) { setMcpToolsError(""); - if (url === resolvedUrl && url.trim()) { - setResolutionError("Unable to resolve Server URL at design time. Tools cannot be auto-fetched with the current configuration."); - } + setAvailableMcpTools([]); + setSelectedMcpTools(new Set()); + setToolSource(null); + setResolutionError("Unable to resolve Server URL at design time. Tools cannot be auto-fetched with the current configuration."); setLoadingMcpTools(false); return []; } @@ -161,6 +162,9 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { if (requiresAuth && accessToken === null) { setMcpToolsError(""); + setAvailableMcpTools([]); + setSelectedMcpTools(new Set()); + setToolSource(null); setResolutionError("Unable to resolve authentication configuration at design time. Tools cannot be auto-fetched with the current configuration."); setLoadingMcpTools(false); return []; @@ -237,6 +241,7 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { useEffect(() => { if (toolsInclude !== "selected") { + debouncedFetchTools.cancel(); setAvailableMcpTools([]); setSelectedMcpTools(new Set()); setLoadingMcpTools(false); @@ -246,12 +251,12 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { return; } - if (editMode && !resolutionAttempted) { - return; - } + if (isInitializingEditModeRef.current) return; + if (editMode && toolSource !== null) return; debouncedFetchTools(serverUrl, auth); - }, [serverUrl, auth, toolsInclude]); + return () => debouncedFetchTools.cancel(); + }, [serverUrl, auth, toolsInclude, requiresAuth, debouncedFetchTools]); useEffect(() => { // Clear auth field value when requiresAuth is unchecked @@ -264,54 +269,59 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { }, [requiresAuth]); const initializeEditMode = async () => { - const node = mcpToolKitNodeRef.current; - if (!node) return; - - const { serverUrl: savedUrl, auth: savedAuth, permittedTools, requiresAuth: savedRequiresAuth } = extractOriginalValues(node); + isInitializingEditModeRef.current = true; - // Step 1: Update form state so FormGenerator displays values - setRequiresAuth(savedRequiresAuth); - - // Step 2: If no tools saved, exit early - if (permittedTools.length === 0) { - return; - } + try { + const node = mcpToolKitNodeRef.current; + if (!node) return; - // Step 3: Attempt to resolve variables - const resolution = await attemptValueResolution( - savedUrl, - savedAuth, - rpcClient, - projectPathUriRef.current, - agentFilePathRef.current - ); + const { serverUrl: savedUrl, auth: savedAuth, permittedTools, requiresAuth: savedRequiresAuth } = extractOriginalValues(node); - // Set toolSource BEFORE setToolsInclude to prevent the useEffect from triggering a duplicate fetch - setToolSource(resolution.canResolve ? 'auto-fetched' : 'saved-mock'); - setToolsInclude("selected"); + // Update form state so FormGenerator displays values + setRequiresAuth(savedRequiresAuth); - if (resolution.canResolve) { - // Case 1: Values CAN be resolved - fetch tools from server and preselect saved tools - const tools = await fetchToolsFromServer(resolution.resolvedUrl, resolution.resolvedAuth, { - preselectTools: permittedTools, - skipResolution: true // Already resolved - }); + // If no tools saved, exit early + if (permittedTools.length === 0) { + return; + } - // If fetch failed, fall back to mock tools - if (!tools || tools.length === 0) { + // Attempt to resolve variables + const resolution = await attemptValueResolution( + savedUrl, + savedAuth, + rpcClient, + projectPathUriRef.current, + agentFilePathRef.current + ); + + // Set toolSource BEFORE setToolsInclude to prevent the useEffect from triggering a duplicate fetch + setToolSource(resolution.canResolve ? 'auto-fetched' : 'saved-mock'); + setToolsInclude("selected"); + + if (resolution.canResolve) { + // Values CAN be resolved - fetch tools from server and preselect saved tools + const tools = await fetchToolsFromServer(resolution.resolvedUrl, resolution.resolvedAuth, { + preselectTools: permittedTools, + skipResolution: true // Already resolved + }); + + // If fetch failed, fall back to mock tools + if (!tools || tools.length === 0) { + if (resolution.error) { + setResolutionError(resolution.error); + } + displayMockTools(permittedTools); + } + } else { + // Values CANNOT be resolved - show mock tools if (resolution.error) { setResolutionError(resolution.error); } displayMockTools(permittedTools); } - } else { - // Case 2: Values CANNOT be resolved - show mock tools - if (resolution.error) { - setResolutionError(resolution.error); - } - displayMockTools(permittedTools); + } finally { + isInitializingEditModeRef.current = false; } - setResolutionAttempted(true); }; const displayMockTools = (toolNames: string[]) => { @@ -430,6 +440,16 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { }]; }, [availableMcpTools, selectedMcpTools, loadingMcpTools, mcpToolsError, serverUrl, handleToolSelectionChange, handleSelectAllTools, isSaveDisabled, requiresAuth, toolsInclude, editMode, toolSource, resolutionError, handleRetryFetch]); + const fieldOverrides = useMemo(() => ({ + auth: { + advanced: false, + hidden: !requiresAuth + }, + toolKitName: { + advanced: true, + } + }), [requiresAuth]); + return ( {isLoading && ( @@ -450,8 +470,14 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { onChange={(fieldKey, value) => { if (fieldKey === SERVER_URL_FIELD_KEY) { setServerUrl(value); + if (editMode && !isInitializingEditModeRef.current && toolSource !== null) { + setToolSource(null); + } } else if (fieldKey === AUTH_FIELD_KEY) { setAuth(value); + if (editMode && !isInitializingEditModeRef.current && toolSource !== null) { + setToolSource(null); + } } }} derivedFields={editMode ? [] : [ @@ -465,15 +491,7 @@ export function AddMcpServer(props: AddMcpServerProps): JSX.Element { showProgressIndicator={isSaving} disableSaveButton={isSaveDisabled} injectedComponents={injectedComponents} - fieldOverrides={useMemo(() => ({ - auth: { - advanced: false, - hidden: !requiresAuth - }, - toolKitName: { - advanced: true, - } - }), [requiresAuth])} + fieldOverrides={fieldOverrides} /> )} diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx index 6201ac27b4f..8b413f3bff4 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx @@ -88,10 +88,6 @@ const StyledModalContent = styled(ModalContent)` const isValidUrl = (url: string): boolean => { if (!url || !url.trim()) return false; try { - // Allow localhost URLs - if (url.includes("localhost") || url.includes("127.0.0.1")) { - return url.startsWith("http://") || url.startsWith("https://"); - } const urlObj = new URL(url); return urlObj.protocol === "http:" || urlObj.protocol === "https:"; } catch { @@ -184,7 +180,7 @@ export const DiscoverToolsModal: React.FC = ({ } finally { setLoadingDiscovery(false); } - }, [manualServerUrl, authToken, authType, rpcClient]); + }, [manualServerUrl, authToken, authType, rpcClient, existingToolNames]); const handleToolSelectionChange = useCallback((toolName: string, isSelected: boolean) => { setSelectedDiscoveredTools(prev => { @@ -350,7 +346,7 @@ export const DiscoverToolsModal: React.FC = ({ /> - + {selectedDiscoveredTools.size} of {discoveredTools.length} selected diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx index 226e9018b16..74826dfec45 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/RequiresAuthCheckbox.tsx @@ -64,11 +64,11 @@ export const RequiresAuthCheckbox: React.FC = ({ chec Enable if the server requires authentication
- + { }} sx={{ display: "contents" }} /> diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts index 4a2cf6e827c..b1f1ad5ea12 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/utils.ts @@ -43,7 +43,7 @@ export async function attemptValueResolution( let error = ""; // Try to resolve server URL - let resolvedUrl = ""; + let resolvedUrl = null; try { resolvedUrl = await resolveVariableValue( serverUrl, @@ -63,7 +63,7 @@ export async function attemptValueResolution( } // Try to resolve auth - let resolvedAuthValue = ""; + let resolvedAuthValue = null; if (auth && auth.trim()) { try { resolvedAuthValue = await resolveAuthConfig( diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts index e5877a63f63..839276bc033 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/formUtils.ts @@ -224,7 +224,7 @@ export function createToolParameters(): ToolParameters { }; } -export const cleanServerUrl = (url: string): string => { +export const cleanServerUrl = (url: string): string | null => { if (url === null || url === undefined) return null; return url.replace(/^"|"$/g, '').trim(); }; diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts index 985e9a61050..93b1fa29814 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/utils.ts @@ -646,7 +646,7 @@ export const findValueInConfigVariables = async ( ); if (variable) { // Return the value from configValue or defaultValue - const configValue = variable.properties?.configValue?.value as string; + const configValue = variable.properties?.configValue?.value as string | null; if (configValue === "" || configValue === null) return null; return configValue; } From 219a1604cab325243b3850475eb930f899f5cf1c Mon Sep 17 00:00:00 2001 From: Dan Niles Date: Mon, 15 Dec 2025 17:51:21 +0530 Subject: [PATCH 100/102] Change auth token input type to password in discover tools modal --- .../src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx index 8b413f3bff4..50d6f3c6b68 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/AIChatAgent/Mcp/DiscoverToolsModal.tsx @@ -81,7 +81,7 @@ const BackArrow = styled.div` const StyledModalContent = styled(ModalContent)` .mcp-stepper { - margin: 8px 0; + margin: 12px 0; } `; @@ -314,6 +314,7 @@ export const DiscoverToolsModal: React.FC = ({ onChange={(e: React.ChangeEvent) => setAuthToken(e.target.value)} disabled={loadingDiscovery} description="Enter your bearer token for authentication" + type="password" /> )}
From 8dd1f00d567a91c8388bc7b75ac3983f03a687ea Mon Sep 17 00:00:00 2001 From: gigara Date: Tue, 16 Dec 2025 14:22:11 +0530 Subject: [PATCH 101/102] Update OpenVSX publish condition to allow pre-releases --- .github/workflows/publish-vsix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-vsix.yml b/.github/workflows/publish-vsix.yml index e6d496d5907..027b0863720 100644 --- a/.github/workflows/publish-vsix.yml +++ b/.github/workflows/publish-vsix.yml @@ -102,7 +102,7 @@ jobs: run: vsce publish -p ${{ secrets.VSCE_TOKEN }} --packagePath ${{ steps.vsix.outputs.vsixName }} ${{ steps.vsix.outputs.releaseMode }} - name: Publish to OpenVSX marketplace - if: ${{ github.event.inputs.openVSX == 'true' && github.event.inputs.isPreRelease == 'false' }} + if: ${{ github.event.inputs.openVSX == 'true' }} run: ovsx publish -p ${{ secrets.OPENVSX_TOKEN }} --packagePath ${{ steps.vsix.outputs.vsixName }} ${{ steps.vsix.outputs.releaseMode }} - name: Create a release in ${{ steps.repo.outputs.repo }} repo From 56a069847c66e0569a31409900532d834b616b75 Mon Sep 17 00:00:00 2001 From: kaje94 Date: Tue, 16 Dec 2025 15:28:35 +0530 Subject: [PATCH 102/102] bump platform extension version --- workspaces/wso2-platform/wso2-platform-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/wso2-platform/wso2-platform-extension/package.json b/workspaces/wso2-platform/wso2-platform-extension/package.json index 6499ed7a84a..976f5bef7cc 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/package.json +++ b/workspaces/wso2-platform/wso2-platform-extension/package.json @@ -3,7 +3,7 @@ "displayName": "WSO2 Platform", "description": "Manage WSO2 Choreo and Devant projects in VS Code.", "license": "Apache-2.0", - "version": "1.0.17", + "version": "1.0.18", "cliVersion": "v1.2.212509091800", "publisher": "wso2", "bugs": {