From c467a5e3ad3cc7630d682c7c095453534d2a7dbd Mon Sep 17 00:00:00 2001 From: tharindulak Date: Fri, 4 Jul 2025 15:46:44 +0530 Subject: [PATCH 001/148] Support users add value to configurables --- .../mi-core/src/rpc-types/mi-diagram/types.ts | 1 + .../src/rpc-types/mi-visualizer/types.ts | 1 + .../Form/HelperPane/ConfigsPage.tsx | 20 +++++++++++-------- .../rpc-managers/mi-diagram/rpc-manager.ts | 14 ++++++++++++- .../rpc-managers/mi-visualizer/rpc-manager.ts | 14 ++++++++++++- .../ManageConfigurables.tsx | 14 +++++++++++-- 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts b/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts index 7b4221b052b..9959dfd0d96 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts @@ -2125,6 +2125,7 @@ export interface TestConnectorConnectionResponse { export interface SaveConfigRequest { configName: string; configType: "string" | "cert"; + configValue: string; } export interface SaveConfigResponse { diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts index cdcb79fdae7..4d56c352f55 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts @@ -106,6 +106,7 @@ export interface AdvancedProjectDetails { export interface PomNodeDetails { value: string; + type?: string; key?: string; displayValue?: string; range?: STRange | STRange[]; diff --git a/workspaces/mi/mi-diagram/src/components/Form/HelperPane/ConfigsPage.tsx b/workspaces/mi/mi-diagram/src/components/Form/HelperPane/ConfigsPage.tsx index b093f64c6f0..3ea69413d77 100644 --- a/workspaces/mi/mi-diagram/src/components/Form/HelperPane/ConfigsPage.tsx +++ b/workspaces/mi/mi-diagram/src/components/Form/HelperPane/ConfigsPage.tsx @@ -58,7 +58,8 @@ type ConfigsPageProps = { /* Validation schema for the config form */ const schema = yup.object({ configName: yup.string().required('Config Name is required'), - configType: yup.string().oneOf(['string', 'cert'] as const).required('Config Type is required') + configType: yup.string().oneOf(['string', 'cert'] as const).required('Config Type is required'), + configValue: yup.string().required('Config Value is required').required('Config Value is required') }); type ConfigFormData = yup.InferType; @@ -112,7 +113,8 @@ export const ConfigsPage = ({ position, onChange }: ConfigsPageProps) => { // Handle form submission rpcClient.getMiDiagramRpcClient().saveConfig({ configName: data.configName, - configType: data.configType + configType: data.configType, + configValue: data.configValue }).then(({ success }) => { if (success) { // Retrieve the updated config info @@ -165,12 +167,6 @@ export const ConfigsPage = ({ position, onChange }: ConfigsPageProps) => { - { { id: '2', content: 'cert', value: 'cert' } ]} /> + {isCreating && } diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionWizard/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionWizard/index.tsx index a3f1a7f3549..6fbe805ccf3 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionWizard/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Connection/EditConnectionWizard/index.tsx @@ -26,6 +26,7 @@ import { ExpressionFormField, PanelContainer } from "@wso2/ballerina-side-panel" import { ProgressRing, ThemeColors } from "@wso2/ui-toolkit"; import { InlineDataMapper } from "../../../InlineDataMapper"; import { HelperView } from "../../HelperView"; +import { URI, Utils } from "vscode-uri"; const Container = styled.div` width: 100%; @@ -40,19 +41,20 @@ const SpinnerContainer = styled.div` `; interface EditConnectionWizardProps { - fileName: string; // file path of `connection.bal` + projectUri: string; connectionName: string; onClose?: () => void; } export function EditConnectionWizard(props: EditConnectionWizardProps) { - const { fileName, connectionName, onClose } = props; + const { projectUri, connectionName, onClose } = props; const { rpcClient } = useRpcContext(); const [connection, setConnection] = useState(); const [subPanel, setSubPanel] = useState({ view: SubPanelView.UNDEFINED }); const [showSubPanel, setShowSubPanel] = useState(false); const [updatingContent, setUpdatingContent] = useState(false); + const [filePath, setFilePath] = useState(""); const [updatedExpressionField, setUpdatedExpressionField] = useState(undefined); useEffect(() => { @@ -72,6 +74,10 @@ export function EditConnectionWizard(props: EditConnectionWizardProps) { onClose?.(); return; } + const connectionFile = connector.codedata.lineRange.fileName; + let connectionFilePath = Utils.joinPath(URI.file(projectUri), connectionFile).fsPath; + setFilePath(connectionFilePath); + setConnection(connector); const formProperties = getFormProperties(connector); console.log(">>> Connector form properties", formProperties); @@ -83,7 +89,7 @@ export function EditConnectionWizard(props: EditConnectionWizardProps) { if (connection) { setUpdatingContent(true); - if (fileName === "") { + if (filePath === "") { console.error(">>> Error updating source code. No source file found"); setUpdatingContent(false); return; @@ -92,7 +98,7 @@ export function EditConnectionWizard(props: EditConnectionWizardProps) { rpcClient .getBIDiagramRpcClient() .getSourceCode({ - filePath: fileName, + filePath: filePath, flowNode: node, isConnector: true, }) @@ -183,7 +189,7 @@ export function EditConnectionWizard(props: EditConnectionWizardProps) { > Date: Mon, 7 Jul 2025 14:36:42 +0530 Subject: [PATCH 015/148] Add experimental feature toggle for Sequence Diagram View --- workspaces/ballerina/ballerina-extension/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 76bb3fdd315..3a35c7bb328 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -199,6 +199,14 @@ "type": "boolean", "default": false, "description": "Use Ballerina distribution language server instead of bundled language server. Note: This will be automatically enabled for Ballerina versions older than 2201.12.3." + }, + "ballerina.enableSequenceDiagramView":{ + "type": "boolean", + "default": true, + "description": "Enable the experimental Sequence Diagram View.", + "tags": [ + "experimental" + ] } } }, From 4b577c8891ba8931172e9558c993133804dca835 Mon Sep 17 00:00:00 2001 From: Vellummyilum Vinoth Date: Mon, 7 Jul 2025 16:34:43 +0530 Subject: [PATCH 016/148] Allow underscores in function names --- .../src/views/AIPanel/components/AIChat/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx index 0fd779d90de..8e5857a9f86 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx @@ -529,6 +529,12 @@ const AIChat: React.FC = () => { case Command.DataMap: { switch (parsedInput.templateId) { case "mappings-for-records": + // TODO: Update this to use the LS API for validating function names + const invalidPattern = /[<>\/\(\)\{\}\[\]\\!@#$%^&*+=|;:'",.?`~]/; + if (invalidPattern.test(parsedInput.placeholderValues.functionName)) { + throw new Error("Please provide a valid function name without special characters."); + } + await processMappingParameters( inputText, { @@ -1565,12 +1571,6 @@ const AIChat: React.FC = () => { const functionName = parameters.functionName; - const invalidPattern = /[<>\/\(\)\{\}\[\]\\!@#$%^&*_+=|;:'",.?`~]/; - - if (invalidPattern.test(functionName)) { - throw new Error("Please provide a valid function name without special characters."); - } - const projectImports = await rpcClient.getBIDiagramRpcClient().getAllImports(); const activeFile = await rpcClient.getAiPanelRpcClient().getActiveFile(); const projectComponents = await rpcClient.getBIDiagramRpcClient().getProjectComponents(); From b48cd3ad2ee7f3eb8299a608bda079dfb7aa50f3 Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Mon, 7 Jul 2025 23:07:43 +0530 Subject: [PATCH 017/148] Refactor environment variable handling in webpack configuration to return missing variables and log warnings, improving build resilience. --- common/scripts/env-webpack-helper.js | 11 ++--------- .../ballerina-extension/webpack.config.js | 17 ++++++++--------- workspaces/mi/mi-extension/webpack.config.js | 17 ++++++++--------- .../wso2-platform-extension/webpack.config.js | 18 +++++++++--------- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/common/scripts/env-webpack-helper.js b/common/scripts/env-webpack-helper.js index 7e9398e0123..ac672ce5a54 100644 --- a/common/scripts/env-webpack-helper.js +++ b/common/scripts/env-webpack-helper.js @@ -23,7 +23,7 @@ * 3. Only define variables that are explicitly declared in .env file * * @param {Object} env - Parsed environment variables from .env file (from dotenv.config().parsed) - * @returns {Object} Environment variables object ready for webpack.DefinePlugin + * @returns {Object} Object containing envKeys for webpack.DefinePlugin and missingVars array */ function createEnvDefinePlugin(env) { @@ -44,14 +44,7 @@ function createEnvDefinePlugin(env) { }); } - if (missingVars.length > 0) { - throw new Error( - `Missing required environment variables: ${missingVars.join(', ')}\n` + - `Please provide values in either .env file or runtime environment.\n` - ); - } - - return envKeys; + return { envKeys, missingVars }; } module.exports = { diff --git a/workspaces/ballerina/ballerina-extension/webpack.config.js b/workspaces/ballerina/ballerina-extension/webpack.config.js index 2701bc79c22..1591d571bd3 100644 --- a/workspaces/ballerina/ballerina-extension/webpack.config.js +++ b/workspaces/ballerina/ballerina-extension/webpack.config.js @@ -10,15 +10,14 @@ const { createEnvDefinePlugin } = require('../../../common/scripts/env-webpack-h const envPath = path.resolve(__dirname, '.env'); const env = dotenv.config({ path: envPath }).parsed; - -let envKeys; -try { - envKeys = createEnvDefinePlugin(env); -} catch (error) { - console.warn('\n⚠️ Environment Variable Configuration Warning:'); - console.warn(error.message); - console.warn('Continuing build with empty environment variables...'); - envKeys = {}; +console.log("Fetching values for environment variables..."); +const { envKeys, missingVars } = createEnvDefinePlugin(env); +if (missingVars.length > 0) { + console.warn( + '\n⚠️ Environment Variable Configuration Warning:\n' + + `Missing required environment variables: ${missingVars.join(', ')}\n` + + `Please provide values in either .env file or runtime environment.\n` + ); } /** @type {import('webpack').Configuration} */ diff --git a/workspaces/mi/mi-extension/webpack.config.js b/workspaces/mi/mi-extension/webpack.config.js index 2e7a1f586c5..df1e80e457a 100644 --- a/workspaces/mi/mi-extension/webpack.config.js +++ b/workspaces/mi/mi-extension/webpack.config.js @@ -27,15 +27,14 @@ const { createEnvDefinePlugin } = require('../../../common/scripts/env-webpack-h const envPath = path.resolve(__dirname, '.env'); const env = dotenv.config({ path: envPath }).parsed; - -let envKeys; -try { - envKeys = createEnvDefinePlugin(env); -} catch (error) { - console.warn('\n⚠️ Environment Variable Configuration Warning:'); - console.warn(error.message); - console.warn('Continuing build with empty environment variables...'); - envKeys = {}; +console.log("Fetching values for environment variables..."); +const { envKeys, missingVars } = createEnvDefinePlugin(env); +if (missingVars.length > 0) { + console.warn( + '\n⚠️ Environment Variable Configuration Warning:\n' + + `Missing required environment variables: ${missingVars.join(', ')}\n` + + `Please provide values in either .env file or runtime environment.\n` + ); } /** @type {import('webpack').Configuration} */ diff --git a/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js b/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js index eb72aee50b3..52f0683d7af 100644 --- a/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js +++ b/workspaces/wso2-platform/wso2-platform-extension/webpack.config.js @@ -26,16 +26,16 @@ const { createEnvDefinePlugin } = require('../../../common/scripts/env-webpack-h const envPath = path.resolve(__dirname, ".env"); const env = dotenv.config({ path: envPath }).parsed; - -let envKeys; -try { - envKeys = createEnvDefinePlugin(env); -} catch (error) { - console.warn('\n⚠️ Environment Variable Configuration Warning:'); - console.warn(error.message); - console.warn('Continuing build with empty environment variables...'); - envKeys = {}; +console.log("Fetching values for environment variables..."); +const { envKeys, missingVars } = createEnvDefinePlugin(env); +if (missingVars.length > 0) { + console.warn( + '\n⚠️ Environment Variable Configuration Warning:\n' + + `Missing required environment variables: ${missingVars.join(', ')}\n` + + `Please provide values in either .env file or runtime environment.\n` + ); } + //@ts-check /** @typedef {import('webpack').Configuration} WebpackConfig **/ From e1c3f129fef0a9a0af22de66cbc521b8e0764eb6 Mon Sep 17 00:00:00 2001 From: ChathuraIshara Date: Tue, 8 Jul 2025 09:45:35 +0530 Subject: [PATCH 018/148] Add sanitized intergration name preview in ProjectForm component --- .../src/views/BI/ProjectForm/index.tsx | 67 ++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx index 91a7d1a5970..d71ca3c17ce 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx @@ -17,7 +17,7 @@ */ import React, { useEffect, useState } from "react"; -import { Button, Icon, LocationSelector, TextField, Typography } from "@wso2/ui-toolkit"; +import { Button, Icon, LocationSelector, TextField, Typography,Codicon,Tooltip } from "@wso2/ui-toolkit"; import styled from "@emotion/styled"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { BodyText } from "../../styles"; @@ -47,6 +47,48 @@ const IconButton = styled.div` } `; +const PreviewContainer = styled.div` + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + padding: 8px 12px; + display: inline-flex; + align-items: center; + width: fit-content; + height: 28px; + gap: 8px; + background-color: var(--vscode-editor-background); + * { + cursor: default !important; + } +`; + +const InputPreviewWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 3px; + margin: 20px 0; +`; + +const PreviewText = styled(Typography)` + color: var(--vscode-sideBarTitle-foreground); + opacity: 0.5; + font-family: var(--vscode-editor-font-family, 'Monaco', 'Menlo', 'Ubuntu Mono', monospace); + word-break: break-all; + min-width: 100px; + display: flex; + align-items: center; + line-height: 1; +`; + +const PreviewIcon = styled(Codicon)` + display: flex; + align-items: center; +`; + +const sanitizeProjectName = (name: string): string => { + return name.replace(/[^a-z0-9]/gi, '_').toLowerCase(); +}; + export function ProjectForm() { const { rpcClient } = useRpcContext(); const [selectedModule, setSelectedModule] = useState("Main"); @@ -94,13 +136,22 @@ export function ProjectForm() { Name your integration and select a location to start building. - + + + + + + + {name ? sanitizeProjectName(name) : "integration_id"} + + + + Date: Tue, 8 Jul 2025 10:20:40 +0530 Subject: [PATCH 019/148] Add loading view on initial webview creation --- .../src/views/visualizer/webview.ts | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts index 658523db3d6..a48236fbafb 100644 --- a/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts +++ b/workspaces/ballerina/ballerina-extension/src/views/visualizer/webview.ts @@ -109,7 +109,16 @@ export class VisualizerWebview { private getWebviewContent(webView: Webview) { const body = `
-
+
+
+
+
+

Welcome to WSO2 Integrator: BI

+

Setting up your workspace and tools

+
+ Loading +
+
`; const bodyCss = ``; @@ -123,9 +132,10 @@ export class VisualizerWebview { .loader-wrapper { display: flex; justify-content: center; - align-items: center; + align-items: flex-start; height: 100%; width: 100%; + padding-top: 30vh; } .loader { width: 32px; @@ -151,6 +161,56 @@ export class VisualizerWebview { 50% {transform:scaleY(-1) rotate(0deg)} 100% {transform:scaleY(-1) rotate(-135deg)} } + /* New welcome view styles */ + .welcome-content { + text-align: center; + max-width: 500px; + padding: 2rem; + animation: fadeIn 1s ease-in-out; + font-family: var(--vscode-font-family); + } + .logo-container { + margin-bottom: 2rem; + display: flex; + justify-content: center; + } + .welcome-title { + color: var(--vscode-foreground); + margin: 0 0 0.5rem 0; + letter-spacing: -0.02em; + font-size: 1.5em; + font-weight: 400; + line-height: normal; + } + .welcome-subtitle { + color: var(--vscode-descriptionForeground); + font-size: 13px; + margin: 0 0 2rem 0; + opacity: 0.8; + } + .loading-text { + color: var(--vscode-foreground); + font-size: 13px; + font-weight: 500; + } + .loading-dots::after { + content: ''; + animation: dots 1.5s infinite; + } + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + @keyframes dots { + 0%, 20% { content: ''; } + 40% { content: '.'; } + 60% { content: '..'; } + 80%, 100% { content: '...'; } + } `; const scripts = ` function loadedScript() { From c27dc4b41f2815c7ff147256c076c2bfecbb3332 Mon Sep 17 00:00:00 2001 From: kaumini Date: Tue, 8 Jul 2025 10:21:02 +0530 Subject: [PATCH 020/148] Add loading view for main panel --- .../ballerina-visualizer/src/MainPanel.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx index fc58d1f00ed..5ef7b8c56f6 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/MainPanel.tsx @@ -71,6 +71,7 @@ import { ServiceClassConfig } from "./views/BI/ServiceClassEditor/ServiceClassCo import { AIAgentDesigner } from "./views/BI/AIChatAgent"; import { AIChatAgentWizard } from "./views/BI/AIChatAgent/AIChatAgentWizard"; import { BallerinaUpdateView } from "./views/BI/BallerinaUpdateView"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; const globalStyles = css` *, @@ -78,6 +79,33 @@ const globalStyles = css` *::after { box-sizing: border-box; } + + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + + .loading-dots::after { + content: ''; + animation: dots 1.5s infinite; + } + + @keyframes dots { + 0%, 20% { content: ''; } + 40% { content: '.'; } + 60% { content: '..'; } + 80%, 100% { content: '...'; } + } +`; + +const ProgressRing = styled(VSCodeProgressRing)` + height: 50px; + width: 50px; + margin: 1.5rem; `; const VisualizerContainer = styled.div` @@ -98,6 +126,50 @@ const PopUpContainer = styled.div` z-index: 2000; `; +const LoadingViewContainer = styled.div` + background-color: var(--vscode-editor-background); + height: 100vh; + width: 100%; + display: flex; + font-family: var(--vscode-font-family); +`; + +const LoadingContent = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + height: 100%; + width: 100%; + padding-top: 30vh; + text-align: center; + max-width: 500px; + margin: 0 auto; + animation: fadeIn 1s ease-in-out; +`; + +const LoadingTitle = styled.h1` + color: var(--vscode-foreground); + font-size: 1.5em; + font-weight: 400; + margin: 0; + letter-spacing: -0.02em; + line-height: normal; +`; + +const LoadingSubtitle = styled.p` + color: var(--vscode-descriptionForeground); + font-size: 13px; + margin: 0.5rem 0 2rem 0; + opacity: 0.8; +`; + +const LoadingText = styled.div` + color: var(--vscode-foreground); + font-size: 13px; + font-weight: 500; +`; + const MainPanel = () => { const { rpcClient } = useRpcContext(); const { sidePanel, setSidePanel, popupMessage, setPopupMessage, activePanel } = useVisualizerContext(); @@ -376,6 +448,20 @@ const MainPanel = () => { {/* {navActive && } */} {viewComponent && {viewComponent}} + {!viewComponent && ( + + + + + Loading Integration + Setting up your integration environment + + Please wait + + + + + )} {sidePanel !== "EMPTY" && sidePanel === "ADD_CONNECTION" && ( )} From a65d93de79c33a683cf11a282e57f7f89d134a09 Mon Sep 17 00:00:00 2001 From: kaumini Date: Tue, 8 Jul 2025 10:30:08 +0530 Subject: [PATCH 021/148] Add loading view on activating language server --- .../ballerina-visualizer/src/Visualizer.tsx | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx b/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx index 3d5eb3ce48b..22df08b7a63 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/Visualizer.tsx @@ -20,9 +20,71 @@ import React, { useEffect } from "react"; import { useRpcContext } from "@wso2/ballerina-rpc-client"; import { AIMachineStateValue, MachineStateValue } from "@wso2/ballerina-core"; import MainPanel from "./MainPanel"; +import styled from '@emotion/styled'; import { LoadingRing } from "./components/Loader"; import AIPanel from "./views/AIPanel/AIPanel"; import { AgentChat } from "./views/AgentChatPanel/AgentChat"; +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { Global, css } from '@emotion/react'; + +const ProgressRing = styled(VSCodeProgressRing)` + height: 50px; + width: 50px; + margin: 1.5rem; +`; + +const LoadingContent = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + height: 100%; + width: 100%; + padding-top: 30vh; + text-align: center; + max-width: 500px; + margin: 0 auto; + animation: fadeIn 1s ease-in-out; +`; + +const LoadingTitle = styled.h1` + color: var(--vscode-foreground); + font-size: 1.5em; + font-weight: 400; + margin: 0; + letter-spacing: -0.02em; + line-height: normal; +`; + +const LoadingSubtitle = styled.p` + color: var(--vscode-descriptionForeground); + font-size: 13px; + margin: 0.5rem 0 2rem 0; + opacity: 0.8; +`; + +const LoadingText = styled.div` + color: var(--vscode-foreground); + font-size: 13px; + font-weight: 500; +`; + +const globalStyles = css` + @keyframes fadeIn { + 0% { opacity: 0; } + 100% { opacity: 1; } + } + .loading-dots::after { + content: ''; + animation: dots 1.5s infinite; + } + @keyframes dots { + 0%, 20% { content: ''; } + 40% { content: '.'; } + 60% { content: '..'; } + 80%, 100% { content: '...'; } + } +`; const MODES = { VISUALIZER: "visualizer", @@ -61,7 +123,7 @@ export function Visualizer({ mode }: { mode: string }) { case MODES.VISUALIZER: return case MODES.AI: - return + return case MODES.AGENT_CHAT: return } @@ -75,6 +137,32 @@ const VisualizerComponent = React.memo(({ state }: { state: MachineStateValue }) case typeof state === 'object' && 'viewActive' in state && state.viewActive === "viewReady": return ; default: - return ; + return ; } }); + +const LanguageServerLoadingView = () => { + return ( +
+ + + + + Activating Language Server + + + Preparing your Ballerina development environment + + + Initializing + + +
+ ); +}; From 6337dee89dd1af94b4412776599e32d45289e4f8 Mon Sep 17 00:00:00 2001 From: kaumini Date: Tue, 8 Jul 2025 10:32:28 +0530 Subject: [PATCH 022/148] Update statemachine to create webview and render initial loader before activating LS --- .../ballerina-extension/src/stateMachine.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts index ed49d2e4cbb..b01ab4274fe 100644 --- a/workspaces/ballerina/ballerina-extension/src/stateMachine.ts +++ b/workspaces/ballerina/ballerina-extension/src/stateMachine.ts @@ -1,5 +1,5 @@ -import { ExtendedLangClient } from './core'; +import { ballerinaExtInstance, ExtendedLangClient } from './core'; import { createMachine, assign, interpret } from 'xstate'; import { activateBallerina } from './extension'; import { EVENT_TYPE, SyntaxTree, History, HistoryEntry, MachineStateValue, STByRangeRequest, SyntaxTreeResponse, UndoRedoManager, VisualizerLocation, webviewReady, MACHINE_VIEW, DIRECTORY_MAP, SCOPE, ProjectStructureResponse, ArtifactData, ProjectStructureArtifactResponse } from "@wso2/ballerina-core"; @@ -67,7 +67,7 @@ const stateMachine = createMachine( invoke: { src: checkForProjects, onDone: { - target: "activateLS", + target: "renderInitialView", actions: assign({ isBI: (context, event) => event.data.isBI, projectUri: (context, event) => event.data.projectPath, @@ -76,6 +76,17 @@ const stateMachine = createMachine( package: (context, event) => event.data.packageName, }) }, + onError: { + target: "renderInitialView" + } + } + }, + renderInitialView: { + invoke: { + src: 'openWebView', + onDone: { + target: "activateLS" + }, onError: { target: "activateLS" } @@ -256,6 +267,7 @@ const stateMachine = createMachine( // Get context values from the project storage so that we can restore the earlier state when user reopens vscode return new Promise((resolve, reject) => { if (!VisualizerWebview.currentPanel) { + ballerinaExtInstance.setContext(extension.context); VisualizerWebview.currentPanel = new VisualizerWebview(); RPCLayer._messenger.onNotification(webviewReady, () => { history = new History(); From 14537b1a2763e2d4f88519496e64a0501bf9c849 Mon Sep 17 00:00:00 2001 From: ChathuraIshara Date: Tue, 8 Jul 2025 10:33:03 +0530 Subject: [PATCH 023/148] Add autoFocus to Integration Name input in ProjectForm --- .../ballerina-visualizer/src/views/BI/ProjectForm/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx index d71ca3c17ce..76d4a91c407 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ProjectForm/index.tsx @@ -142,6 +142,7 @@ export function ProjectForm() { value={name} label="Integration Name" placeholder="Enter a integration name" + autoFocus={true} /> From 487b571c3deea4650f458dd42a3d0436202e22c0 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Tue, 8 Jul 2025 10:47:26 +0530 Subject: [PATCH 024/148] Fix intermittent test failure on projectSetup --- .../src/test/e2e-playwright-tests/utils.ts | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts index 96a61922c61..9bc6f0b2369 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts @@ -62,7 +62,7 @@ export async function toggleNotifications(disable: boolean) { export async function setupBallerinaIntegrator() { await page.selectSidebarItem('WSO2 Integrator: BI'); - const webview = await switchToIFrame('WSO2 Integrator: BI', page.page); + const webview = await getWebview('WSO2 Integrator: BI', page); if (!webview) { throw new Error('WSO2 Integrator: BI webview not found'); } @@ -86,10 +86,51 @@ export async function setupBallerinaIntegrator() { } } +async function getWebview(viewName: string, page: ExtendedPage) { + let webview; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + await page.page.waitForLoadState('domcontentloaded'); + await page.page.waitForTimeout(1000); + + webview = await switchToIFrame(viewName, page.page); + if (webview) { + return webview; + } + // If webview is falsy, treat it as a failed attempt + console.log(`Attempt ${retryCount + 1} failed: switchToIFrame returned ${webview}`); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (message.includes('Frame was detached')) { + console.log(`Frame was detached, retrying (${retryCount + 1}/${maxRetries})`); + } else { + console.log(`Attempt ${retryCount + 1} failed to access iframe:`, message); + } + } + + // Always increment retry count after each attempt + retryCount++; + + // Only retry if we haven't reached max retries + if (retryCount < maxRetries) { + await page.page.waitForTimeout(2000); + try { + await page.selectSidebarItem(viewName); + } catch (sidebarError) { + console.log('Failed to reselect sidebar item:', sidebarError); + } + } + } + throw new Error(`Failed to access iframe for ${viewName} after ${maxRetries} attempts`); +} + export async function createProject(page: ExtendedPage, projectName?: string) { console.log('Creating new project'); await setupBallerinaIntegrator(); - const webview = await switchToIFrame('WSO2 Integrator: BI', page.page, 60000); + const webview = await getWebview('WSO2 Integrator: BI', page); if (!webview) { throw new Error('WSO2 Integrator: BI webview not found'); } @@ -108,7 +149,7 @@ export async function createProject(page: ExtendedPage, projectName?: string) { } }); await form.submit('Create Integration'); - const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + const artifactWebView = await getWebview('WSO2 Integrator: BI', page); if (!artifactWebView) { throw new Error('WSO2 Integrator: BI webview not found'); } @@ -148,7 +189,7 @@ export function initTest(newProject: boolean = false, skipProjectCreation: boole export async function addArtifact(artifactName: string, testId: string) { console.log(`Adding artifact: ${artifactName}`); - const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + const artifactWebView = await getWebview('WSO2 Integrator: BI', page); if (!artifactWebView) { throw new Error('WSO2 Integrator: BI webview not found'); } @@ -162,7 +203,7 @@ export async function addArtifact(artifactName: string, testId: string) { export async function enableICP() { console.log('Enabling ICP'); - const webview = await switchToIFrame('WSO2 Integrator: BI', page.page); + const webview = await getWebview('WSO2 Integrator: BI', page); if (!webview) { throw new Error('WSO2 Integrator: BI webview not found'); } From 071371cc0c1f71e5eb9c76d9be7c4244454eb47b Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Tue, 8 Jul 2025 13:36:22 +0530 Subject: [PATCH 025/148] Add vscode setting to enable ai suggestions in flow diagram --- workspaces/ballerina/ballerina-extension/package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 3a35c7bb328..49e33cd5fe9 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -207,6 +207,11 @@ "tags": [ "experimental" ] + }, + "ballerina.enableAiSuggestions": { + "type": "boolean", + "default": true, + "description": "Enable AI suggestions in the Flow Diagram View." } } }, From 551f959102bad90b30df5437747d1ef449cfe6be Mon Sep 17 00:00:00 2001 From: madushajg Date: Tue, 8 Jul 2025 13:43:32 +0530 Subject: [PATCH 026/148] Remove unused import of STNode --- .../src/components/Diagram/Node/ObjectOutput/ObjectOutputNode.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/workspaces/ballerina/inline-data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputNode.ts b/workspaces/ballerina/inline-data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputNode.ts index d15e7b35a5e..eba900b1697 100644 --- a/workspaces/ballerina/inline-data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputNode.ts +++ b/workspaces/ballerina/inline-data-mapper/src/components/Diagram/Node/ObjectOutput/ObjectOutputNode.ts @@ -24,7 +24,6 @@ import { DataMapperNodeModel } from "../commons/DataMapperNode"; import { getFilteredMappings, getSearchFilteredOutput, hasNoOutputMatchFound } from "../../utils/search-utils"; import { getTypeName } from "../../utils/type-utils"; import { OBJECT_OUTPUT_TARGET_PORT_PREFIX } from "../../utils/constants"; -import { STNode } from "@wso2/syntax-tree"; import { findInputNode } from "../../utils/node-utils"; import { InputOutputPortModel } from "../../Port"; import { DataMapperLinkModel } from "../../Link"; From 6168900e66a4787d7c5f98609baf6518104ec6be Mon Sep 17 00:00:00 2001 From: Senith Uthsara Date: Tue, 8 Jul 2025 15:11:50 +0530 Subject: [PATCH 027/148] configure rush to accept all versions of node v22 --- rush.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rush.json b/rush.json index bfd39805c73..f600b8dc962 100644 --- a/rush.json +++ b/rush.json @@ -38,7 +38,7 @@ * LTS schedule: https://nodejs.org/en/about/releases/ * LTS versions: https://nodejs.org/en/download/releases/ */ - "nodeSupportedVersionRange": ">=20.0.0 <=22.16.0", + "nodeSupportedVersionRange": ">=20.0.0 <23.0.0", /** * If the version check above fails, Rush will display a message showing the current * node version and the supported version range. You can use this setting to provide From cccda577df2ff603411e5c104a2a8d770aa11ae3 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 8 Jul 2025 13:56:11 +0530 Subject: [PATCH 028/148] Fix Config.toml creation via config editor UI --- .../src/rpc-managers/bi-diagram/rpc-manager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 a170a1315cc..0080b21e004 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 @@ -661,12 +661,12 @@ export class BiDiagramRpcManager implements BIDiagramAPI { } - // Function to open config toml + // Function to open Config.toml async openConfigToml(params: OpenConfigTomlRequest): Promise { return new Promise(async (resolve) => { const currentProject: BallerinaProject | undefined = await getCurrentBIProject(params.filePath); - const configFilePath = path.join(StateMachine.context().projectUri, "config.toml"); + const configFilePath = path.join(StateMachine.context().projectUri, "Config.toml"); const ignoreFile = path.join(StateMachine.context().projectUri, ".gitignore"); const docLink = "https://ballerina.io/learn/provide-values-to-configurable-variables/#provide-via-toml-syntax"; const uri = Uri.file(configFilePath); @@ -694,8 +694,8 @@ export class BiDiagramRpcManager implements BIDiagramAPI { if (fs.existsSync(ignoreFile)) { const ignoreUri = Uri.file(ignoreFile); let ignoreContent: string = fs.readFileSync(ignoreUri.fsPath, 'utf8'); - if (!ignoreContent.includes("config.toml")) { - ignoreContent += `\n${"config.toml"}\n`; + if (!ignoreContent.includes("Config.toml")) { + ignoreContent += `\n${"Config.toml"}\n`; fs.writeFile(ignoreUri.fsPath, ignoreContent, function (error) { if (error) { return window.showErrorMessage('Unable to update the .gitIgnore file: ' + error); From 53e0dc7d525f762e31892b8b7260d353327110e3 Mon Sep 17 00:00:00 2001 From: madushajg Date: Tue, 8 Jul 2025 20:19:54 +0530 Subject: [PATCH 029/148] Fix hiding intermediate nodes when applying searches in data mapper --- .../Node/LinkConnector/LinkConnectorNode.ts | 13 ++++++------- .../Node/QueryExpression/QueryExpressionNode.ts | 14 ++++++-------- .../src/components/Hooks/index.tsx | 4 ++++ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/LinkConnector/LinkConnectorNode.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/LinkConnector/LinkConnectorNode.ts index 361976b4771..0a5ed2dde43 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/LinkConnector/LinkConnectorNode.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/LinkConnector/LinkConnectorNode.ts @@ -70,7 +70,7 @@ export class LinkConnectorNode extends DataMapperNodeModel { public value: string; public diagnostics: Diagnostic[]; public hidden: boolean; - public hasInitialized: boolean; + public hasScreenWidthChanged: boolean; constructor( public context: IDataMapperContext, @@ -151,11 +151,7 @@ export class LinkConnectorNode extends DataMapperNodeModel { node.recordField, targetPortPrefix, (portId: string) => node.getPort(portId) as RecordFieldPortModel, rootName); - const previouslyHidden = this.hidden; this.hidden = this.targetMappedPort?.portName !== this.targetPort?.portName; - if (this.hidden !== previouslyHidden) { - this.hasInitialized = false; - } } } }); @@ -163,7 +159,7 @@ export class LinkConnectorNode extends DataMapperNodeModel { } initLinks(): void { - if (this.hasInitialized) { + if (this.hasScreenWidthChanged) { return; } if (!this.hidden) { @@ -258,7 +254,6 @@ export class LinkConnectorNode extends DataMapperNodeModel { }) } } - this.hasInitialized = true; } updateSource(): void { @@ -349,4 +344,8 @@ export class LinkConnectorNode extends DataMapperNodeModel { } } + + public setHasScreenWidthChanged(hasScreenWidthChanged: boolean): void { + this.hasScreenWidthChanged = hasScreenWidthChanged; + } } diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/QueryExpression/QueryExpressionNode.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/QueryExpression/QueryExpressionNode.ts index 83e82d96f48..892236a1ea8 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/QueryExpression/QueryExpressionNode.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Node/QueryExpression/QueryExpressionNode.ts @@ -76,7 +76,7 @@ export class QueryExpressionNode extends DataMapperNodeModel { public targetFieldFQN: string; public hidden: boolean; - public hasInitialized: boolean; + public hasScreenWidthChanged: boolean; constructor( public context: IDataMapperContext, @@ -279,19 +279,14 @@ export class QueryExpressionNode extends DataMapperNodeModel { }); } - const previouslyHidden = this.hidden; this.hidden = this.targetPort?.hidden; - - if (this.hidden !== previouslyHidden) { - this.hasInitialized = false; - } while (this.targetPort && this.targetPort.hidden){ this.targetPort = this.targetPort.parentModel; } } initLinks(): void { - if (this.hasInitialized) { + if (this.hasScreenWidthChanged) { return; } if (!this.hidden) { @@ -357,7 +352,6 @@ export class QueryExpressionNode extends DataMapperNodeModel { this.getModel().addAll(link); } } - this.hasInitialized = true; } public updatePosition() { @@ -395,4 +389,8 @@ export class QueryExpressionNode extends DataMapperNodeModel { this.context.applyModifications(modifications); } + + public setHasScreenWidthChanged(hasScreenWidthChanged: boolean): void { + this.hasScreenWidthChanged = hasScreenWidthChanged; + } } diff --git a/workspaces/ballerina/data-mapper-view/src/components/Hooks/index.tsx b/workspaces/ballerina/data-mapper-view/src/components/Hooks/index.tsx index e0256401115..1832ab01a9e 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Hooks/index.tsx +++ b/workspaces/ballerina/data-mapper-view/src/components/Hooks/index.tsx @@ -121,6 +121,9 @@ export const useDiagramModel = ( if (prevScreenWidth.current !== screenWidth && diagramModel.getNodes().length > 0) { const diagModelNodes = diagramModel.getNodes() as DataMapperNodeModel[]; diagModelNodes.forEach(diagModelNode => { + if (diagModelNode instanceof LinkConnectorNode || diagModelNode instanceof QueryExpressionNode) { + diagModelNode.setHasScreenWidthChanged(true); + } const repositionedNode = nodes.find(newNode => isOutputNode(newNode) && newNode.id === diagModelNode.id); if (repositionedNode) { diagModelNode.setPosition(repositionedNode.getX(), repositionedNode.getY()); @@ -159,6 +162,7 @@ export const useDiagramModel = ( node.setModel(newModel); await node.initPorts(); if (node instanceof LinkConnectorNode || node instanceof QueryExpressionNode) { + node.setHasScreenWidthChanged(false); continue; } node.initLinks(); From a90b1669de1109fe845a0054399ba7d721d531f8 Mon Sep 17 00:00:00 2001 From: madushajg Date: Wed, 9 Jul 2025 09:53:13 +0530 Subject: [PATCH 030/148] Enhanced DataMapperErrorBoundary to display error messages dynamically --- .../src/components/DataMapper/DataMapper.tsx | 13 ++-- .../DataMapper/ErrorBoundary/Error/index.tsx | 53 ---------------- .../DataMapper/ErrorBoundary/Error/style.ts | 57 ----------------- .../DataMapper/ErrorBoundary/index.tsx | 61 +++++++++++++++++-- .../Diagram/visitors/NodeInitVisitor.ts | 15 +++++ 5 files changed, 80 insertions(+), 119 deletions(-) delete mode 100644 workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/index.tsx delete mode 100644 workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/style.ts diff --git a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/DataMapper.tsx b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/DataMapper.tsx index e634926f9c8..055a15e7bcc 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/DataMapper.tsx +++ b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/DataMapper.tsx @@ -163,6 +163,11 @@ export interface ExpressionInfo { label?: string; } +export interface InternalError { + hasError: boolean; + message?: string; +} + export interface DMNode { // the parent node of the selected node stNode: STNode; @@ -257,7 +262,7 @@ export function DataMapperC(props: DataMapperViewProps) { const [dmContext, setDmContext] = useState(); const [dmNodes, setDmNodes] = useState(); const [shouldRestoreTypes, setShouldRestoreTypes] = useState(true); - const [hasInternalError, setHasInternalError] = useState(false); + const [internalError, setInternalError] = useState(); const [errorKind, setErrorKind] = useState(); const [isSelectionComplete, setIsSelectionComplete] = useState(false); const [currentReferences, setCurrentReferences] = useState([]); @@ -410,7 +415,7 @@ export function DataMapperC(props: DataMapperViewProps) { dispatchSelection({ type: ViewOption.INITIALIZE, payload: { prevST, selectedST: selectedST || defaultSt } }); } catch (e) { - setHasInternalError(true); + setInternalError({ hasError: true}); // tslint:disable-next-line:no-console console.error(e); } @@ -492,7 +497,7 @@ export function DataMapperC(props: DataMapperViewProps) { setDmNodes(nodes); } } catch (e) { - setHasInternalError(true); + setInternalError({ hasError: true, message: e.message}); // tslint:disable-next-line:no-console console.error(e); } @@ -569,7 +574,7 @@ export function DataMapperC(props: DataMapperViewProps) { }; return ( - + {selection.state === DMState.INITIALIZED && (
diff --git a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/index.tsx b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/index.tsx deleted file mode 100644 index c8d541e5ae8..00000000000 --- a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/index.tsx +++ /dev/null @@ -1,53 +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 { Typography } from "@wso2/ui-toolkit"; -import { ISSUES_URL } from "../../utils"; - -export default function Default() { - 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-view/src/components/DataMapper/ErrorBoundary/Error/style.ts b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/style.ts deleted file mode 100644 index 2f55e24ab9c..00000000000 --- a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/Error/style.ts +++ /dev/null @@ -1,57 +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({ - 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" - }), - 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-view/src/components/DataMapper/ErrorBoundary/index.tsx b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/index.tsx index 8831d90c525..5d222ac1fb9 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/index.tsx +++ b/workspaces/ballerina/data-mapper-view/src/components/DataMapper/ErrorBoundary/index.tsx @@ -17,19 +17,52 @@ */ import * as React from "react"; -import ErrorScreen from "./Error"; +import { WarningBanner } from "../Warning/DataMapperWarning"; +import { css, keyframes } from "@emotion/css"; +import { Typography } from "@wso2/ui-toolkit"; +import { ISSUES_URL } from "../utils"; + +const fadeIn = keyframes` + from { opacity: 0.5; } + to { opacity: 1; } +`; + +const classes = { + errorContainer: css({ + display: 'flex', + flexDirection: 'column', + opacity: 0.7 + }), + errorBanner: css({ + borderColor: "var(--vscode-errorForeground)" + }), + errorMessage: css({ + zIndex: 1, + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '500px', + animation: `${fadeIn} 0.5s ease-in-out` + }), + errorTitle: css({ + marginTop: "10px" + }) +} export interface DataMapperErrorBoundaryProps { hasError: boolean; + message?: string; children?: React.ReactNode; } -export class DataMapperErrorBoundaryC extends React.Component { - state = { hasError: false } +export class DataMapperErrorBoundaryC extends React.Component { + state = { hasError: false, message: "" } static getDerivedStateFromProps(props: DataMapperErrorBoundaryProps) { return { - hasError: props.hasError + hasError: props.hasError, + message: props.message }; } @@ -42,9 +75,27 @@ export class DataMapperErrorBoundaryC extends React.Component + + A problem occurred while rendering the Data Mapper. + + + Please raise an issue with the sample code in our issue tracker + +
+ ); + render() { if (this.state.hasError) { - return ; + return ( +
+ +
+ ); } return this.props?.children; } diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts index 8921cb6b715..738387586d5 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts @@ -81,6 +81,9 @@ import { QueryParentFindingVisitor } from "./QueryParentFindingVisitor" import { QueryExprFindingVisitorByPosition } from "./QueryExprFindingVisitorByPosition"; import { DefaultPortModel } from "@projectstorm/react-diagrams"; +const INNER_FROM_CLAUSE_ERROR_MESSAGE = `Your mapping contains a query expression with one or more inner 'from' clauses, which are currently not supported. +To proceed, please refactor your mapping to remove any nested 'from' clauses and try again.`; + export class NodeInitVisitor implements Visitor { private inputParamNodes: DataMapperNodeModel[] = []; @@ -134,6 +137,12 @@ export class NodeInitVisitor implements Visitor { ); } else if (STKindChecker.isQueryExpression(bodyExpr)) { const { queryPipeline: { fromClause, intermediateClauses } } = bodyExpr; + + const hasInnerFromClauses = intermediateClauses.some(clause => STKindChecker.isFromClause(clause)); + if (hasInnerFromClauses) { + throw new Error(INNER_FROM_CLAUSE_ERROR_MESSAGE); + } + if (this.context.selection.selectedST.fieldPath === FUNCTION_BODY_QUERY) { isFnBodyQueryExpr = true; const selectClause = bodyExpr?.selectClause || bodyExpr?.resultClause; @@ -527,6 +536,12 @@ export class NodeInitVisitor implements Visitor { if (isSelectedExpr) { const { fromClause, intermediateClauses } = node.queryPipeline; + + const hasInnerFromClauses = intermediateClauses.some(clause => STKindChecker.isFromClause(clause)); + if (hasInnerFromClauses) { + throw new Error(INNER_FROM_CLAUSE_ERROR_MESSAGE); + } + if (parentIdentifier) { const intermediateClausesHeight = 100 + intermediateClauses.length * OFFSETS.INTERMEDIATE_CLAUSE_HEIGHT; // create output node From ed5f7923d315e17f71b72a09fdc9e59d1f340898 Mon Sep 17 00:00:00 2001 From: kaumini Date: Wed, 9 Jul 2025 11:42:19 +0530 Subject: [PATCH 031/148] Fix config create cmd --- .../src/features/project/cmds/configRun.ts | 19 ++++++++++++------- .../rpc-managers/bi-diagram/rpc-manager.ts | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts index a36afc39675..5bfc2afaf7b 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts @@ -16,12 +16,11 @@ * under the License. */ -import { commands, languages, Uri, window } from "vscode"; +import { commands, languages, Uri, window, workspace } from "vscode"; import { BALLERINA_COMMANDS, getRunCommand, PALETTE_COMMANDS, runCommand } from "./cmd-runner"; import { ballerinaExtInstance } from "../../../core"; -import { prepareAndGenerateConfig } from "../../config-generator/configGenerator"; import { getConfigCompletions } from "../../config-generator/utils"; - +import { BiDiagramRpcManager } from "../../../rpc-managers/bi-diagram/rpc-manager"; function activateConfigRunCommand() { // register the config view run command @@ -37,10 +36,16 @@ function activateConfigRunCommand() { commands.registerCommand(PALETTE_COMMANDS.CONFIG_CREATE_COMMAND, async () => { try { - const currentProject = ballerinaExtInstance.getDocumentContext().getCurrentProject(); - const filePath = window.activeTextEditor.document; - const path = filePath.uri.fsPath; - prepareAndGenerateConfig(ballerinaExtInstance, currentProject ? currentProject.path! : path, true); + // Get opened workspace path + let projectPath: string | undefined; + if (window.activeTextEditor) { + projectPath = window.activeTextEditor.document.uri.fsPath; + } else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { + projectPath = workspace.workspaceFolders[0].uri.fsPath; + } + + const biDiagramRpcManager = new BiDiagramRpcManager(); + await biDiagramRpcManager.openConfigToml({filePath: projectPath}); return; } catch (error) { throw new Error("Unable to create Config.toml file. Try again with a valid Ballerina file open in the editor."); 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 a170a1315cc..f40b4946af4 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 @@ -666,7 +666,7 @@ export class BiDiagramRpcManager implements BIDiagramAPI { return new Promise(async (resolve) => { const currentProject: BallerinaProject | undefined = await getCurrentBIProject(params.filePath); - const configFilePath = path.join(StateMachine.context().projectUri, "config.toml"); + const configFilePath = path.join(StateMachine.context().projectUri, "Config.toml"); const ignoreFile = path.join(StateMachine.context().projectUri, ".gitignore"); const docLink = "https://ballerina.io/learn/provide-values-to-configurable-variables/#provide-via-toml-syntax"; const uri = Uri.file(configFilePath); From a088414d5125aba942482797acda13c0971653c0 Mon Sep 17 00:00:00 2001 From: kaumini Date: Wed, 9 Jul 2025 11:46:11 +0530 Subject: [PATCH 032/148] Fix config create cmd --- .../src/features/project/cmds/configRun.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts index 5bfc2afaf7b..1dec715093a 100644 --- a/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts +++ b/workspaces/ballerina/ballerina-extension/src/features/project/cmds/configRun.ts @@ -36,16 +36,16 @@ function activateConfigRunCommand() { commands.registerCommand(PALETTE_COMMANDS.CONFIG_CREATE_COMMAND, async () => { try { - // Get opened workspace path - let projectPath: string | undefined; + // Open current config.toml or create a new config.toml if it does not exist + let projectPath: string; if (window.activeTextEditor) { projectPath = window.activeTextEditor.document.uri.fsPath; } else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) { projectPath = workspace.workspaceFolders[0].uri.fsPath; } - + const biDiagramRpcManager = new BiDiagramRpcManager(); - await biDiagramRpcManager.openConfigToml({filePath: projectPath}); + await biDiagramRpcManager.openConfigToml({ filePath: projectPath }); return; } catch (error) { throw new Error("Unable to create Config.toml file. Try again with a valid Ballerina file open in the editor."); From 21db483ebc66a6b39ec5f03cd44b6503276ab50b Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 9 Jul 2025 11:49:48 +0530 Subject: [PATCH 033/148] Update import icon --- .../ballerina/type-editor/src/TypeEditor/TypeEditor.tsx | 2 +- .../type-editor/src/components/FileSelector/index.tsx | 6 +++--- .../common-libs/font-wso2-vscode/src/icons/bi-import.svg | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx index d09f218240e..7b2e0456c90 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/TypeEditor.tsx @@ -140,7 +140,7 @@ export function TypeEditor(props: TypeEditorProps) { icon: } ]} diff --git a/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx b/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx index cc8597e78ce..392ab8499de 100644 --- a/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx +++ b/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx @@ -52,13 +52,13 @@ export function FileSelector(props: FileSelectorProps) { ); diff --git a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-import.svg b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-import.svg index 3f5ec81b668..ea552be6921 100644 --- a/workspaces/common-libs/font-wso2-vscode/src/icons/bi-import.svg +++ b/workspaces/common-libs/font-wso2-vscode/src/icons/bi-import.svg @@ -15,6 +15,4 @@ ~ specific language governing permissions and limitations ~ under the License. --> - - - + \ No newline at end of file From b20a4d8becb8889dceb46ba633a62aac6722a278 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 9 Jul 2025 11:53:02 +0530 Subject: [PATCH 034/148] Make imported type not editable --- .../EntityNode/EntityHead/EntityHead.tsx | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx index dab70a6c754..41c4a7b438b 100644 --- a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx +++ b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx @@ -24,7 +24,7 @@ import { EntityHead, EntityName } from '../styles'; import { CtrlClickGo2Source } from '../../../common/CtrlClickHandler/CtrlClickGo2Source'; import { DiagramContext } from '../../../common'; import styled from '@emotion/styled'; -import { Button, Item, Menu, MenuItem, Popover } from '@wso2/ui-toolkit'; +import { Button, Item, Menu, MenuItem, Popover, ThemeColors } from '@wso2/ui-toolkit'; import { MoreVertIcon } from '../../../../resources'; import { GraphQLIcon } from '../../../../resources/assets/icons/GraphqlIcon'; @@ -68,11 +68,25 @@ const HeaderWrapper = styled.div` width: 100%; `; +const ImportedLabel = styled.span` + background-color: ${ThemeColors.SURFACE_CONTAINER}; + border-radius: 3px; + color: ${ThemeColors.ON_SURFACE_VARIANT}; + font-family: GilmerRegular; + font-size: 10px; + height: 20px; + line-height: 20px; + padding: 0 6px; + margin-left: 8px; + white-space: nowrap; +`; + export function EntityHeadWidget(props: ServiceHeadProps) { const { engine, node, isSelected } = props; const { setFocusedNodeId, onEditNode, goToSource } = useContext(DiagramContext); const displayName: string = node.getID()?.slice(node.getID()?.lastIndexOf(':') + 1); + const isImported = !node?.entityObject?.editable; const [anchorEl, setAnchorEl] = useState(null); const isMenuOpen = Boolean(anchorEl); @@ -82,7 +96,7 @@ export function EntityHeadWidget(props: ServiceHeadProps) { }; const onNodeEdit = () => { - if (onEditNode) { + if (onEditNode && node?.entityObject?.editable) { if (node.isGraphqlRoot) { onEditNode(node.getID(), true); } else { @@ -103,13 +117,23 @@ export function EntityHeadWidget(props: ServiceHeadProps) { } const menuItems: Item[] = [ + ...(node?.entityObject?.editable ? [ + { + id: "edit", + label: "Edit", + onClick: () => onNodeEdit(), + }, + { + id: "goToSource", + label: "Source", + onClick: () => onGoToSource() + } + ] : []), { - id: "edit", - label: "Edit", - onClick: () => onNodeEdit(), - }, - { id: "goToSource", label: "Source", onClick: () => onGoToSource() }, - { id: "focusView", label: "Focused View", onClick: () => onFocusedView() } + id: "focusView", + label: "Focused View", + onClick: () => onFocusedView() + } ]; const isClickable = true; @@ -133,12 +157,17 @@ export function EntityHeadWidget(props: ServiceHeadProps) { )} {displayName} + {isImported && ( + + Imported Type + + )} {/* {selectedNodeId === node.getID() && ( From 8fbd12fff1dae247291a41e3adc70358cc681768 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 9 Jul 2025 13:37:39 +0530 Subject: [PATCH 035/148] Update import file button styling --- .../src/components/FileSelector/index.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx b/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx index 392ab8499de..b98c77cbd32 100644 --- a/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx +++ b/workspaces/ballerina/type-editor/src/components/FileSelector/index.tsx @@ -16,7 +16,7 @@ * under the License. */ import React from "react"; -import { Button, Icon, Typography } from "@wso2/ui-toolkit"; +import { LinkButton, Icon, Typography } from "@wso2/ui-toolkit"; export interface FileSelectorProps { label: string; @@ -49,17 +49,18 @@ export function FileSelector(props: FileSelectorProps) { return ( - + > + {`Import ${extension.toUpperCase()} File`} + + ); } From 43d9ed81f245fc1db8c65b33a1c1d207b98de232 Mon Sep 17 00:00:00 2001 From: madushajg Date: Wed, 9 Jul 2025 13:44:32 +0530 Subject: [PATCH 036/148] Enhance label positioning logic in OveriddenLabelWidget --- .../src/components/Diagram/Link/link-utils.ts | 4 --- .../Diagram/LinkState/CreateLinkState.ts | 2 +- .../OverriddenLinkLayer/LabelWidget.tsx | 32 ++++++++++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Link/link-utils.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Link/link-utils.ts index 36f651f780a..91777eccc70 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Link/link-utils.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Link/link-utils.ts @@ -141,10 +141,6 @@ export function removePendingMappingTempLinkIfExists(link: LinkModel) { } export function userActionRequiredMapping(mappingType: MappingType, targetPort: PortModel): boolean { - if (targetPort instanceof RecordFieldPortModel && !targetPort?.parentModel) { - // No user action required provided for root target ports. - return false; - } return mappingType === MappingType.ArrayToArray || mappingType === MappingType.ArrayToSingleton || mappingType === MappingType.RecordToRecord diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/LinkState/CreateLinkState.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/LinkState/CreateLinkState.ts index b4ad5cd4170..84c6882362a 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/LinkState/CreateLinkState.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/LinkState/CreateLinkState.ts @@ -60,7 +60,7 @@ export class CreateLinkState extends State { } if (element instanceof RequiredParamNode - || element instanceof FromClauseNode + || element instanceof FromClauseNode || element instanceof LetExpressionNode || element instanceof ModuleVariableNode || element instanceof EnumTypeNode diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/OverriddenLinkLayer/LabelWidget.tsx b/workspaces/ballerina/data-mapper-view/src/components/Diagram/OverriddenLinkLayer/LabelWidget.tsx index 357623ca782..f6120d0df40 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/OverriddenLinkLayer/LabelWidget.tsx +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/OverriddenLinkLayer/LabelWidget.tsx @@ -81,11 +81,35 @@ export class OveriddenLabelWidget extends React.Component { }; const pathCentre = path.getPointAtLength(position); + const canvas = this.props.engine.getCanvas(); + + // Get canvas boundaries + const canvasWidth = canvas?.offsetWidth || 0; + const canvasHeight = canvas?.offsetHeight || 0; + + // Calculate initial centered position + let x = pathCentre.x - labelDimensions.width / 2 + this.props.label.getOptions().offsetX; + let y = pathCentre.y - labelDimensions.height / 2 + this.props.label.getOptions().offsetY; + + // Apply boundary constraints to keep label fully visible + // Ensure label doesn't go off the left edge + if (x < 0) { + x = 0; + } + // Ensure label doesn't go off the right edge + if (x + labelDimensions.width > canvasWidth) { + x = canvasWidth - labelDimensions.width; + } + // Ensure label doesn't go off the top edge + if (y < 0) { + y = 0; + } + // Ensure label doesn't go off the bottom edge + if (y + labelDimensions.height > canvasHeight) { + y = canvasHeight - labelDimensions.height; + } - const labelCoordinates = { - x: pathCentre.x - labelDimensions.width / 2 + this.props.label.getOptions().offsetX, - y: pathCentre.y - labelDimensions.height / 2 + this.props.label.getOptions().offsetY - }; + const labelCoordinates = { x, y }; this.ref.current.style.transform = `translate(${labelCoordinates.x}px, ${labelCoordinates.y}px)`; }; From eb92929df8f2f751a3e87e1d9cbb1aad2ef7caf1 Mon Sep 17 00:00:00 2001 From: Anjana S Porawagama Date: Wed, 9 Jul 2025 13:48:34 +0530 Subject: [PATCH 037/148] Add data-testid attributes to action buttons and nodes. --- .../ComponentListView/IntegrationApiPanel.tsx | 2 ++ .../src/views/GraphQLDiagram/index.tsx | 2 +- .../AgentCallNode/AgentCallNodeWidget.tsx | 22 +++++++++---------- .../ballerina/type-diagram/src/Diagram.tsx | 12 +++++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ComponentListView/IntegrationApiPanel.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ComponentListView/IntegrationApiPanel.tsx index bc85f3683de..08197b8b860 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/ComponentListView/IntegrationApiPanel.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/ComponentListView/IntegrationApiPanel.tsx @@ -63,6 +63,7 @@ export function IntegrationAPIPanel(props: IntegrationAPIPanelProps) { tooltip={isDisabled ? OutOfScopeComponentTooltip : ""} /> } title="GraphQL Service" @@ -73,6 +74,7 @@ export function IntegrationAPIPanel(props: IntegrationAPIPanelProps) { isBeta /> } title="TCP Service" diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/GraphQLDiagram/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/GraphQLDiagram/index.tsx index 1f149d74b69..925faf37976 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/GraphQLDiagram/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/GraphQLDiagram/index.tsx @@ -227,7 +227,7 @@ export function GraphQLDiagram(props: GraphQLDiagramProps) { } actions={ - + Edit diff --git a/workspaces/ballerina/bi-diagram/src/components/nodes/AgentCallNode/AgentCallNodeWidget.tsx b/workspaces/ballerina/bi-diagram/src/components/nodes/AgentCallNode/AgentCallNodeWidget.tsx index 51b921e5986..38dc1c80192 100644 --- a/workspaces/ballerina/bi-diagram/src/components/nodes/AgentCallNode/AgentCallNodeWidget.tsx +++ b/workspaces/ballerina/bi-diagram/src/components/nodes/AgentCallNode/AgentCallNodeWidget.tsx @@ -81,8 +81,8 @@ export namespace NodeStyles { props.hasError ? ThemeColors.ERROR : props.hovered && !props.disabled - ? ThemeColors.HIGHLIGHT - : ThemeColors.OUTLINE_VARIANT}; + ? ThemeColors.HIGHLIGHT + : ThemeColors.OUTLINE_VARIANT}; border-radius: 10px; background-color: ${(props: NodeStyleProp) => props?.isActiveBreakpoint ? ThemeColors.DEBUGGER_BREAKPOINT_BACKGROUND : ThemeColors.SURFACE_DIM}; @@ -306,7 +306,7 @@ interface AgentCallNodeWidgetProps { onClick?: (node: FlowNode) => void; } -export interface NodeWidgetProps extends Omit {} +export interface NodeWidgetProps extends Omit { } export function AgentCallNodeWidget(props: AgentCallNodeWidgetProps) { const { model, engine, onClick } = props; @@ -490,7 +490,7 @@ export function AgentCallNodeWidget(props: AgentCallNodeWidgetProps) { } return ( - + ( onToolClick(tool)} css={css` cursor: pointer; @@ -861,11 +860,10 @@ export function AgentCallNodeWidget(props: AgentCallNodeWidgetProps) { {/* Add "Add new tool" button below all tools */} 0 - ? (tools.length + 1) * (NODE_HEIGHT + AGENT_NODE_TOOL_GAP) + AGENT_NODE_TOOL_SECTION_GAP - : NODE_HEIGHT + AGENT_NODE_TOOL_SECTION_GAP - })`} + transform={`translate(-11, ${tools.length > 0 + ? (tools.length + 1) * (NODE_HEIGHT + AGENT_NODE_TOOL_GAP) + AGENT_NODE_TOOL_SECTION_GAP + : NODE_HEIGHT + AGENT_NODE_TOOL_SECTION_GAP + })`} onClick={onAddToolClick} style={{ cursor: "pointer" }} > diff --git a/workspaces/ballerina/type-diagram/src/Diagram.tsx b/workspaces/ballerina/type-diagram/src/Diagram.tsx index 9a98cae7de8..1c3e3e7812b 100644 --- a/workspaces/ballerina/type-diagram/src/Diagram.tsx +++ b/workspaces/ballerina/type-diagram/src/Diagram.tsx @@ -63,7 +63,7 @@ export function TypeDiagram(props: TypeDiagramProps) { const drawDiagram = (focusedNode?: string) => { let diagramModel; - + // Create diagram model based on type if (isGraphql && rootService) { console.log("Modeling graphql diagram"); @@ -72,24 +72,24 @@ export function TypeDiagram(props: TypeDiagramProps) { console.log("Modeling entity diagram"); diagramModel = entityModeller(typeModel, focusedNode); } - + if (diagramModel) { // Setup initial model diagramModel.addLayer(new OverlayLayerModel()); diagramEngine.setModel(diagramModel); setDiagramModel(diagramModel); - + // Layout and focus handling setTimeout(() => { dagreEngine.redistribute(diagramEngine.getModel()); - + if (selectedNodeId) { const selectedModel = diagramEngine.getModel().getNode(selectedNodeId); focusToNode(selectedModel, diagramEngine.getModel().getZoomLevel(), diagramEngine); } else if (diagramEngine?.getCanvas()?.getBoundingClientRect) { diagramEngine.zoomToFitNodes({ margin: 10, maxZoom: 1 }); } - + // Cleanup and refresh diagramEngine.getModel().removeLayer( diagramEngine.getModel().getLayers().find(layer => layer instanceof OverlayLayerModel) @@ -133,7 +133,7 @@ export function TypeDiagram(props: TypeDiagramProps) { {diagramEngine?.getModel() && diagramModel ?
- + Date: Wed, 9 Jul 2025 14:01:03 +0530 Subject: [PATCH 038/148] Add end-to-end tests for Automation, AI Chat Agent, GraphQL Service, HTTP Service, and TCP Service --- .../api-services/ai-chat-service.spec.ts | 72 ++++++++++++ .../api-services/graphql-service.spec.ts | 100 +++++++++++++++++ .../api-services/http-service.spec.ts | 85 +++++++++++++++ .../api-services/tcp-service.spec.ts | 103 ++++++++++++++++++ .../automation/automation.spec.ts | 6 + 5 files changed, 366 insertions(+) create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/ai-chat-service.spec.ts create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/graphql-service.spec.ts create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/http-service.spec.ts create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/tcp-service.spec.ts diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/ai-chat-service.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/ai-chat-service.spec.ts new file mode 100644 index 00000000000..4cb7e41adbc --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/ai-chat-service.spec.ts @@ -0,0 +1,72 @@ + +/** + * 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'; +import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; +import { ProjectExplorer } from '../ProjectExplorer'; + +export default function createTests() { + test.describe('AI Chat Agent Tests', { + tag: '@group1', + }, async () => { + initTest(); + test('Create AI Chat Agent', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Creating a new AI Chat Agent in test attempt: ', testAttempt); + // Creating a AI Chat Agent + await addArtifact('AI Chat Agent', 'ai-agent-card'); + const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!artifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + const sampleName = `sample${testAttempt}`; + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + await form.fill({ + values: { + 'NameName of the agent': { + type: 'input', + value: sampleName, + } + } + }); + await form.submit('Create'); + + // Check if the diagram canvas is visible + const diagramCanvas = artifactWebView.locator('#bi-diagram-canvas'); + await diagramCanvas.waitFor({ state: 'visible', timeout: 30000 }); + + const diagramTitle = artifactWebView.locator('h2', { hasText: 'AI Chat Agent' }); + await diagramTitle.waitFor(); + + // Check if the agent call node is visible + const agentCallNode = artifactWebView.locator('[data-testid="agent-call-node"]'); + await agentCallNode.waitFor(); + + // Check if the AI Chat Agent is created in the project explorer + const projectExplorer = new ProjectExplorer(page.page); + await projectExplorer.findItem(['sample', `AI Agent Services - /${sampleName}`], true); + + const updateArtifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!updateArtifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + }); + }); +} diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/graphql-service.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/graphql-service.spec.ts new file mode 100644 index 00000000000..fbed7ef9c24 --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/graphql-service.spec.ts @@ -0,0 +1,100 @@ +/** + * 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'; +import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; +import { ProjectExplorer } from '../ProjectExplorer'; + +export default function createTests() { + test.describe('GraphQL Service Tests', { + tag: '@group1', + }, async () => { + initTest(); + test('Create GraphQL Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Creating a new service in test attempt: ', testAttempt); + // Creating a HTTP Service + await addArtifact('GraphQL Service', 'graphql-service-card'); + const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!artifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + const sampleName = `/sample${testAttempt}`; + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + await form.fill({ + values: { + 'Service Base Path*': { + type: 'input', + value: sampleName, + } + } + }); + await form.submit('Create'); + + // Check if the type diagram canvas is visible + const typeDiagram = artifactWebView.locator('[data-testid="type-diagram"]'); + await typeDiagram.waitFor({ state: 'visible', timeout: 30000 }); + + // Check if the service name is visible + const context = artifactWebView.locator(`text=${sampleName}`).first(); + await context.waitFor(); + + // Check if the AI Chat Agent is created in the project explorer + const projectExplorer = new ProjectExplorer(page.page); + await projectExplorer.findItem(['sample', `GraphQL Service`], true); + + const updateArtifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!updateArtifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + }); + + test('Editing GraphQL Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Editing a service 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('[data-testid="edit-service-btn"]'); + await editBtn.waitFor(); + await editBtn.click({ force: true }); + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + const sampleName = `/editedSample${testAttempt}`; + await form.fill({ + values: { + 'Service Base Path*': { + type: 'input', + value: sampleName, + } + } + }); + await form.submit('Save'); + + // Check if the type diagram canvas is visible + const typeDiagram = artifactWebView.locator('[data-testid="type-diagram"]'); + await typeDiagram.waitFor({ state: 'visible', timeout: 30000 }); + + // Check if the service name is visible + const context = artifactWebView.locator(`text=${sampleName}`).first(); + await context.waitFor(); + }); + }); +} diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/http-service.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/http-service.spec.ts new file mode 100644 index 00000000000..b9f1825f770 --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/http-service.spec.ts @@ -0,0 +1,85 @@ +/** + * 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'; +import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; +import { ProjectExplorer } from '../ProjectExplorer'; + +export default function createTests() { + test.describe('HTTP Service Tests', { + tag: '@group1', + }, async () => { + initTest(); + test('Create HTTP Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Creating a new service in test attempt: ', testAttempt); + // Creating a HTTP Service + await addArtifact('HTTP Service', 'http-service-card'); + const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!artifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + const sampleName = `/sample${testAttempt}`; + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + await form.fill({ + values: { + 'Service Base Path*': { + type: 'input', + value: sampleName, + } + } + }); + await form.submit('Create'); + const context = artifactWebView.locator(`text=${sampleName}`); + await context.waitFor(); + const projectExplorer = new ProjectExplorer(page.page); + await projectExplorer.findItem(['sample', `HTTP Service - ${sampleName}`], true); + const updateArtifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!updateArtifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + }); + + test('Editing HTTP Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Editing a service 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('vscode-button[title="Edit Service"]'); + await editBtn.waitFor(); + await editBtn.click({ force: true }); + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + const sampleName = `/editedSample${testAttempt}`; + await form.fill({ + values: { + 'Service Base Path*': { + type: 'input', + value: sampleName, + } + } + }); + await form.submit('Save'); + const context = artifactWebView.locator(`text=${sampleName}`); + await context.waitFor(); + }); + }); +} diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/tcp-service.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/tcp-service.spec.ts new file mode 100644 index 00000000000..81ce1722079 --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/api-services/tcp-service.spec.ts @@ -0,0 +1,103 @@ +/** + * 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'; +import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; +import { ProjectExplorer } from '../ProjectExplorer'; + +export default function createTests() { + test.describe('TCP Service Tests', { + tag: '@group1', + }, async () => { + let listenerName: string; + initTest(); + test('Create TCP Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Creating a new service in test attempt: ', testAttempt); + // Creating a HTTP Service + await addArtifact('TCP Service', 'tcp-service-card'); + const artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!artifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + + // Create a new listener + listenerName = `listenerTcp${testAttempt}`; + const listenerPort = `6060`; + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + await form.fill({ + values: { + 'Name*The name of the listener': { + type: 'input', + value: listenerName, + }, + 'localPort': { + type: 'textarea', + value: listenerPort, + } + } + }); + await form.submit('Next'); + + const selectedListener = artifactWebView.locator(`[current-value="${listenerName}"]`); + await selectedListener.waitFor(); + + // Create a new TCP Service + const configTitle = artifactWebView.locator('h3', { hasText: 'TCP Service Configuration' }); + await configTitle.waitFor(); + + await form.submit('Create'); + + const context = artifactWebView.locator(`text="onConnect"`); + await context.waitFor(); + + const projectExplorer = new ProjectExplorer(page.page); + await projectExplorer.findItem(['sample', `TCP Service`], true); + + const updateArtifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); + if (!updateArtifactWebView) { + throw new Error('WSO2 Integrator: BI webview not found'); + } + }); + + test('Editing TCP Service', async ({ }, testInfo) => { + const testAttempt = testInfo.retry + 1; + console.log('Editing a service 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('vscode-button[title="Edit Service"]'); + await editBtn.waitFor(); + await editBtn.click({ force: true }); + + const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await form.switchToFormView(false, artifactWebView); + + const selectedListener = artifactWebView.locator(`[current-value="${listenerName}"]`); + await selectedListener.waitFor(); + + await form.submit('Save'); + + const context = artifactWebView.locator(`text="onConnect"`); + await context.waitFor(); + }); + }); +} diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/automation/automation.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/automation/automation.spec.ts index 4bd717d9b05..7975bb9f2f0 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/automation/automation.spec.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/automation/automation.spec.ts @@ -32,6 +32,12 @@ export default function createTests() { throw new Error('WSO2 Integrator: BI webview not found'); } await artifactWebView.getByRole('button', { name: 'Create' }).click(); + + const diagramCanvas = artifactWebView.locator('#bi-diagram-canvas'); + await diagramCanvas.waitFor({ state: 'visible', timeout: 30000 }); + + const diagramTitle = artifactWebView.locator('h2', { hasText: 'Automation' }); + await diagramTitle.waitFor(); }); }); } From 06912d34ad6c9da02f1155609fca610fb0699928 Mon Sep 17 00:00:00 2001 From: madushajg Date: Wed, 9 Jul 2025 14:15:31 +0530 Subject: [PATCH 039/148] Update labels and error messages --- .../components/Diagram/Label/ArrayMappingOptionsWidget.tsx | 4 ++-- .../src/components/Diagram/visitors/NodeInitVisitor.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Label/ArrayMappingOptionsWidget.tsx b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Label/ArrayMappingOptionsWidget.tsx index f5b4e7d79b0..f7304d96e3a 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/Label/ArrayMappingOptionsWidget.tsx +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/Label/ArrayMappingOptionsWidget.tsx @@ -145,7 +145,7 @@ export function ArrayMappingOptionsWidget(props: ArrayMappingOptionsWidgetProps) }, { id: "a2a-inner", - label: getItemElement("a2a-inner", "Map Array Elements Individually"), + label: getItemElement("a2a-inner", "Map Using Query Expression"), onClick: onClickMapIndividualElements } ]; @@ -158,7 +158,7 @@ export function ArrayMappingOptionsWidget(props: ArrayMappingOptionsWidgetProps) }, { id: "a2s-inner", - label: getItemElement("a2s-inner", "Map Array Elements Individually and access element"), + label: getItemElement("a2s-inner", "Map via Query Expression and access element"), onClick: () => onClickMapIndividualElements(ClauseType.Select, true) } ]; diff --git a/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts b/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts index 738387586d5..35d32f839c1 100644 --- a/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts +++ b/workspaces/ballerina/data-mapper-view/src/components/Diagram/visitors/NodeInitVisitor.ts @@ -81,8 +81,8 @@ import { QueryParentFindingVisitor } from "./QueryParentFindingVisitor" import { QueryExprFindingVisitorByPosition } from "./QueryExprFindingVisitorByPosition"; import { DefaultPortModel } from "@projectstorm/react-diagrams"; -const INNER_FROM_CLAUSE_ERROR_MESSAGE = `Your mapping contains a query expression with one or more inner 'from' clauses, which are currently not supported. -To proceed, please refactor your mapping to remove any nested 'from' clauses and try again.`; +const INNER_FROM_CLAUSE_ERROR_MESSAGE = `This mapping contains a query expression with inner 'from' clauses, which are not currently supported in the visual data mapper. +Please switch to the code editor to continue working with this mapping.`; export class NodeInitVisitor implements Visitor { From 2402b646d21b21d299a6de1c3d8f1e02f03c838a Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Wed, 9 Jul 2025 14:29:55 +0530 Subject: [PATCH 040/148] Add data test ids --- .../EntityNode/EntityHead/EntityHead.tsx | 2 +- .../src/utils/model-mapper/entityModelMapper.ts | 4 ++-- .../ballerina/type-editor/src/TypeEditor/EnumEditor.tsx | 3 ++- .../type-editor/src/TypeEditor/IdentifierField.tsx | 1 + .../ballerina/type-editor/src/TypeEditor/RecordEditor.tsx | 2 +- .../type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx | 3 +++ .../ballerina/type-editor/src/TypeEditor/TypeEditor.tsx | 8 ++++---- .../ballerina/type-editor/src/TypeEditor/TypeField.tsx | 1 + .../ballerina/type-editor/src/TypeEditor/UnionEditor.tsx | 3 ++- 9 files changed, 17 insertions(+), 10 deletions(-) diff --git a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx index 41c4a7b438b..cc425d87db3 100644 --- a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx +++ b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx @@ -143,7 +143,7 @@ export function EntityHeadWidget(props: ServiceHeadProps) { ): EntityLinkMod let sourcePort = sourceNode.getPort(`right-${sourceNode.getID()}/${member.name}`); let targetPort = targetNode.getPort(`left-${ref}`); - const linkId = `entity-link-${sourceNode.getID()}-${ref}`; - let link = new EntityLinkModel(undefined, linkId); // REMOVE cardinalities + const linkTestID = `node-link-${sourceNode.getID()}/${member.name}-${ref}`; + let link = new EntityLinkModel(undefined, linkTestID); // REMOVE cardinalities entityLinks.push(createLinks(sourcePort, targetPort, link)); } }); diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/EnumEditor.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/EnumEditor.tsx index a60fa821b58..739ed4ad7be 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/EnumEditor.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/EnumEditor.tsx @@ -214,7 +214,7 @@ export function EnumEditor({ type, onChange, onValidationError }: EnumEditorProp Members -
+
@@ -239,6 +239,7 @@ export function EnumEditor({ type, onChange, onValidationError }: EnumEditorProp
diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx index 47dcfd736a3..1de72ef4fed 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/Tabs/TypeCreatorTab.tsx @@ -463,6 +463,7 @@ export function TypeCreatorTab(props: TypeCreatorTabProps) { {isNewType && ( ({ @@ -477,6 +478,7 @@ export function TypeCreatorTab(props: TypeCreatorTabProps) { @@ -231,6 +231,7 @@ export function UnionEditor({ type, onChange, rpcClient, onValidationError }: Un /> + + + {/* Side arrow of the helper pane */} {helperPaneArrowPosition && ( diff --git a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/types/token.ts b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/types/token.ts index d2f7ff4169b..ca1198e6e1b 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/types/token.ts +++ b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/types/token.ts @@ -32,12 +32,13 @@ type TokenEditorBaseProps = { onFocus?: () => void; onBlur?: () => void; getExpressionEditorIcon?: () => ReactNode; + height?: number; editorSx?: CSSProperties; }; type HelperPaneConditionalProps = | { - getHelperPane: (onChange: (value: string) => void, addFunction: (signature: string) => void) => JSX.Element; + getHelperPane: (onChange: (value: string) => void, addFunction: (signature: string) => void, height?: number) => JSX.Element; helperPaneOrigin?: HelperPaneOrigin; changeHelperPaneState: (state: boolean) => void; isHelperPaneOpen: boolean; diff --git a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/form.tsx b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/form.tsx index fb5937d1d2e..9ce9ea323ae 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/form.tsx +++ b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/form.tsx @@ -98,6 +98,9 @@ export const getHelperPanePosition = ( if (window.innerHeight - rect.top < HELPER_PANE_HEIGHT / 2) { position.top = window.innerHeight - HELPER_PANE_HEIGHT; } + if (window.innerHeight < HELPER_PANE_HEIGHT) { + position.top = 0; + } return position; }; diff --git a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts index 58d5bae222e..9967b6df0ec 100644 --- a/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts +++ b/workspaces/common-libs/ui-toolkit/src/components/ExpressionEditor/utils/token.ts @@ -138,6 +138,9 @@ export const getHelperPaneWithEditorPosition = ( if (window.innerHeight - rect.top < HELPER_PANE_WITH_EDITOR_HEIGHT / 2) { position.top = window.innerHeight - HELPER_PANE_WITH_EDITOR_HEIGHT; } + if (window.innerHeight < HELPER_PANE_WITH_EDITOR_HEIGHT) { + position.top = 0; + } return position; }; diff --git a/workspaces/mi/mi-diagram/src/components/Form/FormExpressionField/index.tsx b/workspaces/mi/mi-diagram/src/components/Form/FormExpressionField/index.tsx index 8ed516bcd22..79463ad595e 100644 --- a/workspaces/mi/mi-diagram/src/components/Form/FormExpressionField/index.tsx +++ b/workspaces/mi/mi-diagram/src/components/Form/FormExpressionField/index.tsx @@ -274,7 +274,11 @@ export const FormExpressionField = (params: FormExpressionFieldProps) => { position, 'default', () => handleChangeHelperPaneState(false), - handleHelperPaneChange + handleHelperPaneChange, + undefined, + undefined, + 300, + false ); }, [expressionRef.current, handleChangeHelperPaneState, nodeRange, getHelperPane]); diff --git a/workspaces/mi/mi-diagram/src/components/Form/FormTokenEditor/index.tsx b/workspaces/mi/mi-diagram/src/components/Form/FormTokenEditor/index.tsx index 78b37da89f6..35d3f7ffbc6 100644 --- a/workspaces/mi/mi-diagram/src/components/Form/FormTokenEditor/index.tsx +++ b/workspaces/mi/mi-diagram/src/components/Form/FormTokenEditor/index.tsx @@ -108,7 +108,9 @@ export const FormTokenEditor = ({ () => handleChangeHelperPaneState(false), onChange, addFunction, - { width: 'auto', border: '1px solid var(--dropdown-border)' } + { width: 'auto', border: '1px solid var(--dropdown-border)' }, + 400, + true ); }, [nodeRange, handleChangeHelperPaneState, getHelperPane]); diff --git a/workspaces/mi/mi-diagram/src/components/Form/HelperPane/index.tsx b/workspaces/mi/mi-diagram/src/components/Form/HelperPane/index.tsx index 07918d08039..e370f38d77d 100644 --- a/workspaces/mi/mi-diagram/src/components/Form/HelperPane/index.tsx +++ b/workspaces/mi/mi-diagram/src/components/Form/HelperPane/index.tsx @@ -16,7 +16,7 @@ * under the License. */ -import React, { CSSProperties, useState } from 'react'; +import React, { CSSProperties, useEffect, useRef, useState } from 'react'; import { Position } from 'vscode-languageserver-types'; import { HelperPane, HelperPaneHeight } from '@wso2/ui-toolkit'; import { CategoryPage } from './CategoryPage'; @@ -29,6 +29,8 @@ import { ParamsPage } from './ParamsPage'; export type HelperPaneProps = { position: Position; helperPaneHeight: HelperPaneHeight; + contentHeight?: number; + isTokenEditor?: boolean; onClose: () => void; onChange: (value: string) => void; addFunction?: (value: string) => void; @@ -46,56 +48,108 @@ export const PAGE = { export type Page = (typeof PAGE)[keyof typeof PAGE]; -const HelperPaneEl = ({ position, helperPaneHeight, sx, onClose, onChange, addFunction }: HelperPaneProps) => { +const HelperPaneEl = ({ position, helperPaneHeight, contentHeight, isTokenEditor, sx, onClose, onChange, addFunction }: HelperPaneProps) => { const [currentPage, setCurrentPage] = useState(PAGE.CATEGORY); + const panelRef = useRef(null); + const [height, setHeight] = useState(400); + const [isComponentOverflowing, setIsComponentOverflowing] = useState(false); + const componentDefaultHeight = isTokenEditor ? 380 : 400; + useEffect(() => { + const checkOverflow = () => { + if (panelRef.current) { + const element = panelRef.current; + const rect = element.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + + // Get children height + const clientHeight = isTokenEditor ? (element.clientHeight + 180) : element.clientHeight; + + const heightDiff = clientHeight - viewportHeight; // Adjust for token editor if needed + let overflowHeight = 0; + let bottomOverflow = 0; + if (heightDiff < 0) { + bottomOverflow = rect.bottom - viewportHeight; + if (bottomOverflow < 0) { + overflowHeight = 0; // No overflow + } + overflowHeight = bottomOverflow + (isTokenEditor ? 40 : 0); + } else { + overflowHeight = heightDiff; + console.log('Overflow Height:', overflowHeight); + } + const heightWithComponents = clientHeight - overflowHeight - (isTokenEditor ? 180 : 0); // Adjust for token editor if needed + const newHeight = heightWithComponents > componentDefaultHeight ? componentDefaultHeight : heightWithComponents; + setIsComponentOverflowing(heightWithComponents > componentDefaultHeight); + console.log('New Height:', newHeight, 'Is Overflowing:', isComponentOverflowing); + setHeight(newHeight); + } + }; + + // Check immediately and on window resize + // checkOverflow(); + window.addEventListener('resize', checkOverflow); + window.addEventListener('scroll', checkOverflow); // Also check on scroll + + // Use setTimeout to check after render is complete + setTimeout(checkOverflow, 10); + + return () => { + window.removeEventListener('resize', checkOverflow); + window.removeEventListener('scroll', checkOverflow); + }; + }, []); + + console.log('Current Page:', height); return ( - - {currentPage === PAGE.CATEGORY && ( - - )} - {currentPage === PAGE.PAYLOAD && ( - - )} - {currentPage === PAGE.VARIABLES && ( - - )} - {currentPage === PAGE.HEADERS && ( - - )} - {currentPage === PAGE.PARAMS && ( - - )} - {currentPage === PAGE.PROPERTIES && ( - - )} - +
+ + {currentPage === PAGE.CATEGORY && ( + + )} + {currentPage === PAGE.PAYLOAD && ( + + )} + {currentPage === PAGE.VARIABLES && ( + + )} + {currentPage === PAGE.HEADERS && ( + + )} + {currentPage === PAGE.PARAMS && ( + + )} + {currentPage === PAGE.PROPERTIES && ( + + )} + +
); }; @@ -105,7 +159,9 @@ export const getHelperPane = ( onClose: () => void, onChange: (value: string) => void, addFunction?: (value: string) => void, - sx?: CSSProperties + sx?: CSSProperties, + contentHeight?: number, + isTokenEditor?: boolean ) => { return ( ); }; From bc49273b8839bbd5575e16b9e42b31ca6863e625 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 14:06:57 +0530 Subject: [PATCH 064/148] Add data test ids --- .../entity-relationship/EntityNode/EntityHead/EntityHead.tsx | 2 +- workspaces/ballerina/type-editor/src/TypeEditor/ClassEditor.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx index cc425d87db3..8678eb24aee 100644 --- a/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx +++ b/workspaces/ballerina/type-diagram/src/components/entity-relationship/EntityNode/EntityHead/EntityHead.tsx @@ -184,7 +184,7 @@ export function EntityHeadWidget(props: ServiceHeadProps) { )} */} - +
diff --git a/workspaces/ballerina/type-editor/src/TypeEditor/ClassEditor.tsx b/workspaces/ballerina/type-editor/src/TypeEditor/ClassEditor.tsx index 0dce42f3f2a..4451e82e7c5 100644 --- a/workspaces/ballerina/type-editor/src/TypeEditor/ClassEditor.tsx +++ b/workspaces/ballerina/type-editor/src/TypeEditor/ClassEditor.tsx @@ -351,7 +351,7 @@ export function ClassEditor({ type, onChange, isGraphql, onValidationError }: Cl {isGraphql ? 'Object Fields' : 'Resource Methods'} -
+
From cf76afd95383455605485fd1a4de7df482873d5b Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 14:07:49 +0530 Subject: [PATCH 065/148] Add service class test --- .../e2e-playwright-tests/type/type.spec.ts | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts index 96c345e1ea3..dea7f87c62c 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts @@ -254,8 +254,112 @@ export default function createTests() { const enumLinkElement = artifactWebView.locator(`[data-testid="${enumLinkTestID}"]`); await enumLinkElement.waitFor({ state: 'visible', timeout: 60000 }); + + + // click the menu button of the Employee type + const employeeMenuButton = artifactWebView.locator(`[data-testid="type-node-${recordName}-menu"]`); + await employeeMenuButton.waitFor({ state: 'visible', timeout: 60000 }); + await employeeMenuButton.click(); + + const menuItem = artifactWebView.getByText('Edit', { exact: true }); + await menuItem.waitFor({ state: 'visible', timeout: 60000 }); + await menuItem.click(); + + // Wait for type editor content to be visible + const typeEditorContent2 = artifactWebView.locator('[data-testid="type-editor-container"]'); + await typeEditorContent2.waitFor({ state: 'visible', timeout: 60000 }); + + // Add new field to the record + const addFieldButton2 = artifactWebView.locator('[data-testid="add-field-button"]'); + await addFieldButton2.waitFor({ state: 'visible', timeout: 60000 }); + await addFieldButton2.click(); + + //make the field type enumName + const newFieldNameInput = artifactWebView.locator('[data-testid="identifier-field"]').last(); + await newFieldNameInput.waitFor({ state: 'visible', timeout: 60000 }); + await newFieldNameInput.dblclick(); + await newFieldNameInput.type('role'); + + const newFieldTypeInput = artifactWebView.locator('[data-testid="type-field"]').last(); + await newFieldTypeInput.waitFor({ state: 'visible', timeout: 60000 }); + await newFieldTypeInput.dblclick(); + await newFieldTypeInput.type(enumName); // Use the enum type created earlier + + + // Save the updated record type + await form.submit('Save'); + + + // Wait for the save operation to complete + await page.page.waitForTimeout(2000); + await page.page.waitForLoadState('domcontentloaded'); + + // Verify the link between the record and enum in the type diagram + const updatedEnumLinkTestID = `node-link-${recordName}/role-${enumName}`; + // Verify the link between the record and enum + const updatedEnumLinkElement = artifactWebView.locator(`[data-testid="${updatedEnumLinkTestID}"]`); + await updatedEnumLinkElement.waitFor({ state: 'visible', timeout: 60000 }); + + + // Create new type of Service Class + await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); + await addTypeButton.click(); + + const serviceClassName = `Project${testAttempt}`; + await form.fill({ + values: { + 'Name': { + type: 'input', + value: serviceClassName, + }, + 'Kind': { + type: 'dropdown', + value: 'Service Class', + } + } + }); + + // click the add function button + const addFunctionButton = artifactWebView.locator('[data-testid="function-add-button"]'); + await addFunctionButton.waitFor({ state: 'visible', timeout: 60000 }); + await addFunctionButton.click(); + + // Fill in the function details + const functionNameInput = artifactWebView.locator('[data-testid="identifier-field"]').first(); + await functionNameInput.waitFor({ state: 'visible', timeout: 60000 }); + // For VS Code text fields, we need to double-click to select all, then type + await functionNameInput.dblclick(); + await functionNameInput.type('employeeDetails'); + + const functionTypeInput = artifactWebView.locator('[data-testid="type-field"]').first(); + await functionTypeInput.waitFor({ state: 'visible', timeout: 60000 }); + await functionTypeInput.dblclick(); + await functionTypeInput.type(recordName); + + // Save the function + await form.submit('Save'); + + + // Wait for the save operation to complete + await page.page.waitForTimeout(2000); + await page.page.waitForLoadState('domcontentloaded'); + + // Verify the service class was created in the type diagram by checking for EntityHeads data-testid + const serviceClassElement = artifactWebView.locator(`[data-testid="type-node-${serviceClassName}"]`); + await serviceClassElement.waitFor({ state: 'visible', timeout: 60000 }); + + // Verify the link between the service class and record in the type diagram + const serviceClassLinkTestID = `node-link-${serviceClassName}/employeeDetails-${recordName}`; + const serviceClassLinkElement = artifactWebView.locator(`[data-testid="${serviceClassLinkTestID}"]`); + await serviceClassLinkElement.waitFor({ state: 'visible', timeout: 60000 }); + await page.page.pause(); + + + + + }); }); } \ No newline at end of file From 58859b833c2a33dc1e4e4136bcb0511fb5f48a34 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 15:47:55 +0530 Subject: [PATCH 066/148] Add TypeEditor utils --- .../type/TypeEditorUtils.ts | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts new file mode 100644 index 00000000000..612089c22d1 --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts @@ -0,0 +1,260 @@ +/** + * 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 { Frame, Locator, Page } from '@playwright/test'; +import { Form } from '@wso2/playwright-vscode-tester'; + +/** + * Utility class for type editor test operations + */ +export class TypeEditorUtils { + constructor(private page: Page, private webView: Frame) {} + + /** + * Wait for element to be visible and interactable + */ + async waitForElement(locator: Locator, timeout: number = 60000): Promise { + await locator.waitFor({ state: 'visible', timeout }); + } + + /** + * Fill an identifier field (double-click and type) + */ + async fillIdentifierField(index: number = 0, value: string): Promise { + const field = this.webView.locator('[data-testid="identifier-field"]').nth(index); + await this.waitForElement(field); + await field.dblclick(); + await field.type(value); + } + + /** + * Fill a type field (double-click and type) + */ + async fillTypeField(index: number = 0, value: string): Promise { + const field = this.webView.locator('[data-testid="type-field"]').nth(index); + await this.waitForElement(field); + await field.dblclick(); + await field.type(value); + } + + /** + * Add a new enum member with the given name + */ + async addEnumMember(memberName: string): Promise { + const addButton = this.webView.locator('[data-testid="add-member-button"]'); + await addButton.click(); + + // Get the last identifier field (newly added) + const memberFields = this.webView.locator('[data-testid="identifier-field"]'); + const count = await memberFields.count(); + await this.fillIdentifierField(count - 1, memberName); + } + + /** + * Delete an enum member by index + */ + async deleteEnumMember(index: number): Promise { + const deleteButton = this.webView.locator(`[data-testid="delete-member-${index}"]`); + await this.waitForElement(deleteButton); + await deleteButton.click(); + } + + /** + * Add a new record field with name and type + */ + async addRecordField(fieldName: string, fieldType: string): Promise { + const addButton = this.webView.locator('[data-testid="add-field-button"]'); + await this.waitForElement(addButton); + await addButton.click(); + + // Fill the newly added field (last in the form) + const identifierFields = this.webView.locator('[data-testid="identifier-field"]'); + + const fieldCount = await identifierFields.count(); + const lastIndex = fieldCount - 1; + + await this.fillIdentifierField(lastIndex, fieldName); + await this.fillTypeField(lastIndex, fieldType); + } + + /** + * Add a function to service class + */ + async addFunction(functionName: string, returnType: string): Promise { + const addButton = this.webView.locator('[data-testid="function-add-button"]'); + await this.waitForElement(addButton); + await addButton.click(); + + // Fill the newly added function (last in the form) + const identifierFields = this.webView.locator('[data-testid="identifier-field"]'); + const typeFields = this.webView.locator('[data-testid="type-field"]'); + + const fieldCount = await identifierFields.count(); + const lastIndex = fieldCount - 1; + + await this.fillIdentifierField(lastIndex, functionName); + await this.fillTypeField(lastIndex, returnType); + } + + /** + * Create a type using the form with name and kind + */ + async createType(name: string, kind: 'Enum' | 'Union' | 'Record' | 'Service Class'): Promise { + const form = new Form(this.page, 'WSO2 Integrator: BI', this.webView); + await form.switchToFormView(false, this.webView); + + await form.fill({ + values: { + 'Name': { + type: 'input', + value: name, + }, + 'Kind': { + type: 'dropdown', + value: kind, + } + } + }); + + return form; + } + + /** + * Save form and wait for completion + */ + async saveAndWait(form: Form): Promise { + await form.submit('Save'); + await this.page.waitForTimeout(2000); + await this.page.waitForLoadState('domcontentloaded'); + } + + /** + * Click Add Type button + */ + async clickAddType(): Promise { + const addTypeButton = this.webView.getByRole('button', { name: 'Add Type' }); + await this.waitForElement(addTypeButton); + await addTypeButton.click(); + } + + /** + * Verify that a type node exists in the diagram + */ + async verifyTypeNodeExists(typeName: string): Promise { + const typeElement = this.webView.locator(`[data-testid="type-node-${typeName}"]`); + await this.waitForElement(typeElement); + } + + /** + * Verify that a link exists between two types + */ + async verifyTypeLink(fromType: string, field: string, toType: string): Promise { + const linkTestId = `node-link-${fromType}/${field}-${toType}`; + const linkElement = this.webView.locator(`[data-testid="${linkTestId}"]`); + await this.waitForElement(linkElement); + } + + /** + * Edit an existing type by clicking its menu + */ + async editType(typeName: string): Promise { + const menuButton = this.webView.locator(`[data-testid="type-node-${typeName}-menu"]`); + await this.waitForElement(menuButton); + await menuButton.click(); + + const editMenuItem = this.webView.getByText('Edit', { exact: true }); + await this.waitForElement(editMenuItem); + await editMenuItem.click(); + + // Wait for type editor to load + const typeEditorContent = this.webView.locator('[data-testid="type-editor-container"]'); + await this.waitForElement(typeEditorContent); + } + + /** + * Wait for type editor to be ready + */ + async waitForTypeEditor(): Promise { + await this.page.waitForTimeout(2000); + await this.page.waitForLoadState('domcontentloaded'); + + const typeEditorContent = this.webView.locator('[data-testid="type-editor-container"]'); + await this.waitForElement(typeEditorContent); + } + + /** + * Create an enum type with multiple members + */ + async createEnumType(enumName: string, members: string[]): Promise { + const form = await this.createType(enumName, 'Enum'); + + // Fill the first member (already exists) + if (members.length > 0) { + await this.fillIdentifierField(0, members[0]); + } + + // Add additional members + for (let i = 1; i < members.length; i++) { + await this.addEnumMember(members[i]); + } + + return form; + } + + /** + * Create a union type with specified types + */ + async createUnionType(unionName: string, types: string[]): Promise { + const form = await this.createType(unionName, 'Union'); + + // Fill union types + for (let i = 0; i < types.length; i++) { + await this.fillTypeField(i, types[i]); + } + + return form; + } + + /** + * Create a record type with specified fields + */ + async createRecordType(recordName: string, fields: Array<{name: string, type: string}>): Promise { + const form = await this.createType(recordName, 'Record'); + + // Add fields + for (const field of fields) { + await this.addRecordField(field.name, field.type); + } + + return form; + } + + /** + * Create a service class with functions + */ + async createServiceClass(className: string, functions: Array<{name: string, returnType: string}>): Promise { + const form = await this.createType(className, 'Service Class'); + + // Add functions + for (const func of functions) { + await this.addFunction(func.name, func.returnType); + } + + return form; + } +} \ No newline at end of file From 208ade7b9910c6b276dd8515f750303a78f5423e Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 15:48:31 +0530 Subject: [PATCH 067/148] Move logic to utils --- .../e2e-playwright-tests/type/type.spec.ts | 368 +++--------------- 1 file changed, 51 insertions(+), 317 deletions(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts index dea7f87c62c..0f1f9e32d37 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts @@ -16,10 +16,11 @@ * under the License. */ -import { expect, test } from '@playwright/test'; -import { addArtifact, initTest, page } from '../utils'; -import { Form, switchToIFrame } from '@wso2/playwright-vscode-tester'; -import { ProjectExplorer } from '../ProjectExplorer'; +import { test } from '@playwright/test'; +import { addArtifact, initTest, page, getWebview, verifyGeneratedSource } from '../utils'; +import { Form } from '@wso2/playwright-vscode-tester'; +import { TypeEditorUtils } from './TypeEditorUtils'; +import path from 'path'; export default function createTests() { test.describe('Type Editor Tests', { @@ -29,7 +30,7 @@ export default function createTests() { test('Create Types from Scratch', async ({ }, testInfo) => { const testAttempt = testInfo.retry + 1; - console.log('Creating a record type from scratch in test attempt: ', testAttempt); + console.log('Creating types from scratch in test attempt: ', testAttempt); // Navigate to type editor await addArtifact('Type', 'type'); @@ -37,328 +38,61 @@ export default function createTests() { // Wait for page to be stable before accessing iframe await page.page.waitForLoadState('networkidle'); - // Retry logic for iframe access - let artifactWebView; - let retryCount = 0; - const maxRetries = 3; + // Get webview directly from utils + const artifactWebView = await getWebview('WSO2 Integrator: BI', page); + const typeUtils = new TypeEditorUtils(page.page, artifactWebView); - while (retryCount < maxRetries) { - try { - artifactWebView = await switchToIFrame('WSO2 Integrator: BI', page.page); - if (artifactWebView) { - break; - } - } catch (error) { - console.log(`Attempt ${retryCount + 1} failed to access iframe:`, error instanceof Error ? error.message : String(error)); - retryCount++; - if (retryCount < maxRetries) { - // Wait a bit before retrying - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - } - } - } - - if (!artifactWebView) { - throw new Error('WSO2 Integrator: BI webview not found after multiple attempts'); - } - - // Click Add Type button - // const addTypeButton = artifactWebView.getByRole('button', { name: 'Add Type' }); - // await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); - // await addTypeButton.click(); - - // Wait for the type editor to load - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - - // Wait for type editor content to be visible - const typeEditorContent = artifactWebView.locator('[data-testid="type-editor-container"]'); - await typeEditorContent.waitFor({ state: 'visible', timeout: 60000 }); - - // create an enum type to verify the type diagram updates - // await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); - // await addTypeButton.click(); + // Wait for type editor to be ready + await typeUtils.waitForTypeEditor(); // ENUM: Role - const enumName = `Role${testAttempt}`; - - const form = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); - await form.switchToFormView(false, artifactWebView); - - await form.fill({ - values: { - 'Name': { - type: 'input', - value: enumName, - }, - 'Kind': { - type: 'dropdown', - value: 'Enum', - } - } - }); - - // already a field is added in enum, so we just need to fill it - const enumFieldNameInput = artifactWebView.locator('[data-testid="identifier-field"]').first(); - await enumFieldNameInput.waitFor({ state: 'visible', timeout: 60000 }); - await enumFieldNameInput.dblclick(); - await enumFieldNameInput.type('Admin'); - - // add new enum field - const addMemberButton = artifactWebView.locator('[data-testid="add-member-button"]'); - await addMemberButton.click(); - - - const enumFieldNameInput2 = artifactWebView.locator('[data-testid="identifier-field"]').nth(1); - await enumFieldNameInput2.waitFor({ state: 'visible', timeout: 60000 }); - await enumFieldNameInput2.dblclick(); - await enumFieldNameInput2.type('Engineer'); - - // add sales enum field - await addMemberButton.click(); - const enumFieldNameInput3 = artifactWebView.locator('[data-testid="identifier-field"]').nth(2); - await enumFieldNameInput3.waitFor({ state: 'visible', timeout: 60000 }); - await enumFieldNameInput3.dblclick(); - await enumFieldNameInput3.type('Sales'); - - //add marketing enum field - await addMemberButton.click(); - const enumFieldNameInput4 = artifactWebView.locator('[data-testid="identifier-field"]').nth(3); - await enumFieldNameInput4.waitFor({ state: 'visible', timeout: 60000 }); - await enumFieldNameInput4.dblclick(); - await enumFieldNameInput4.type('Marketing'); - - // delete the second field - const deleteButton2 = artifactWebView.locator('[data-testid="delete-member-1"]'); - await deleteButton2.waitFor({ state: 'visible', timeout: 60000 }); - await deleteButton2.click(); - - // Save the enum type - await form.submit('Save'); - - // Wait for the save operation to complete - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - - // Verify the enum type was created in the type diagram by checking for EntityHeads data-testid - const enumElement = artifactWebView.locator(`[data-testid="type-node-${enumName}"]`); - await enumElement.waitFor({ state: 'visible', timeout: 60000 }); + + // Create enum with members, delete one, then save + const enumForm = await typeUtils.createEnumType(enumName, ['Admin', 'Engineer', 'Sales', 'Marketing']); + await typeUtils.deleteEnumMember(1); // Delete 'Engineer' + await typeUtils.saveAndWait(enumForm); + await typeUtils.verifyTypeNodeExists(enumName); // UNION: Id - const addTypeButton = artifactWebView.getByRole('button', { name: 'Add Type' }); - - // create union type to verify the type diagram updates union has two fields int and string - await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); - await addTypeButton.click(); - + await typeUtils.clickAddType(); const unionName = `Id${testAttempt}`; - await form.fill({ - values: { - 'Name': { - type: 'input', - value: unionName, - }, - 'Kind': { - type: 'dropdown', - value: 'Union', - } - } - }); - - // add first field to union already a field is added in union, so we just need to fill it - const unionFieldNameInput = artifactWebView.locator('[data-testid="type-field"]').first(); - await unionFieldNameInput.waitFor({ state: 'visible', timeout: 60000 }); - await unionFieldNameInput.dblclick(); - await unionFieldNameInput.type('int'); - // just need to fill the second field as well - const unionFieldNameInput2 = artifactWebView.locator('[data-testid="type-field"]').nth(1); - await unionFieldNameInput2.waitFor({ state: 'visible', timeout: 60000 }); - await unionFieldNameInput2.dblclick(); - await unionFieldNameInput2.type('string'); - - //save - await form.submit('Save'); - - - // RECORD: Employee - - // const addTypeButton = artifactWebView.getByRole('button', { name: 'Add Type' }); - await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); - await addTypeButton.click(); + const unionForm = await typeUtils.createUnionType(unionName, ['int', 'string']); + await typeUtils.saveAndWait(unionForm); + await typeUtils.verifyTypeNodeExists(unionName); - // Fill in the record type details using test IDs + // RECORD: Employee (initially with just id field) + await typeUtils.clickAddType(); const recordName = `Employee${testAttempt}`; - - await form.fill({ - values: { - 'Name': { - type: 'input', - value: recordName, - }, - 'Kind': { - type: 'dropdown', - value: 'Record', - } - } - }); - - // Add a field to the record - const addFieldButton = artifactWebView.locator('[data-testid="add-field-button"]'); - await addFieldButton.waitFor({ state: 'visible', timeout: 60000 }); - await addFieldButton.click(); - - // Fill in field details - use identifier-field for field name and type-field for field type - const fieldNameInput = artifactWebView.locator('[data-testid="identifier-field"]').first(); - await fieldNameInput.waitFor({ state: 'visible', timeout: 60000 }); - // For VS Code text fields, we need to double-click to select all, then type - await fieldNameInput.dblclick(); - await fieldNameInput.type('role'); - - const fieldTypeInput = artifactWebView.locator('[data-testid="type-field"]').first(); - await fieldTypeInput.waitFor({ state: 'visible', timeout: 60000 }); - // For VS Code text fields, we need to double-click to select all, then type - await fieldTypeInput.dblclick(); - await fieldTypeInput.type(enumName); // Use the enum type created earlier - - // Add a field to the record - await addFieldButton.click(); - - // Fill in field details - use identifier-field for field name and type-field for field type - const fieldNameInput2 = artifactWebView.locator('[data-testid="identifier-field"]').first(); - await fieldNameInput2.waitFor({ state: 'visible', timeout: 60000 }); - // For VS Code text fields, we need to double-click to select all, then type - await fieldNameInput2.dblclick(); - await fieldNameInput2.type('id'); - - const fieldTypeInput2 = artifactWebView.locator('[data-testid="type-field"]').first(); - await fieldTypeInput2.waitFor({ state: 'visible', timeout: 60000 }); - // For VS Code text fields, we need to double-click to select all, then type - await fieldTypeInput2.dblclick(); - await fieldTypeInput2.type(unionName); // Use the union type created earlier - - await form.submit('Save'); - - // Wait for the save operation to complete - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - - // Verify the record was created in the type diagram by checking for EntityHeads data-testid - const recordElement = artifactWebView.locator(`[data-testid="type-node-${recordName}"]`); - await recordElement.waitFor({ state: 'visible', timeout: 60000 }); - - // Verify the link between the record and enum in the type diagram - const enumLinkTestID = `node-link-${recordName}/id-${unionName}`; - // Verify the link between the record and enum - const enumLinkElement = artifactWebView.locator(`[data-testid="${enumLinkTestID}"]`); - await enumLinkElement.waitFor({ state: 'visible', timeout: 60000 }); - - - - // click the menu button of the Employee type - const employeeMenuButton = artifactWebView.locator(`[data-testid="type-node-${recordName}-menu"]`); - await employeeMenuButton.waitFor({ state: 'visible', timeout: 60000 }); - await employeeMenuButton.click(); - - const menuItem = artifactWebView.getByText('Edit', { exact: true }); - await menuItem.waitFor({ state: 'visible', timeout: 60000 }); - await menuItem.click(); - - // Wait for type editor content to be visible - const typeEditorContent2 = artifactWebView.locator('[data-testid="type-editor-container"]'); - await typeEditorContent2.waitFor({ state: 'visible', timeout: 60000 }); - - // Add new field to the record - const addFieldButton2 = artifactWebView.locator('[data-testid="add-field-button"]'); - await addFieldButton2.waitFor({ state: 'visible', timeout: 60000 }); - await addFieldButton2.click(); - - //make the field type enumName - const newFieldNameInput = artifactWebView.locator('[data-testid="identifier-field"]').last(); - await newFieldNameInput.waitFor({ state: 'visible', timeout: 60000 }); - await newFieldNameInput.dblclick(); - await newFieldNameInput.type('role'); - - const newFieldTypeInput = artifactWebView.locator('[data-testid="type-field"]').last(); - await newFieldTypeInput.waitFor({ state: 'visible', timeout: 60000 }); - await newFieldTypeInput.dblclick(); - await newFieldTypeInput.type(enumName); // Use the enum type created earlier - - - // Save the updated record type - await form.submit('Save'); - - - // Wait for the save operation to complete - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - - // Verify the link between the record and enum in the type diagram - const updatedEnumLinkTestID = `node-link-${recordName}/role-${enumName}`; - // Verify the link between the record and enum - const updatedEnumLinkElement = artifactWebView.locator(`[data-testid="${updatedEnumLinkTestID}"]`); - await updatedEnumLinkElement.waitFor({ state: 'visible', timeout: 60000 }); - - - // Create new type of Service Class - await addTypeButton.waitFor({ state: 'visible', timeout: 60000 }); - await addTypeButton.click(); - + const recordForm = await typeUtils.createRecordType(recordName, [ + { name: 'id', type: unionName } + ]); + await typeUtils.saveAndWait(recordForm); + await typeUtils.verifyTypeNodeExists(recordName); + + // Verify link + await typeUtils.verifyTypeLink(recordName, 'id', unionName); + + // Edit Employee type to add role field + await typeUtils.editType(recordName); + await typeUtils.addRecordField('role', enumName); + const editForm = new Form(page.page, 'WSO2 Integrator: BI', artifactWebView); + await typeUtils.saveAndWait(editForm); + await typeUtils.verifyTypeLink(recordName, 'role', enumName); + + // Create Service Class: Project + await typeUtils.clickAddType(); const serviceClassName = `Project${testAttempt}`; - await form.fill({ - values: { - 'Name': { - type: 'input', - value: serviceClassName, - }, - 'Kind': { - type: 'dropdown', - value: 'Service Class', - } - } - }); - - // click the add function button - const addFunctionButton = artifactWebView.locator('[data-testid="function-add-button"]'); - await addFunctionButton.waitFor({ state: 'visible', timeout: 60000 }); - await addFunctionButton.click(); - - // Fill in the function details - const functionNameInput = artifactWebView.locator('[data-testid="identifier-field"]').first(); - await functionNameInput.waitFor({ state: 'visible', timeout: 60000 }); - // For VS Code text fields, we need to double-click to select all, then type - await functionNameInput.dblclick(); - await functionNameInput.type('employeeDetails'); - - const functionTypeInput = artifactWebView.locator('[data-testid="type-field"]').first(); - await functionTypeInput.waitFor({ state: 'visible', timeout: 60000 }); - await functionTypeInput.dblclick(); - await functionTypeInput.type(recordName); - - // Save the function - await form.submit('Save'); - - - // Wait for the save operation to complete - await page.page.waitForTimeout(2000); - await page.page.waitForLoadState('domcontentloaded'); - - // Verify the service class was created in the type diagram by checking for EntityHeads data-testid - const serviceClassElement = artifactWebView.locator(`[data-testid="type-node-${serviceClassName}"]`); - await serviceClassElement.waitFor({ state: 'visible', timeout: 60000 }); - - // Verify the link between the service class and record in the type diagram - const serviceClassLinkTestID = `node-link-${serviceClassName}/employeeDetails-${recordName}`; - const serviceClassLinkElement = artifactWebView.locator(`[data-testid="${serviceClassLinkTestID}"]`); - await serviceClassLinkElement.waitFor({ state: 'visible', timeout: 60000 }); - - await page.page.pause(); - - - - - + const serviceForm = await typeUtils.createServiceClass(serviceClassName, [ + { name: 'employeeDetails', returnType: recordName } + ]); + await typeUtils.saveAndWait(serviceForm); + await typeUtils.verifyTypeNodeExists(serviceClassName); + await typeUtils.verifyTypeLink(serviceClassName, 'employeeDetails', recordName); + + // Verify the generated types.bal matches testOutput.bal + const expectedFilePath = path.join(__dirname, 'testOutput.bal'); + await verifyGeneratedSource('types.bal', expectedFilePath); }); }); From 6c809995c7d0f11052bd8d1505d694d345c3906b Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 15:49:00 +0530 Subject: [PATCH 068/148] Add util to verify the source --- .../src/test/e2e-playwright-tests/utils.ts | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts index 9bc6f0b2369..40593e4a954 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/utils.ts @@ -86,7 +86,7 @@ export async function setupBallerinaIntegrator() { } } -async function getWebview(viewName: string, page: ExtendedPage) { +export async function getWebview(viewName: string, page: ExtendedPage) { let webview; let retryCount = 0; const maxRetries = 3; @@ -213,3 +213,45 @@ export async function enableICP() { await icpToggle.click(); } } + +/** + * Normalize source code for comparison + */ +function normalizeSource(source: string): string { + return source + .replace(/\r\n/g, '\n') // Normalize line endings + .replace(/\t/g, ' ') // Convert tabs to spaces + .split('\n') + .map(line => line.trimEnd()) // Remove trailing whitespace + .filter(line => line.trim() !== '') // Remove empty lines + .join('\n') + .trim(); +} + +/** + * Compare a generated .bal file with an expected .bal file + * @param generatedFileName - Name of the generated file (e.g., 'types.bal') + * @param expectedFilePath - Path to the expected file (e.g., path to testOutput.bal) + */ +export async function verifyGeneratedSource(generatedFileName: string, expectedFilePath: string): Promise { + const { expect } = await import('@playwright/test'); + + // Generated file is in the project sample folder + const generatedFilePath = path.join(newProjectPath, 'sample', generatedFileName); + + if (!fs.existsSync(generatedFilePath)) { + throw new Error(`Generated file not found at: ${generatedFilePath}`); + } + + if (!fs.existsSync(expectedFilePath)) { + throw new Error(`Expected file not found at: ${expectedFilePath}`); + } + + const actualContent = fs.readFileSync(generatedFilePath, 'utf-8'); + const expectedContent = fs.readFileSync(expectedFilePath, 'utf-8'); + + const normalizedActual = normalizeSource(actualContent); + const normalizedExpected = normalizeSource(expectedContent); + + expect(normalizedActual).toBe(normalizedExpected); +} From c491a12208764eb78f77587c860f2f269e7d797f Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 15:50:31 +0530 Subject: [PATCH 069/148] Add generated test source --- .../e2e-playwright-tests/type/testOutput.bal | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/testOutput.bal diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/testOutput.bal b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/testOutput.bal new file mode 100644 index 00000000000..d31bc73a0a1 --- /dev/null +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/testOutput.bal @@ -0,0 +1,27 @@ + +enum Role1 { + Admin, + Sales, + Marketing +} + +type Id1 int|string; + +type Employee1 record {| + Id1 id; + Role1 role; +|}; + +service class Project1 { + function init() { + } + + resource function get employeeDetails() returns Employee1 { + do { + panic error("Unimplemented function"); + } on fail error err { + //handle error + panic error("Unhandled error"); + } + } +} From ca5799559e853b5d3d3faf89ec3611c962b655b2 Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 16:07:58 +0530 Subject: [PATCH 070/148] Add typeEditor test to test list --- .../bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts index 1ca51a1f5fd..c67b28f1de7 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts @@ -25,6 +25,7 @@ const videosFolder = path.join(__dirname, '..', 'test-resources', 'videos'); import service from './service/service.spec'; import automation from './automation/automation.spec'; import configuration from './configuration/configuration.spec'; +import typeTest from './type/type.spec'; test.describe.configure({ mode: 'default' }); @@ -38,6 +39,7 @@ test.beforeAll(async () => { test.describe(service); test.describe(automation); test.describe(configuration); +test.describe(typeTest); test.afterAll(async () => { console.log(`>>> Finished test suite`); From bdfb3d75203bef80bb07a29e6f8ce92873f45f48 Mon Sep 17 00:00:00 2001 From: Anjana S Porawagama Date: Fri, 11 Jul 2025 16:11:13 +0530 Subject: [PATCH 071/148] Add end-to-end tests for various integrations and artifacts, including AI Chat Service, Event Integrations, File Integrations, and Other Artifacts. --- .../test/e2e-playwright-tests/test.list.ts | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts index 1ca51a1f5fd..ba5f6fea463 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts @@ -22,8 +22,30 @@ const fs = require('fs'); const path = require('path'); const videosFolder = path.join(__dirname, '..', 'test-resources', 'videos'); -import service from './service/service.spec'; import automation from './automation/automation.spec'; + +import httpService from './api-services/http-service.spec'; +import aiChatService from './api-services/ai-chat-service.spec'; +import graphqlService from './api-services/graphql-service.spec'; +import tcpService from './api-services/tcp-service.spec'; + +import kafkaIntegration from './event-integrations/kafka.spec'; +import rabbitmqIntegration from './event-integrations/rabbitmq.spec'; +import mqttIntegration from './event-integrations/mqtt.spec'; +import azureIntegration from './event-integrations/azure.spec'; +import salesforceIntegration from './event-integrations/salesforce.spec'; +import twillioIntegration from './event-integrations/twillio.spec'; +import githubIntegration from './event-integrations/github.spec'; + +import ftpIntegration from './file-integrations/ftp.spec'; +import directoryIntegration from './file-integrations/directory.spec'; + +import functionArtifact from './other-artifacts/function.spec'; +import naturalFunctionArtifact from './other-artifacts/np.spec'; +import dataMapperArtifact from './other-artifacts/data-mapper.spec'; +import typeDiagramArtifact from './other-artifacts/type.spec'; +import connectionArtifact from './other-artifacts/connection.spec'; + import configuration from './configuration/configuration.spec'; test.describe.configure({ mode: 'default' }); @@ -35,9 +57,37 @@ test.beforeAll(async () => { console.log('>>> Starting test suite'); }); -test.describe(service); +// <----Automation Test----> test.describe(automation); -test.describe(configuration); + +// <----AI Chat Service Test----> +test.describe(aiChatService); + +// <----Integration as API Test----> +test.describe(httpService); +test.describe(graphqlService); +test.describe(tcpService); + +// <----Event Integration Test----> +test.describe(kafkaIntegration); +test.describe(rabbitmqIntegration); +test.describe(mqttIntegration); +test.describe(azureIntegration); +test.describe(salesforceIntegration); +test.describe(twillioIntegration); +test.describe(githubIntegration); + +// <----File Integration Test----> +test.describe(ftpIntegration); +test.describe(directoryIntegration); + +// <----Other Artifacts Test----> +test.describe(functionArtifact); +test.describe(naturalFunctionArtifact); +test.describe(dataMapperArtifact); // TODO: Fix this test +test.describe(typeDiagramArtifact); // TODO: Fix this test +test.describe(connectionArtifact); +test.describe(configuration); // TODO: Fix this test test.afterAll(async () => { console.log(`>>> Finished test suite`); From 8160a77871a7993ad3bc9a1186afa0acdd8a82ba Mon Sep 17 00:00:00 2001 From: sachiniSam Date: Fri, 11 Jul 2025 16:11:59 +0530 Subject: [PATCH 072/148] Format code --- .../type/TypeEditorUtils.ts | 19 +++++++++---------- .../e2e-playwright-tests/type/type.spec.ts | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts index 612089c22d1..fef11ddb937 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/TypeEditorUtils.ts @@ -23,7 +23,7 @@ import { Form } from '@wso2/playwright-vscode-tester'; * Utility class for type editor test operations */ export class TypeEditorUtils { - constructor(private page: Page, private webView: Frame) {} + constructor(private page: Page, private webView: Frame) { } /** * Wait for element to be visible and interactable @@ -58,7 +58,7 @@ export class TypeEditorUtils { async addEnumMember(memberName: string): Promise { const addButton = this.webView.locator('[data-testid="add-member-button"]'); await addButton.click(); - + // Get the last identifier field (newly added) const memberFields = this.webView.locator('[data-testid="identifier-field"]'); const count = await memberFields.count(); @@ -84,10 +84,10 @@ export class TypeEditorUtils { // Fill the newly added field (last in the form) const identifierFields = this.webView.locator('[data-testid="identifier-field"]'); - + const fieldCount = await identifierFields.count(); const lastIndex = fieldCount - 1; - + await this.fillIdentifierField(lastIndex, fieldName); await this.fillTypeField(lastIndex, fieldType); } @@ -102,8 +102,7 @@ export class TypeEditorUtils { // Fill the newly added function (last in the form) const identifierFields = this.webView.locator('[data-testid="identifier-field"]'); - const typeFields = this.webView.locator('[data-testid="type-field"]'); - + const fieldCount = await identifierFields.count(); const lastIndex = fieldCount - 1; @@ -117,7 +116,7 @@ export class TypeEditorUtils { async createType(name: string, kind: 'Enum' | 'Union' | 'Record' | 'Service Class'): Promise { const form = new Form(this.page, 'WSO2 Integrator: BI', this.webView); await form.switchToFormView(false, this.webView); - + await form.fill({ values: { 'Name': { @@ -233,7 +232,7 @@ export class TypeEditorUtils { /** * Create a record type with specified fields */ - async createRecordType(recordName: string, fields: Array<{name: string, type: string}>): Promise { + async createRecordType(recordName: string, fields: Array<{ name: string, type: string }>): Promise { const form = await this.createType(recordName, 'Record'); // Add fields @@ -247,7 +246,7 @@ export class TypeEditorUtils { /** * Create a service class with functions */ - async createServiceClass(className: string, functions: Array<{name: string, returnType: string}>): Promise { + async createServiceClass(className: string, functions: Array<{ name: string, returnType: string }>): Promise { const form = await this.createType(className, 'Service Class'); // Add functions @@ -257,4 +256,4 @@ export class TypeEditorUtils { return form; } -} \ No newline at end of file +} diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts index 0f1f9e32d37..0efcc63a7f2 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/type/type.spec.ts @@ -16,7 +16,7 @@ * under the License. */ -import { test } from '@playwright/test'; +import { test } from '@playwright/test'; import { addArtifact, initTest, page, getWebview, verifyGeneratedSource } from '../utils'; import { Form } from '@wso2/playwright-vscode-tester'; import { TypeEditorUtils } from './TypeEditorUtils'; @@ -47,7 +47,7 @@ export default function createTests() { // ENUM: Role const enumName = `Role${testAttempt}`; - + // Create enum with members, delete one, then save const enumForm = await typeUtils.createEnumType(enumName, ['Admin', 'Engineer', 'Sales', 'Marketing']); await typeUtils.deleteEnumMember(1); // Delete 'Engineer' @@ -96,4 +96,4 @@ export default function createTests() { }); }); -} \ No newline at end of file +} From b211383b138df019cc8abc674a5b7a48b303cdcd Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Fri, 11 Jul 2025 17:01:54 +0530 Subject: [PATCH 073/148] Update changelog for BI version 1.1.0 --- .../ballerina-extension/CHANGELOG.md | 47 ++++++++++++++++++ workspaces/bi/bi-extension/CHANGELOG.md | 48 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/workspaces/ballerina/ballerina-extension/CHANGELOG.md b/workspaces/ballerina/ballerina-extension/CHANGELOG.md index 84c0d8068e8..f21ce7b0622 100644 --- a/workspaces/ballerina/ballerina-extension/CHANGELOG.md +++ b/workspaces/ballerina/ballerina-extension/CHANGELOG.md @@ -2,6 +2,53 @@ All notable changes to the "Ballerina" extension will be documented in this file. +## **5.2.0** (2025-07-12) + +### Major Features + +- **Bundled Language Server**: Ballerina Language Server is now bundled with the extension, eliminating separate installation requirements and improving startup performance +- **Configurable Editor v2**: Complete redesign of the configuration editor with enhanced UI/UX and improved functionality +- **Type Editor Revamp**: Comprehensive overhaul of the type editor with modern UI components and a better user experience + +### Added + +- Enhanced AI file upload support with additional file types for improved analysis capabilities +- Documentation display in Signature Help for a better developer experience during code completion +- Enhanced service resource creation with comprehensive validation system for base paths, resource action calls, reserved keywords, and new UX for creating HTTP responses + +### Changed + +- **Project Management**: Refactored artifacts management and navigation system for better organization +- **UI Components**: + - Type Diagram and GraphQL designer with improved visual presentation +- **Developer Experience**: + - Enhanced renaming editor functionality + - Enhanced Form and Input Editor with Markdown support + - Updated imported types display as view-only nodes for clarity + +### Fixed + +- **Extension Stability**: + - Resolved extension startup and activation issues for reliable performance + - Improved compatibility across different development environments +- **Data Mapping & Visualization**: + - Fixed issues when working with complex data types from imported modules + - Improved visualization of array types and nested data structures + - Enhanced connection line display in design diagrams +- **Testing & Debugging**: + - Fixed GraphQL testing functionality for seamless API testing + - Improved service testing support across different Ballerina versions + - Enhanced test explorer compatibility with legacy projects +- **Configuration Management**: + - Resolved configuration file editing and creation issues + - Fixed form rendering problems that could cause UI freezing +- **Cross-Platform Support**: + - Enhanced Windows compatibility for Java development kit integration + - Improved file path handling across different operating systems +- **User Interface**: + - Fixed theme-related display issues in command interfaces + - Enhanced project artifact management and navigation + ## **5.1.3** (2025-05-28) ### Fixed diff --git a/workspaces/bi/bi-extension/CHANGELOG.md b/workspaces/bi/bi-extension/CHANGELOG.md index 6ef515a5e6a..4420470c031 100644 --- a/workspaces/bi/bi-extension/CHANGELOG.md +++ b/workspaces/bi/bi-extension/CHANGELOG.md @@ -1,5 +1,53 @@ # Change log +## **1.1.0** (2025-07-12) + +### Major Features + +- **Bundled Language Server**: Ballerina Language Server is now bundled with the extension, eliminating separate installation requirements and improving startup performance +- **Configurable Editor v2**: Complete redesign of the configuration editor with enhanced UI/UX and improved functionality +- **Type Editor Revamp**: Comprehensive revamp of the type editor with modern UI components and a better user experience + +### Added + +- Enhanced AI file upload support with additional file types for improved analysis capabilities +- Documentation display in Signature Help for a better developer experience during code completion +- Enhanced service resource creation with comprehensive validation system for base paths, resource action calls, reserved keywords, and new UX for creating HTTP responses + +### Changed + +- **Project Management**: Refactored artifacts management and navigation system for better organization +- **UI Components**: + - Type Diagram and GraphQL designer with improved visual presentation +- **Developer Experience**: + - Enhanced renaming editor functionality + - Enhanced Form and Input Editor with Markdown support + - Updated imported types display as view-only nodes for clarity + +### Fixed + +- **Extension Stability**: + - Resolved extension startup and activation issues for reliable performance + - Improved compatibility across different development environments +- **Data Mapping & Visualization**: + - Fixed issues when working with complex data types from imported modules + - Improved visualization of array types and nested data structures + - Enhanced connection line display in design diagrams +- **Testing & Debugging**: + - Fixed GraphQL testing functionality for seamless API testing + - Improved service testing support across different Ballerina versions + - Enhanced test explorer compatibility with legacy projects +- **Configuration Management**: + - Resolved configuration file editing and creation issues + - Fixed form rendering problems that could cause UI freezing +- **Cross-Platform Support**: + - Enhanced Windows compatibility for Java development kit integration + - Improved file path handling across different operating systems +- **User Interface**: + - Fixed theme-related display issues in command interfaces + - Enhanced project artifact management and navigation + + ## **1.0.3** (2024-05-28) ### Fixes From 7ff412bf9682ad5c7c5687f8f182e68efb1f6d63 Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Fri, 11 Jul 2025 17:11:56 +0530 Subject: [PATCH 074/148] Remove duplicate test suite calls caused by merge conflicts --- .../bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts index 38eb5feee9b..d4d64a2b899 100644 --- a/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts +++ b/workspaces/bi/bi-extension/src/test/e2e-playwright-tests/test.list.ts @@ -61,9 +61,6 @@ test.beforeAll(async () => { // <----Automation Test----> test.describe(automation); -test.describe(configuration); -test.describe(typeTest); - // <----AI Chat Service Test----> test.describe(aiChatService); From 42bd526db72e7c17cf7230c38f629a07166ad91a Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Fri, 11 Jul 2025 21:40:33 +0530 Subject: [PATCH 075/148] Revert download-ls script to download both released and prereleased ls --- workspaces/ballerina/ballerina-extension/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-extension/package.json b/workspaces/ballerina/ballerina-extension/package.json index 49e33cd5fe9..4fe4e7132ab 100644 --- a/workspaces/ballerina/ballerina-extension/package.json +++ b/workspaces/ballerina/ballerina-extension/package.json @@ -200,7 +200,7 @@ "default": false, "description": "Use Ballerina distribution language server instead of bundled language server. Note: This will be automatically enabled for Ballerina versions older than 2201.12.3." }, - "ballerina.enableSequenceDiagramView":{ + "ballerina.enableSequenceDiagramView": { "type": "boolean", "default": true, "description": "Enable the experimental Sequence Diagram View.", @@ -1088,7 +1088,7 @@ "copyFonts": "copyfiles -f ./node_modules/@wso2/font-wso2-vscode/dist/* ./resources/font-wso2-vscode/dist/", "copyVSIX": "copyfiles *.vsix ./vsix", "copyVSIXToRoot": "copyfiles -f ./vsix/*.vsix ../../..", - "download-ls": "node scripts/download-ls.js --prerelease", + "download-ls": "node scripts/download-ls.js", "build": "pnpm run compile && pnpm run lint && pnpm run postbuild", "rebuild": "pnpm run clean && pnpm run compile && pnpm run postbuild", "postbuild": "if [ \"$isPreRelease\" = \"true\" ]; then pnpm run download-ls --prerelease; else pnpm run download-ls; fi && pnpm run copyFonts && pnpm run copyJSLibs && pnpm run package && pnpm run copyVSIX", From dcdae50efa83f73f4d06dbb7cdccccb811d5c5f7 Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Sat, 12 Jul 2025 13:07:34 +0530 Subject: [PATCH 076/148] Update extension versions --- 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 49e33cd5fe9..875e3e38f23 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.1.5", + "version": "5.2.0", "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 872b065ec37..3801e7dd23e 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.0.5", + "version": "1.1.0", "publisher": "wso2", "icon": "resources/images/wso2-ballerina-integrator-logo.png", "repository": { From 7bd06ef8826b782c40968ef97058864e4f46541c Mon Sep 17 00:00:00 2001 From: Chinthaka Jayatilake <37581983+ChinthakaJ98@users.noreply.github.com> Date: Sat, 12 Jul 2025 21:38:05 +0530 Subject: [PATCH 077/148] Migrate the latest MI extension updates --- .../mi-core/src/rpc-types/mi-diagram/types.ts | 4 + .../src/rpc-types/mi-visualizer/index.ts | 3 + .../src/rpc-types/mi-visualizer/rpc-type.ts | 3 + .../src/rpc-types/mi-visualizer/types.ts | 15 + .../src/components/Form/FormGenerator.tsx | 3 +- workspaces/mi/mi-extension/CHANGELOG.md | 13 + workspaces/mi/mi-extension/package.json | 2 +- .../src/lang-client/ExtendedLanguageClient.ts | 10 + .../mi-extension/src/lang-client/activator.ts | 5 + .../project-explorer-provider.ts | 10 +- .../rpc-managers/mi-diagram/rpc-manager.ts | 20 +- .../rpc-managers/mi-visualizer/rpc-handler.ts | 5 + .../rpc-managers/mi-visualizer/rpc-manager.ts | 36 +- .../mi-extension/src/util/fileOperations.ts | 12 +- .../mi/mi-extension/src/util/importCapp.ts | 4 +- workspaces/mi/mi-extension/src/util/index.ts | 4 +- .../mi-extension/src/util/onboardingUtils.ts | 56 +- .../mustach-templates/registryResources.ts | 698 +++++++++++++++++- .../mi/mi-extension/src/util/templates.ts | 5 +- .../mi-extension/src/visualizer/activate.ts | 59 ++ .../rpc-clients/mi-visualizer/rpc-client.ts | 9 + .../mustache-templates/TestSuite.ts | 5 + .../src/views/Forms/APIform/index.tsx | 8 +- .../src/views/Forms/AddressEndpointForm.tsx | 16 +- .../ConnectionFormGenerator.tsx | 2 +- .../DataServiceForm/MainPanelForms/index.tsx | 63 +- .../SidePanelForms/ResourceForm.tsx | 3 - .../src/views/Forms/DataSourceForm/index.tsx | 43 +- .../src/views/Forms/DefaultEndpointForm.tsx | 16 +- .../views/Forms/EditForms/EditProxyForm.tsx | 8 +- .../Forms/EditForms/EditSequenceForm.tsx | 8 +- .../src/views/Forms/FailoverEndpointForm.tsx | 12 +- .../views/Forms/HTTPEndpointForm/index.tsx | 16 +- .../InboundEPform/inboundConnectorForm.tsx | 18 +- .../src/views/Forms/LoadBalanceEPform.tsx | 12 +- .../src/views/Forms/LocalEntryForm.tsx | 12 +- .../src/views/Forms/MessageProcessorForm.tsx | 8 +- .../views/Forms/MessageStoreForm/index.tsx | 8 +- .../src/views/Forms/ProxyServiceForm.tsx | 4 +- .../src/views/Forms/RecipientEndpointForm.tsx | 12 +- .../src/views/Forms/RegistryResourceForm.tsx | 46 +- .../src/views/Forms/SequenceForm.tsx | 18 +- .../src/views/Forms/TaskForm.tsx | 8 +- .../src/views/Forms/TemplateEndpointForm.tsx | 12 +- .../src/views/Forms/TemplateForm.tsx | 12 +- .../src/views/Forms/Tests/TestSuiteForm.tsx | 188 ++++- .../views/Forms/WSDLEndpointForm/index.tsx | 16 +- .../ProjectInformationForm.tsx | 32 + .../Overview/ProjectInformation/index.tsx | 21 + .../syntax-tree/src/syntax-tree-interfaces.ts | 6 +- 50 files changed, 1402 insertions(+), 207 deletions(-) diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts b/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts index 7b4221b052b..1bacadf5b7c 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts @@ -1336,6 +1336,7 @@ export interface CreateRegistryResourceRequest { registryRoot: string; createOption: string; content?: string; + roles?: string; } export interface CreateRegistryResourceResponse { @@ -1499,6 +1500,9 @@ export interface GetAvailableConnectorResponse { iconPath?: string; connectionUiSchema?: connectionUiSchemaRecord; actions?: any[]; + displayName?: string; + artifactId?: string; + connectorZipPath?: string; } export interface ConnectorDependency { diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts index 4a0f7e2233a..2adcc79eede 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts @@ -41,6 +41,7 @@ import { ReadmeContentResponse, AddConfigurableRequest, ProjectDetailsResponse, + UpdatePropertiesRequest, UpdateDependenciesRequest, UpdatePomValuesRequest, UpdateConfigValuesRequest, @@ -86,6 +87,8 @@ export interface MIVisualizerAPI { downloadMI: (params: DownloadMIRequest) => Promise; getSupportedMIVersionsHigherThan: (param:string) => Promise; getProjectDetails: () => Promise; + updateProperties: (params: UpdatePropertiesRequest) => Promise; + reloadIntegrationProjectDependencies: () => Promise; updateDependencies: (params: UpdateDependenciesRequest) => Promise; updatePomValues: (params: UpdatePomValuesRequest) => Promise; updateConfigFileValues: (params: UpdateConfigValuesRequest) => Promise; diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts index db9cd0e07dd..4bbe0f181ba 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts @@ -42,6 +42,7 @@ import { ReadmeContentResponse, AddConfigurableRequest, ProjectDetailsResponse, + UpdatePropertiesRequest, UpdateDependenciesRequest, UpdatePomValuesRequest, UpdateConfigValuesRequest, @@ -90,6 +91,8 @@ export const downloadJavaFromMI: RequestType = { method: `${_pre export const downloadMI: RequestType = { method: `${_preFix}/downloadMI` }; export const getSupportedMIVersionsHigherThan: RequestType = { method: `${_preFix}/getSupportedMIVersionsHigherThan` }; export const getProjectDetails: RequestType = { method: `${_preFix}/getProjectDetails` }; +export const updateProperties: RequestType = { method: `${_preFix}/updateProperties` }; +export const reloadIntegrationProjectDependencies: RequestType = { method: `${_preFix}/reloadIntegrationProjectDependencies` }; export const updateDependencies: RequestType = { method: `${_preFix}/updateDependencies` }; export const updatePomValues: RequestType = { method: `${_preFix}/updatePomValues` }; export const updateConfigFileValues: RequestType = { method: `${_preFix}/updateConfigFileValues` }; diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts index cdcb79fdae7..7fef666f580 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/types.ts @@ -121,6 +121,7 @@ export interface PrimaryDetails { export interface BuildDetails { dockerDetails: DockerDetails; + enableFatCar: PomNodeDetails; advanceDetails: AdvanceDetails; } @@ -148,9 +149,16 @@ export interface PluginDetatils { export interface DependenciesDetails { connectorDependencies: DependencyDetails[]; + integrationProjectDependencies: DependencyDetails[]; otherDependencies: DependencyDetails[]; } +export interface PropertyDetails { + name: string; + value: string; + range?: STRange; +} + export interface DependencyDetails { groupId: string; artifact: string; @@ -177,6 +185,9 @@ export interface UpdateConfigValuesRequest { configValues: PomNodeDetails[]; } +export interface UpdatePropertiesRequest { + properties: PropertyDetails[]; +} export interface UpdateDependenciesRequest { dependencies: DependencyDetails[]; } @@ -185,6 +196,10 @@ export interface UpdateConfigValuesResponse { textEdits: TextEdit[]; } +export interface UpdatePropertiesResponse { + textEdits: TextEdit[]; +} + export interface UpdateDependenciesResponse { textEdits: TextEdit[]; } diff --git a/workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx b/workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx index 8e6e68a41d4..b606ab23091 100644 --- a/workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx +++ b/workspaces/mi/mi-diagram/src/components/Form/FormGenerator.tsx @@ -1297,7 +1297,8 @@ export function FormGenerator(props: FormGeneratorProps) { const subKeyValue = parentValue?.[subKey] || currentVal; return subKeyValue === expectedValue; } - return currentVal === condition[conditionKey] || (typeof condition[conditionKey] === 'string' && String(currentVal) === condition[conditionKey]); + return currentVal === condition[conditionKey] || (typeof condition[conditionKey] === 'string' && String(currentVal) === condition[conditionKey]) || + (typeof condition[conditionKey] === 'boolean' && String(currentVal) === String(condition[conditionKey])); }; if (Array.isArray(conditions)) { diff --git a/workspaces/mi/mi-extension/CHANGELOG.md b/workspaces/mi/mi-extension/CHANGELOG.md index 08b8ac19ffa..a7f57c6dfd4 100644 --- a/workspaces/mi/mi-extension/CHANGELOG.md +++ b/workspaces/mi/mi-extension/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to the "micro-integrator" extension will be documented in this file. +## [2.4.1] - 2025-07-09 + +### Fixed + +Fixed: WS-Policy UI options are not available in the MI extension ([#1164](https://github.com/wso2/mi-vscode/issues/1164)) +Fixed: Artifact name validation issue ([#1165](https://github.com/wso2/mi-vscode/issues/1165)) + +## [2.4.0] - 2025-07-02 + +### New Features + +Added: Support for Resources, Data Mappers and Connections in Unit Test Framework ([#1147](https://github.com/wso2/mi-vscode/issues/1147)) + ## [2.3.3] - 2025-06-27 ### Fixed diff --git a/workspaces/mi/mi-extension/package.json b/workspaces/mi/mi-extension/package.json index 37d27ab5717..40f0c78ab1d 100644 --- a/workspaces/mi/mi-extension/package.json +++ b/workspaces/mi/mi-extension/package.json @@ -3,7 +3,7 @@ "displayName": "Micro Integrator", "description": "An extension which gives a development environment for designing, developing, debugging, and testing integration solutions.", "icon": "resources/images/wso2-micro-integrator-image.png", - "version": "2.3.3", + "version": "2.4.1", "publisher": "wso2", "engines": { "vscode": "^1.100.0" diff --git a/workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts b/workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts index b699a0d4a21..d4d4e94697b 100644 --- a/workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts +++ b/workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts @@ -64,6 +64,8 @@ import { GetConnectionSchemaResponse, GenerateConnectorRequest, GenerateConnectorResponse, + UpdatePropertiesRequest, + UpdatePropertiesResponse, UpdateDependenciesResponse, UpdateDependenciesRequest, GetHelperPaneInfoResponse, @@ -354,6 +356,10 @@ export class ExtendedLanguageClient extends LanguageClient { return this.sendRequest('synapse/getProjectIntegrationType', { uri: Uri.file(path).fsPath }); } + async updateProperties(req: UpdatePropertiesRequest): Promise { + return this.sendRequest('synapse/updateProperty', req); + } + async updateDependencies(req: UpdateDependenciesRequest): Promise { return this.sendRequest('synapse/updateDependency', req); } @@ -362,6 +368,10 @@ export class ExtendedLanguageClient extends LanguageClient { return this.sendRequest('synapse/updateConnectorDependencies'); } + async loadDependentCAppResources(): Promise { + return this.sendRequest('synapse/loadDependentResources'); + } + async getProjectDetails(): Promise { return this.sendRequest('synapse/getOverviewPageDetails'); } diff --git a/workspaces/mi/mi-extension/src/lang-client/activator.ts b/workspaces/mi/mi-extension/src/lang-client/activator.ts index 73825695fb6..4fe20d91811 100644 --- a/workspaces/mi/mi-extension/src/lang-client/activator.ts +++ b/workspaces/mi/mi-extension/src/lang-client/activator.ts @@ -52,6 +52,7 @@ import { log } from '../util/logger'; import { getJavaHomeFromConfig } from '../util/onboardingUtils'; import { SELECTED_SERVER_PATH } from '../debugger/constants'; import { extension } from '../MIExtensionContext'; +import { extractCAppDependenciesAsProjects } from '../visualizer/activate'; const exec = util.promisify(require('child_process').exec); export interface ScopeInfo { @@ -258,6 +259,10 @@ export class MILanguageClient { this.languageClient = new ExtendedLanguageClient('synapseXML', 'Synapse Language Server', this.projectUri, serverOptions, clientOptions); await this.languageClient.start(); + const projectDetails = await this.languageClient?.getProjectDetails(); + const projectName = projectDetails.primaryDetails.projectName.value; + await extractCAppDependenciesAsProjects(projectName); + await this.languageClient?.loadDependentCAppResources(); //Setup autoCloseTags let tagProvider: (document: TextDocument, position: Position) => Thenable = (document: TextDocument, position: Position) => { diff --git a/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts b/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts index cc0dc5f1cfc..2ff33a5810d 100644 --- a/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts +++ b/workspaces/mi/mi-extension/src/project-explorer/project-explorer-provider.ts @@ -135,7 +135,7 @@ async function getProjectStructureData(): Promise { const resp = await langClient?.languageClient?.getProjectExplorerModel(rootPath); const projectDetailsRes = await langClient?.languageClient?.getProjectDetails(); const runtimeVersion = projectDetailsRes.primaryDetails.runtimeVersion.value; - const projectTree = generateTreeData(workspace, resp, runtimeVersion); + const projectTree = await generateTreeData(workspace, resp, runtimeVersion); if (projectTree) { data.push(projectTree); @@ -155,7 +155,7 @@ async function getProjectStructureData(): Promise { } -function generateTreeData(project: vscode.WorkspaceFolder, data: ProjectStructureResponse, runtimeVersion: string): ProjectExplorerEntry | undefined { +async function generateTreeData(project: vscode.WorkspaceFolder, data: ProjectStructureResponse, runtimeVersion: string): Promise { const directoryMap = data.directoryMap; if (directoryMap) { const projectRoot = new ProjectExplorerEntry( @@ -166,12 +166,12 @@ function generateTreeData(project: vscode.WorkspaceFolder, data: ProjectStructur ); projectRoot.contextValue = 'project'; - generateTreeDataOfArtifacts(project, data, projectRoot, runtimeVersion); + await generateTreeDataOfArtifacts(project, data, projectRoot, runtimeVersion); return projectRoot; } } -function generateTreeDataOfArtifacts(project: vscode.WorkspaceFolder, data: ProjectStructureResponse, projectRoot: ProjectExplorerEntry, runtimeVersion: string) { +async function generateTreeDataOfArtifacts(project: vscode.WorkspaceFolder, data: ProjectStructureResponse, projectRoot: ProjectExplorerEntry, runtimeVersion: string) { const artifacts = (data.directoryMap as any)?.src?.main?.wso2mi?.artifacts; if (!artifacts) { return; @@ -199,7 +199,7 @@ function generateTreeDataOfArtifacts(project: vscode.WorkspaceFolder, data: Proj if (['APIs', 'Event Integrations', 'Automations', 'Data Services'].includes(key)) { children = genProjectStructureEntry(artifacts[key]); } else if (key === 'Resources') { - const existingResources = getAvailableRegistryResources(project.uri.fsPath); + const existingResources = await getAvailableRegistryResources(project.uri.fsPath); children = generateResources(artifacts[key], existingResources); } else { children = generateArtifacts(artifacts[key], data, project); diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts index d640dbf1d0c..b8382cdbc24 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts @@ -3733,7 +3733,13 @@ ${endpointAttributes} async createRegistryResource(params: CreateRegistryResourceRequest): Promise { return new Promise(async (resolve) => { const artifactNamePrefix = params.registryRoot === '' ? 'resources/' : params.registryRoot + '/'; - let artifactName = (artifactNamePrefix + params.registryPath).replace(new RegExp('/', 'g'), "_").replace(/_+/g, '_'); + let artifactName; + const runtimeVersion = await this.getMIVersionFromPom(); + if (params.createOption === "import" || compareVersions(runtimeVersion.version, '4.4.0') >= 0) { + artifactName = (artifactNamePrefix + params.registryPath).replace(new RegExp('/', 'g'), "_").replace(/_+/g, '_'); + } else { + artifactName = params.artifactName; + } let projectDir = params.projectDirectory; const fileUri = Uri.file(params.projectDirectory); @@ -3788,7 +3794,9 @@ ${endpointAttributes} let fileName = params.resourceName; const fileData = getMediatypeAndFileExtension(params.templateType); fileName = fileName + "." + fileData.fileExtension; - artifactName = artifactName + '_' + params.resourceName + '_' + fileData.fileExtension; + if (compareVersions(runtimeVersion.version, '4.4.0') >= 0) { + artifactName = artifactName + '_' + params.resourceName + '_' + fileData.fileExtension; + } const registryPath = path.join(registryDir, params.registryPath); const destPath = path.join(registryPath, fileName); if (!fs.existsSync(registryPath)) { @@ -3804,8 +3812,10 @@ ${endpointAttributes} let fileName = params.resourceName; const fileData = getMediatypeAndFileExtension(params.templateType); fileName = fileName + "." + fileData.fileExtension; - artifactName = artifactName + '_' + params.resourceName + '_' + fileData.fileExtension; - let fileContent = params.content ? params.content : getRegistryResourceContent(params.templateType, params.resourceName); + if (compareVersions(runtimeVersion.version, '4.4.0') >= 0) { + artifactName = artifactName + '_' + params.resourceName + '_' + fileData.fileExtension; + } + let fileContent = params.content ? params.content : getRegistryResourceContent(params.templateType, params.resourceName, params.roles); const registryPath = path.join(registryDir, params.registryPath); const destPath = path.join(registryPath, fileName); if (!fs.existsSync(registryPath)) { @@ -3919,7 +3929,7 @@ ${endpointAttributes} async getAvailableRegistryResources(params: ListRegistryArtifactsRequest): Promise { return new Promise(async (resolve) => { - const response = getAvailableRegistryResources(this.projectUri); + const response = await getAvailableRegistryResources(this.projectUri); const artifacts = response.artifacts; var tempArtifactNames: string[] = []; for (let i = 0; i < artifacts.length; i++) { diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts index 3b5b9c88cd8..4dd1a918ad5 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts @@ -62,9 +62,12 @@ import { toggleDisplayOverview, updateContext, getProjectDetails, + updateProperties, + reloadIntegrationProjectDependencies, updateDependencies, updatePomValues, updateConfigFileValues, + UpdatePropertiesRequest, UpdateDependenciesRequest, UpdatePomValuesRequest, UpdateConfigValuesRequest, @@ -123,6 +126,8 @@ export function registerMiVisualizerRpcHandlers(messenger: Messenger, projectUri messenger.onRequest(downloadMI, (args: DownloadMIRequest) => rpcManger.downloadMI(args)); messenger.onRequest(getSupportedMIVersionsHigherThan, (args: string) => rpcManger.getSupportedMIVersionsHigherThan(args)); messenger.onRequest(getProjectDetails, () => rpcManger.getProjectDetails()); + messenger.onRequest(updateProperties, (args: UpdatePropertiesRequest) => rpcManger.updateProperties(args)); + messenger.onRequest(reloadIntegrationProjectDependencies, () => rpcManger.reloadIntegrationProjectDependencies()); messenger.onRequest(updateDependencies, (args: UpdateDependenciesRequest) => rpcManger.updateDependencies(args)); messenger.onRequest(updatePomValues, (args: UpdatePomValuesRequest) => rpcManger.updatePomValues(args)); messenger.onRequest(updateConfigFileValues, (args: UpdateConfigValuesRequest) => rpcManger.updateConfigFileValues(args)); diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts index 6112acfdcf6..25b397cf3b1 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts @@ -55,6 +55,7 @@ import { WorkspaceFolder, WorkspacesResponse, ProjectDetailsResponse, + UpdatePropertiesRequest, UpdateDependenciesRequest, UpdatePomValuesRequest, UpdateConfigValuesRequest, @@ -87,6 +88,7 @@ import { copy } from 'fs-extra'; const fs = require('fs'); import { TextEdit } from "vscode-languageclient"; import { downloadJavaFromMI, downloadMI, getProjectSetupDetails, getSupportedMIVersionsHigherThan, setPathsInWorkSpace, updateRuntimeVersionsInPom, getMIVersionFromPom } from '../../util/onboardingUtils'; +import { extractCAppDependenciesAsProjects } from "../../visualizer/activate"; Mustache.escape = escapeXml; export class MiVisualizerRpcManager implements MIVisualizerAPI { @@ -149,6 +151,38 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { }); } + /** + * Updates project-level properties in the `pom.xml` file. + * + * @param params - An object containing a `properties` field, which is a list of property changes describing the updates to apply. + * @returns A promise that resolves to `true` when the properties have been successfully updated. + */ + async updateProperties(params: UpdatePropertiesRequest): Promise { + return new Promise(async (resolve) => { + const langClient = getStateMachine(this.projectUri).context().langClient!; + const res = await langClient.updateProperties(params); + await this.updatePom(res.textEdits); + resolve(true); + }) + } + + /** + * Reloads the integration project dependencies for the current integration project. + * + * @returns {Promise} A promise that resolves to `true` when all dependency reload operations are complete. + */ + async reloadIntegrationProjectDependencies(): Promise { + return new Promise(async (resolve) => { + const langClient = getStateMachine(this.projectUri).context().langClient!; + const projectDetails = await langClient?.getProjectDetails(); + const projectName = projectDetails.primaryDetails.projectName.value; + await langClient?.updateConnectorDependencies(); + await extractCAppDependenciesAsProjects(projectName); + await langClient?.loadDependentCAppResources(); + resolve(true); + }); + } + async updateDependencies(params: UpdateDependenciesRequest): Promise { return new Promise(async (resolve) => { const langClient = getStateMachine(this.projectUri).context().langClient!; @@ -196,7 +230,7 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { return new Promise(async (resolve) => { const textEdits = params.pomValues.map((pomValue) => { return { - newText: pomValue.value, + newText: String(pomValue.value), range: pomValue.range! as Range }; diff --git a/workspaces/mi/mi-extension/src/util/fileOperations.ts b/workspaces/mi/mi-extension/src/util/fileOperations.ts index 9b9d403f24c..5d58ffa9742 100644 --- a/workspaces/mi/mi-extension/src/util/fileOperations.ts +++ b/workspaces/mi/mi-extension/src/util/fileOperations.ts @@ -30,6 +30,7 @@ import { spawn } from "child_process"; import { RPCLayer } from "../RPCLayer"; import { VisualizerWebview } from "../visualizer/webview"; import { MiVisualizerRpcManager } from "../rpc-managers/mi-visualizer/rpc-manager"; +import { compareVersions, getMIVersionFromPom } from "./onboardingUtils"; interface ProgressMessage { message: string; @@ -644,9 +645,16 @@ export async function createMetadataFilesForRegistryCollection(collectionRoot: s * @param projectDir The project directory. * @returns The list of available registry resources. */ -export function getAvailableRegistryResources(projectDir: string): ListRegistryArtifactsResponse { +export async function getAvailableRegistryResources(projectDir: string): Promise { const result: RegistryArtifact[] = []; - var artifactXMLPath = path.join(projectDir, 'src', 'main', 'wso2mi', 'resources', 'registry', 'artifact.xml'); + + const miVersion = await getMIVersionFromPom(); + if (miVersion && compareVersions(miVersion, '4.4.0') >= 0) { + var artifactXMLPath = path.join(projectDir, 'src', 'main', 'wso2mi', 'resources', 'artifact.xml'); + } else { + var artifactXMLPath = path.join(projectDir, 'src', 'main', 'wso2mi', 'resources', 'registry', 'artifact.xml'); + } + if (fs.existsSync(artifactXMLPath)) { const artifactXML = fs.readFileSync(artifactXMLPath, "utf8"); const options = { diff --git a/workspaces/mi/mi-extension/src/util/importCapp.ts b/workspaces/mi/mi-extension/src/util/importCapp.ts index 6fa855f5564..495f7bf1764 100644 --- a/workspaces/mi/mi-extension/src/util/importCapp.ts +++ b/workspaces/mi/mi-extension/src/util/importCapp.ts @@ -150,7 +150,7 @@ export async function importCapp(params: ImportProjectRequest): Promise { return runtimeVersion; } +export async function getCAppDependenciesFromPom(): Promise { + const pomFiles = await vscode.workspace.findFiles('pom.xml', '**/node_modules/**', 1); + if (pomFiles.length === 0) { + vscode.window.showErrorMessage('pom.xml not found.'); + return []; + } + + const pomContent = await vscode.workspace.openTextDocument(pomFiles[0]); + const result = await parseStringPromise(pomContent.getText(), { explicitArray: false, ignoreAttrs: true }); + const dependencies = result?.project?.dependencies?.dependency; + if (Array.isArray(dependencies)) { + return dependencies + .filter((dep: any) => dep.type === 'car') + .map((dep: any) => `${dep.artifactId}-${dep.version}`); + } + return []; +} + export function filterConnectorVersion(connectorName: string, connectors: any[] | undefined): string { if (!connectors) { return ''; @@ -468,6 +487,18 @@ export async function downloadJavaFromMI(projectUri: string, miVersion: string): export async function downloadMI(projectUri: string, miVersion: string, isUpdatedPack?: boolean): Promise { const miPath = path.join(CACHED_FOLDER, 'micro-integrator'); + if(isUpdatedPack) { + if (fs.existsSync(path.join(miPath, "wso2mi-4.4.0"))) { + const latestUpdateVersion = await fetchLatestMIVersion("4.4.0"); + const currentUpdateVersion = getCurrentUpdateVersion(path.join(miPath, "wso2mi-4.4.0")); + if (latestUpdateVersion && compareVersions(latestUpdateVersion, currentUpdateVersion) > 0) { + fs.rmSync(path.join(miPath, "wso2mi-4.4.0-UPDATED.zip"), { force: true }); + } + } else { + fs.rmSync(path.join(miPath, "wso2mi-4.4.0-UPDATED.zip"), { force: true }); + } + } + try { if (!fs.existsSync(miPath)) { fs.mkdirSync(miPath, { recursive: true }); @@ -1165,7 +1196,7 @@ function getCurrentUpdateVersion(miPath: string): string { return '0'; } -export async function isServerUpdateRequested(): Promise { +export async function isServerUpdateRequested(projectUri: string): Promise { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; if (workspaceFolder) { const config = vscode.workspace.getConfiguration('MI', workspaceFolder.uri); @@ -1184,15 +1215,18 @@ export async function isServerUpdateRequested(): Promise { if (cachedMIPath && cachedMIPath.version === latestUpdateVersion) { const changeOption = 'Switch to Updated Version'; const cancelOption = 'Keep Current Version'; - vscode.window.showWarningMessage( + const selection = await vscode.window.showInformationMessage( 'A newer version of the Micro Integrator is available locally. Would you like to switch to it?', - changeOption, - cancelOption - ).then((selection) => { - if (selection === changeOption) { - setPathsInWorkSpace({ projectUri: workspaceFolder.uri.fsPath, type: 'MI', path: cachedMIPath.path }); - } - }); + { modal: true }, + "Yes", + "No, Don't Ask Again" + ); + if (selection === "Yes") { + setPathsInWorkSpace({ projectUri: projectUri, type: 'MI', path: cachedMIPath.path }); + } else if (selection === "No, Don't Ask Again") { + const config = vscode.workspace.getConfiguration('MI', workspaceFolder.uri); + config.update('suppressServerUpdateNotification', true, vscode.ConfigurationTarget.Workspace); + } } else { const selection = await vscode.window.showInformationMessage( 'A new version of the Micro Integrator is available. Would you like to update now?', diff --git a/workspaces/mi/mi-extension/src/util/template-engine/mustach-templates/registryResources.ts b/workspaces/mi/mi-extension/src/util/template-engine/mustach-templates/registryResources.ts index 5f888638074..e85a67f7395 100644 --- a/workspaces/mi/mi-extension/src/util/template-engine/mustach-templates/registryResources.ts +++ b/workspaces/mi/mi-extension/src/util/template-engine/mustach-templates/registryResources.ts @@ -16,12 +16,30 @@ * under the License. */ -export function getRegistryResource(type: string, resourceName: string) { +import { render } from "mustache"; + +export function getRegistryResource(type: string, resourceName: string, roles: string | undefined) { switch (type) { case "WSDL File": return getWSDLFileTemplate(); case "WS-Policy": return getWSPolicyTemplate(); + case "Username Token": + return render(getUsernameTokenWSPolicyTemplate(), roles); + case "Non-repudiation": + return getNonRepudiationWSPolicyTemplate(); + case "Integrity": + return getIntegrityWSPolicyTemplate(); + case "Confidentiality": + return getConfidentialityWSPolicyTemplate(); + case "Sign and Encrypt - X509 Authentication": + return getSignX509WSPolicyTemplate(); + case "Sign and Encrypt - Anonymous Clients": + return getSignAnonymousWSPolicyTemplate(); + case "Encrypt Only - Username Token Authentication": + return render(getEncryptUsernameWSPolicyTemplate(), roles); + case "Sign and Encrypt - Username Token Authentication": + return render(getSignUsernameWSPolicyTemplate(), roles); case "XSD File": return getXSDTemplate(); case "XSLT File": @@ -131,8 +149,7 @@ function getWSPolicyTemplate() { 300 300 false - org.wso2.carbon.security.util.SecurityTokenStore - + org.wso2.micro.integrator.security.extensions.SecurityTokenStore 300 @@ -158,3 +175,678 @@ function getXSLTemplate() { `; } + +function getUsernameTokenWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + +{{#.}} + + + {{.}} + + +{{/.}}`; +} + +function getNonRepudiationWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +`; +} + +function getIntegrityWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +`; +} + +function getConfidentialityWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +`; +} + +function getSignX509WSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +`; +} + +function getSignAnonymousWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +`; +} + +function getEncryptUsernameWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +{{#.}} + + + {{.}} + + +{{/.}} +`; +} + +function getSignUsernameWSPolicyTemplate() { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wso2carbon + useReqSigCert + true + 300 + 300 + false + org.wso2.micro.integrator.security.extensions.SecurityTokenStore + 300 + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + + + wso2carbon + wso2carbon.jks + -1234 + wso2carbon.jks + wso2carbon + + + +{{#.}} + + + {{.}} + + +{{/.}}`; +} diff --git a/workspaces/mi/mi-extension/src/util/templates.ts b/workspaces/mi/mi-extension/src/util/templates.ts index 28bb3baff9e..0b267959180 100644 --- a/workspaces/mi/mi-extension/src/util/templates.ts +++ b/workspaces/mi/mi-extension/src/util/templates.ts @@ -385,7 +385,7 @@ export const rootPomXmlContent = (projectName: string, groupID: string, artifact org.wso2.maven synapse-unit-test-maven-plugin - 5.2.90 + 5.2.109 synapse-unit-test @@ -418,6 +418,7 @@ export const rootPomXmlContent = (projectName: string, groupID: string, artifact wso2carbon.jks wso2carbon wso2carbon + false true 1.8 1.8 @@ -430,7 +431,7 @@ export const rootPomXmlContent = (projectName: string, groupID: string, artifact 9008 / \${project.runtime.version} - https://github.com/wso2/micro-integrator/releases/download/v\${test.server.version}/wso2mi-\${test.server.version}.zip + false ${initialDependencies} diff --git a/workspaces/mi/mi-extension/src/visualizer/activate.ts b/workspaces/mi/mi-extension/src/visualizer/activate.ts index ffd594a05a8..7c7f8f79282 100644 --- a/workspaces/mi/mi-extension/src/visualizer/activate.ts +++ b/workspaces/mi/mi-extension/src/visualizer/activate.ts @@ -34,6 +34,7 @@ import * as fs from 'fs'; import { AiPanelWebview } from '../ai-panel/webview'; import { MiDiagramRpcManager } from '../rpc-managers/mi-diagram/rpc-manager'; import { log } from '../util/logger'; +import { CACHED_FOLDER, INTEGRATION_PROJECT_DEPENDENCIES_DIR } from '../util/onboardingUtils'; export function activateVisualizer(context: vscode.ExtensionContext, firstProject: string) { context.subscriptions.push( @@ -193,6 +194,9 @@ export function activateVisualizer(context: vscode.ExtensionContext, firstProjec if (document.document.uri.fsPath.endsWith('pom.xml')) { const projectUri = vscode.workspace.getWorkspaceFolder(document.document.uri)?.uri.fsPath; const langClient = getStateMachine(projectUri!).context().langClient; + const projectDetails = await langClient?.getProjectDetails(); + const projectName = projectDetails.primaryDetails.projectName.value; + const confirmUpdate = await vscode.window.showInformationMessage( 'The pom.xml file has been modified. Do you want to update the dependencies?', 'Yes', @@ -204,6 +208,8 @@ export function activateVisualizer(context: vscode.ExtensionContext, firstProjec statusBarItem.text = '$(sync) Updating dependencies...'; statusBarItem.show(); await langClient?.updateConnectorDependencies(); + await extractCAppDependenciesAsProjects(projectName); + await langClient?.loadDependentCAppResources(); statusBarItem.hide(); } } @@ -315,6 +321,59 @@ export function activateVisualizer(context: vscode.ExtensionContext, firstProjec ); } +export async function extractCAppDependenciesAsProjects(projectName: string) { + try { + const dependenciesDir = path.join(CACHED_FOLDER, INTEGRATION_PROJECT_DEPENDENCIES_DIR); + const dependencyDirs = fs.readdirSync(dependenciesDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory() && dirent.name.startsWith(projectName)) + .map(dirent => dirent.name); + + if (dependencyDirs.length === 0) { + return; + } + + const selectedDependencyDir = dependencyDirs[0]; + const downloadedDir = path.join(dependenciesDir, selectedDependencyDir, 'Downloaded'); + const extractedDir = path.join(dependenciesDir, selectedDependencyDir, 'Extracted'); + const carFiles = fs.readdirSync(downloadedDir).filter(file => file.endsWith('.car')); + + // Delete any directory inside the Extracted directory + if (fs.existsSync(extractedDir)) { + const extractedSubDirs = fs.readdirSync(extractedDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => path.join(extractedDir, dirent.name)); + + extractedSubDirs.forEach(subDir => { + fs.rmSync(subDir, { recursive: true, force: true }); + }); + } + + for (const carFile of carFiles) { + const carFileNameWithoutExt = path.basename(carFile, path.extname(carFile)); + const carFileExtractedDir = path.join(extractedDir, carFileNameWithoutExt); + + if (!fs.existsSync(carFileExtractedDir)) { + fs.mkdirSync(carFileExtractedDir, { recursive: true }); + } + await importCapp({ + source: path.join(downloadedDir, carFile), + directory: carFileExtractedDir, + open: false + }); + // During the extraction process, the .car file is renamed to .zip + // Hence remove the .car file after extraction + const zipFilePath = path.join(downloadedDir, carFileNameWithoutExt + '.zip'); + if (fs.existsSync(zipFilePath)) { + fs.rmSync(zipFilePath); + } + } + + vscode.window.showInformationMessage(`Dependencies for project "${projectName}" have been loaded successfully.`); + } catch (error: any) { + vscode.window.showErrorMessage(`Failed to load dependencies: ${error.message}`); + } +} + export const refreshDiagram = debounce(async (projectUri: string, refreshDiagram: boolean = true) => { const webview = webviews.get(projectUri); diff --git a/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts b/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts index 5efd4335a3d..d170cb4b854 100644 --- a/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts +++ b/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts @@ -80,11 +80,14 @@ import { toggleDisplayOverview, updateContext, getProjectDetails, + updateProperties, + reloadIntegrationProjectDependencies, updateDependencies, updatePomValues, updateConfigFileValues, ProjectDetailsResponse, importOpenAPISpec, + UpdatePropertiesRequest, UpdateDependenciesRequest, UpdatePomValuesRequest, UpdateConfigValuesRequest, @@ -245,6 +248,12 @@ export class MiVisualizerRpcClient implements MIVisualizerAPI { getProjectDetails(): Promise { return this._messenger.sendRequest(getProjectDetails, HOST_EXTENSION); } + updateProperties(params: UpdatePropertiesRequest): Promise { + return this._messenger.sendRequest(updateProperties, HOST_EXTENSION, params); + } + reloadIntegrationProjectDependencies(): Promise { + return this._messenger.sendRequest(reloadIntegrationProjectDependencies, HOST_EXTENSION); + } updateDependencies(params: UpdateDependenciesRequest): Promise { return this._messenger.sendRequest(updateDependencies, HOST_EXTENSION, params); } diff --git a/workspaces/mi/mi-visualizer/src/utils/template-engine/mustache-templates/TestSuite.ts b/workspaces/mi/mi-visualizer/src/utils/template-engine/mustache-templates/TestSuite.ts index b6dafb0f935..f3c7fa4d523 100644 --- a/workspaces/mi/mi-visualizer/src/utils/template-engine/mustache-templates/TestSuite.ts +++ b/workspaces/mi/mi-visualizer/src/utils/template-engine/mustache-templates/TestSuite.ts @@ -110,6 +110,11 @@ function getTestSuiteMustacheTemplate() { {{/registryResources}} + + {{#connectorResources}} + {{.}} + {{/connectorResources}} + {{#testCases}} diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/APIform/index.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/APIform/index.tsx index fbc607ffa46..d9e66c74d10 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/APIform/index.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/APIform/index.tsx @@ -110,12 +110,12 @@ export function APIWizard({ apiData, path }: APIWizardProps) { .test('validateApiName', 'An artifact with same file name already exists', function (value) { const { version } = this.parent; const fileName = version ? `${value}_v${version}` : value; - return (value === apiData?.apiName) || !(workspaceFileNames.includes(fileName)); + return (value === apiData?.apiName) || !(workspaceFileNames.includes(fileName.toLowerCase())); }).test('validateArtifactName', 'A registry resource with this artifact name already exists', function (value) { const { version } = this.parent; const artifactName = version ? `${value}:v${version}` : value; - return (value === apiData?.apiName) || !(artifactNames.includes(artifactName)) + return (value === apiData?.apiName) || !(artifactNames.includes(artifactName.toLowerCase())) }), apiContext: yup.string().required("API Context is required") .test('validateApiContext', 'An artifact with same context already exists', function (value) { @@ -231,11 +231,11 @@ export function APIWizard({ apiData, path }: APIWizardProps) { const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: path, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ path: path, }); - setArtifactNames(regArtifactRes.artifacts); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); const contextResp = await rpcClient.getMiDiagramRpcClient().getAllAPIcontexts(); if (apiData) { diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/AddressEndpointForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/AddressEndpointForm.tsx index 409bfd39311..8a796ebcac7 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/AddressEndpointForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/AddressEndpointForm.tsx @@ -117,11 +117,11 @@ export function AddressEndpointWizard(props: AddressEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().required("Endpoint Name is required") .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name"), @@ -170,11 +170,11 @@ export function AddressEndpointWizard(props: AddressEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Template Name") .test('validateTemplateName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateTemplateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().notRequired().default(""), requireTemplateParameters: yup.boolean().notRequired().default(false), @@ -187,11 +187,11 @@ export function AddressEndpointWizard(props: AddressEndpointWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -328,7 +328,7 @@ export function AddressEndpointWizard(props: AddressEndpointWizardProps) { } const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -336,7 +336,7 @@ export function AddressEndpointWizard(props: AddressEndpointWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx index 744d1cea115..6add298a1cb 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/ConnectionForm/ConnectionFormGenerator.tsx @@ -391,7 +391,7 @@ export function AddConnection(props: AddConnectionProps) { return "Connection name already exists"; } else if (workspaceFileNames.includes(connectionName)) { return "An artifact with same name already exists"; - } else if (/[^\w]/.test(connectionName)) { + } else if (/[^a-zA-Z0-9_-]/.test(connectionName)) { return "Connection name cannot contain spaces or special characters"; } return true; diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/MainPanelForms/index.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/MainPanelForms/index.tsx index f386703832c..b8247cacd3e 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/MainPanelForms/index.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/MainPanelForms/index.tsx @@ -82,30 +82,38 @@ const newDataService: DataServiceFields = { ds: [] } -const schema = yup.object({ - dataServiceName: yup.string().required("Data Service Name is required").matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in data service name"), - dataServiceNamespace: yup.string().notRequired(), - serviceGroup: yup.string().notRequired(), - selectedTransports: yup.string().notRequired(), - publishSwagger: yup.string().notRequired(), - jndiName: yup.string().notRequired(), - enableBoxcarring: yup.boolean().notRequired(), - enableBatchRequests: yup.boolean().notRequired(), - serviceStatus: yup.boolean().notRequired(), - disableLegacyBoxcarringMode: yup.boolean().notRequired(), - enableStreaming: yup.boolean().notRequired(), - description: yup.string().notRequired(), - authProviderClass: yup.string().notRequired(), - http: yup.boolean().notRequired(), - https: yup.boolean().notRequired(), - jms: yup.boolean().notRequired(), - local: yup.boolean().notRequired(), - authProps: yup.array().notRequired(), - ds: yup.array().notRequired() -}); - export function DataServiceWizard(props: DataServiceWizardProps) { + const schema = yup.object({ + dataServiceName: yup.string().required("Data Service Name is required") + .matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in Data Service name") + .test('validateTaskName', + 'An artifact with same name already exists', value => { + return !workspaceFileNames.includes(value.toLowerCase()) + }).test('validateArtifactName', + 'A registry resource with this artifact name already exists', value => { + return !artifactNames.includes(value.toLowerCase()) + }), + dataServiceNamespace: yup.string().notRequired(), + serviceGroup: yup.string().notRequired(), + selectedTransports: yup.string().notRequired(), + publishSwagger: yup.string().notRequired(), + jndiName: yup.string().notRequired(), + enableBoxcarring: yup.boolean().notRequired(), + enableBatchRequests: yup.boolean().notRequired(), + serviceStatus: yup.boolean().notRequired(), + disableLegacyBoxcarringMode: yup.boolean().notRequired(), + enableStreaming: yup.boolean().notRequired(), + description: yup.string().notRequired(), + authProviderClass: yup.string().notRequired(), + http: yup.boolean().notRequired(), + https: yup.boolean().notRequired(), + jms: yup.boolean().notRequired(), + local: yup.boolean().notRequired(), + authProps: yup.array().notRequired(), + ds: yup.array().notRequired() + }); + const { control, handleSubmit, @@ -126,9 +134,20 @@ export function DataServiceWizard(props: DataServiceWizardProps) { const [datasources, setDatasources] = useState([]); const [authProperties, setAuthProperties] = useState([]); const [isNewDataService, setIsNewDataService] = useState(!props.path.endsWith(".xml")); + const [artifactNames, setArtifactNames] = useState([]); + const [workspaceFileNames, setWorkspaceFileNames] = useState([]); useEffect(() => { (async () => { + const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ + path: props.path, + }); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); + const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ + path: props.path, + }); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); + if (props.path.endsWith(".dbs")) { if (props.path.includes('/dataServices')) { props.path = props.path.replace('/dataServices', '/data-services'); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/ResourceForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/ResourceForm.tsx index 3bbdaddf676..fc866ec95c6 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/ResourceForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/DataServiceForm/SidePanelForms/ResourceForm.tsx @@ -234,9 +234,6 @@ export const ResourceForm = ({ isOpen, onCancel, onSave, formData }: ResourceFor <>Add Path Param - - <>Add Query Param - {}() /]*$/, "Invalid characters in datasource name"), + name: yup.string().required("Datasource name is required") + .matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in Datasource name") + .test('validateTaskName', + 'An artifact with same name already exists', value => { + return !workspaceFileNames.includes(value.toLowerCase()) + }).test('validateArtifactName', + 'A registry resource with this artifact name already exists', value => { + return !artifactNames.includes(value.toLowerCase()) + }), description: yup.string().notRequired(), type: yup.string().required("Datasource type is required"), dataSourceProvider: yup.string().when('type', { @@ -212,12 +222,21 @@ export function DataSourceWizard(props: DataSourceFormProps) { }, [dsConfigParams]); useEffect(() => { - if (props.path.endsWith(".xml")) { - setIsUpdate(true); - if (props.path.includes('dataSources')) { - props.path = props.path.replace('dataSources', 'data-sources'); - } - (async () => { + (async () => { + const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ + path: props.path, + }); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); + const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ + path: props.path, + }); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); + + if (props.path.endsWith(".xml")) { + setIsUpdate(true); + if (props.path.includes('dataSources')) { + props.path = props.path.replace('dataSources', 'data-sources'); + } const response = await rpcClient.getMiDiagramRpcClient().getDataSource({ path: props.path }); reset(response); const formValues = getValues(); @@ -297,11 +316,11 @@ export function DataSourceWizard(props: DataSourceFormProps) { extractValuesFromUrl(response.url, watch("dbEngine")); } - })(); - } else { - setIsUpdate(false); - reset(newDataSource); - } + } else { + setIsUpdate(false); + reset(newDataSource); + } + })(); }, [props.path]); useEffect(() => { diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/DefaultEndpointForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/DefaultEndpointForm.tsx index 7c89bd7939e..116adb0554e 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/DefaultEndpointForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/DefaultEndpointForm.tsx @@ -115,11 +115,11 @@ export function DefaultEndpointWizard(props: DefaultEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().required("Endpoint Name is required") .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name"), @@ -166,11 +166,11 @@ export function DefaultEndpointWizard(props: DefaultEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Template Name") .test('validateTemplateName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateTemplateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().notRequired().default(""), requireTemplateParameters: yup.boolean().notRequired().default(false), @@ -183,11 +183,11 @@ export function DefaultEndpointWizard(props: DefaultEndpointWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -325,7 +325,7 @@ export function DefaultEndpointWizard(props: DefaultEndpointWizardProps) { } const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -333,7 +333,7 @@ export function DefaultEndpointWizard(props: DefaultEndpointWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditProxyForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditProxyForm.tsx index abfdc7ce113..c214c97fe0b 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditProxyForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditProxyForm.tsx @@ -207,10 +207,10 @@ export function EditProxyForm({ proxyData, isOpen, documentUri, onCancel, onSave name: yup.string().required("Proxy Name is required").matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Proxy name") .test('validateMessageStoreName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && proxyData.name !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && proxyData.name !== value) }).test('validateMessageStoreName', 'A registry resource with this artifact name already exists', value => { - return !(proxyArtifactsNames.includes(value) && proxyData.name !== value) + return !(proxyArtifactsNames.includes(value.toLowerCase()) && proxyData.name !== value) }), endpointType: yup.string(), endpoint: yup.string().when('endpointType', { @@ -587,11 +587,11 @@ export function EditProxyForm({ proxyData, isOpen, documentUri, onCancel, onSave useEffect(() => { (async () => { const result = await getArtifactNamesAndRegistryPaths(documentUri, rpcClient); - setProxyArtifactsNames(result.artifactNamesArr); + setProxyArtifactsNames(result.artifactNamesArr.map(name => name.toLowerCase())); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: documentUri, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [proxyData]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditSequenceForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditSequenceForm.tsx index 97fe9375c1c..17d1010c12a 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditSequenceForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/EditForms/EditSequenceForm.tsx @@ -64,10 +64,10 @@ export function EditSequenceForm({ sequenceData, isOpen, onCancel, onSave, docum name: yup.string().required("Sequence name is required").matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in sequence name") .test('validateSequenceName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && sequenceData.name !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && sequenceData.name !== value) }).test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !(artifactNames.includes(value) && sequenceData.name !== value) + return !(artifactNames.includes(value.toLowerCase()) && sequenceData.name !== value) }), endpoint: yup.string().notRequired(), onError: yup.string().notRequired(), @@ -93,11 +93,11 @@ export function EditSequenceForm({ sequenceData, isOpen, onCancel, onSave, docum const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: documentUri, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ path: documentUri }); - setArtifactNames(regArtifactRes.artifacts); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); })(); }, []); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/FailoverEndpointForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/FailoverEndpointForm.tsx index 7b3ef6e2498..b23879922ee 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/FailoverEndpointForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/FailoverEndpointForm.tsx @@ -105,11 +105,11 @@ export function FailoverWizard(props: FailoverWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }), buildMessage: yup.string().required("Build Message is required"), description: yup.string(), @@ -124,11 +124,11 @@ export function FailoverWizard(props: FailoverWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -204,7 +204,7 @@ export function FailoverWizard(props: FailoverWizardProps) { } (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -212,7 +212,7 @@ export function FailoverWizard(props: FailoverWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/HTTPEndpointForm/index.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/HTTPEndpointForm/index.tsx index 2c34244fee9..51e82c9464a 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/HTTPEndpointForm/index.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/HTTPEndpointForm/index.tsx @@ -45,11 +45,11 @@ export function HttpEndpointWizard(props: HttpEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().required("Endpoint Name is required") .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name"), @@ -143,11 +143,11 @@ export function HttpEndpointWizard(props: HttpEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Template Name") .test('validateTemplateName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateTemplateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().notRequired().default(""), requireTemplateParameters: yup.boolean(), @@ -169,11 +169,11 @@ export function HttpEndpointWizard(props: HttpEndpointWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -278,7 +278,7 @@ export function HttpEndpointWizard(props: HttpEndpointWizardProps) { } const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -286,7 +286,7 @@ export function HttpEndpointWizard(props: HttpEndpointWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/InboundEPform/inboundConnectorForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/InboundEPform/inboundConnectorForm.tsx index 49c6dede1e1..b97a487ffaa 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/InboundEPform/inboundConnectorForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/InboundEPform/inboundConnectorForm.tsx @@ -243,14 +243,9 @@ export function AddInboundConnector(props: AddInboundConnectorProps) { const handleCreateInboundConnector = async (values: any) => { const attributeNames = getGenericAttributeNames(formData); - if (values.generateSequences) { - if (props.model) { - values.sequence = props.model.sequence; - values.onError = props.model.onError; - } - } else { - values.sequence = values.sequence.value; - values.onError = values.onError.value; + if (props.model || String(values.generateSequences) === "false") { + values.sequence = values.sequence.value ?? values.sequence; + values.onError = values.onError.value ?? values.onError; } const { attrFields, paramFields } = extractProperties(values, attributeNames); @@ -264,8 +259,11 @@ export function AddInboundConnector(props: AddInboundConnectorProps) { // Transform the keys of the rest object const transformedParameters = Object.fromEntries( - Object.entries(paramFields).map(([key, value]) => [getOriginalName(key), value]) - .filter(([_, value]) => (value !== null && value !== undefined && value !== '') && typeof value !== 'object') + Object.entries(paramFields) + .filter(([key, value]) => + key !== 'generateSequences' && + value !== null && value !== undefined && value !== '' && typeof value !== 'object' + ).map(([key, value]) => [getOriginalName(key), value]) ); // Merge transformedParameters into finalParameters diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/LoadBalanceEPform.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/LoadBalanceEPform.tsx index fafa736e429..140f8963d50 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/LoadBalanceEPform.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/LoadBalanceEPform.tsx @@ -112,11 +112,11 @@ export function LoadBalanceWizard(props: LoadBalanceWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }), algorithm: yup.string().required("Algorithm is required"), failover: yup.string().required("Failover is required"), @@ -135,11 +135,11 @@ export function LoadBalanceWizard(props: LoadBalanceWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -217,7 +217,7 @@ export function LoadBalanceWizard(props: LoadBalanceWizardProps) { } (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -225,7 +225,7 @@ export function LoadBalanceWizard(props: LoadBalanceWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/LocalEntryForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/LocalEntryForm.tsx index a77a88fcc90..ba098ab8199 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/LocalEntryForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/LocalEntryForm.tsx @@ -104,10 +104,10 @@ export function LocalEntryWizard(props: LocalEntryWizardProps) { const schema = yup.object({ name: yup.string().required("Local Entry Name is required").matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Local Entry name") .test('validateSequenceName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && savedLocalEntryName !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && savedLocalEntryName !== value) }) .test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !(artifactNames.includes(value) && savedLocalEntryName !== value) + return !(artifactNames.includes(value.toLowerCase()) && savedLocalEntryName !== value) }), type: yup.string(), saveInReg: yup.boolean().default(false), @@ -133,10 +133,10 @@ export function LocalEntryWizard(props: LocalEntryWizardProps) { otherwise: () => yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -172,7 +172,7 @@ export function LocalEntryWizard(props: LocalEntryWizardProps) { useEffect(() => { (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -180,7 +180,7 @@ export function LocalEntryWizard(props: LocalEntryWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/MessageProcessorForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/MessageProcessorForm.tsx index c762cd25921..14422ad7f2b 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/MessageProcessorForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/MessageProcessorForm.tsx @@ -98,10 +98,10 @@ export function MessageProcessorWizard(props: MessageProcessorWizardProps) { .matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in Message Processor name") .test('validateTaskName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && savedMPName !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && savedMPName !== value) }).test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !(artifactNames.includes(value) && savedMPName !== value) + return !(artifactNames.includes(value.toLowerCase()) && savedMPName !== value) }), messageProcessorType: yup.string().default(""), messageStoreType: yup.string().when('messageProcessorType', { @@ -198,11 +198,11 @@ export function MessageProcessorWizard(props: MessageProcessorWizardProps) { const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ path: props.path, }); - setArtifactNames(regArtifactRes.artifacts); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); if (props.path.endsWith(".xml")) { setIsNewMessageProcessor(false); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/MessageStoreForm/index.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/MessageStoreForm/index.tsx index 243e6c439c7..102e9a391e4 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/MessageStoreForm/index.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/MessageStoreForm/index.tsx @@ -178,10 +178,10 @@ export function MessageStoreWizard(props: MessageStoreWizardProps) { name: yup.string().required("Message Store name is required").matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in message store name") .test('validateSequenceName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && storeName !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && storeName !== value) }).test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !(artifactNames.includes(value) && storeName !== value) + return !(artifactNames.includes(value.toLowerCase()) && storeName !== value) }), type: yup.string(), connectionInformationType: yup.string().default("Pool").oneOf(["Pool", "Carbon Datasource"]), @@ -362,11 +362,11 @@ export function MessageStoreWizard(props: MessageStoreWizardProps) { const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ path: props.path }); - setArtifactNames(regArtifactRes.artifacts); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); const xmlFileNames = await rpcClient.getMiDiagramRpcClient().getAvailableResources({ resourceType: "messageStore", documentIdentifier: props.path, diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/ProxyServiceForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/ProxyServiceForm.tsx index c50e5ebaeaf..d2c0ebd72ca 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/ProxyServiceForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/ProxyServiceForm.tsx @@ -70,7 +70,7 @@ export function ProxyServiceWizard(props: ProxyServiceWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Proxy Service Name") .test('validateProxyServiceName', 'An artifact with same name already exists', value => { - return !workspaceFileNames.includes(value) + return !workspaceFileNames.includes(value.toLowerCase()) }), proxyServiceType: yup.string().default(""), endpointType: yup.string().notRequired().default(""), @@ -133,7 +133,7 @@ export function ProxyServiceWizard(props: ProxyServiceWizardProps) { const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, []); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/RecipientEndpointForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/RecipientEndpointForm.tsx index 8fcd009c1cc..f4ff23bfcb5 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/RecipientEndpointForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/RecipientEndpointForm.tsx @@ -103,11 +103,11 @@ export function RecipientWizard(props: RecipientWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }), description: yup.string(), endpoints: yup.array(), @@ -121,11 +121,11 @@ export function RecipientWizard(props: RecipientWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -201,7 +201,7 @@ export function RecipientWizard(props: RecipientWizardProps) { } (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -209,7 +209,7 @@ export function RecipientWizard(props: RecipientWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/RegistryResourceForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/RegistryResourceForm.tsx index 050f424bb1b..aef5fb8d7a5 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/RegistryResourceForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/RegistryResourceForm.tsx @@ -39,6 +39,10 @@ const templates = [{ value: "Data Mapper" }, { value: "Javascript File" }, { val { value: "WS-Policy" }, { value: "XSD File" }, { value: "XSL File" }, { value: "XSLT File" }, { value: "YAML File" }, { value: "TEXT File" }, { value: "XML File" }, { value: "RB File" }, { value: "GROOVY File" }]; +const policyTypes = [{ value: "Username Token" }, { value: "Non-repudiation" }, { value: "Integrity" }, { value: "Confidentiality" }, + { value: "Sign and Encrypt - X509 Authentication" }, { value: "Sign and Encrypt - Anonymous Clients" }, + { value: "Encrypt Only - Username Token Authentication" }, { value: "Sign and Encrypt - Username Token Authentication" }]; + type InputsFields = { templateType?: string; filePath?: string; @@ -47,6 +51,8 @@ type InputsFields = { registryPath?: string; createOption?: "new" | "import"; registryType?: string; + policyType?: string; + roles?: string; }; const canCreateTemplateForType = (type: string) => { @@ -77,7 +83,9 @@ const getInitialResource = (type: string): InputsFields => ({ artifactName: "", registryPath: type ? type : "xslt", createOption: canCreateTemplateForType(type) ? "new" : "import", - registryType: "gov" + registryType: "gov", + policyType: "Username Token", + roles: "" }); const getTemplateType = (type: string) => { @@ -189,6 +197,15 @@ export function RegistryResourceForm(props: RegistryWizardProps) { otherwise: () => yup.string().notRequired(), }), + policyType: yup.string().when(['createOption', 'templateType'], { + is: (createOption: string, templateType: string) => + createOption === 'new' && templateType === 'WS-Policy', + then: () => + yup.string().required("Policy type is required"), + otherwise: () => + yup.string().notRequired(), + }), + roles: yup.string().notRequired(), resourceName: yup.string().when('createOption', { is: "new", then: () => @@ -333,13 +350,14 @@ export function RegistryResourceForm(props: RegistryWizardProps) { const projectDir = props.path ? (await rpcClient.getMiDiagramRpcClient().getProjectRoot({ path: props.path })).path : (await rpcClient.getVisualizerState()).projectUri; const regRequest: CreateRegistryResourceRequest = { projectDirectory: projectDir, - templateType: values.templateType, + templateType: values.templateType === "WS-Policy" ? values.policyType : values.templateType, filePath: values.filePath, resourceName: values.resourceName, - artifactName: '', + artifactName: values.resourceName, registryPath: handleRegistryPathPrefix(values.registryPath), registryRoot: getRegistryRoot(values.registryType, values.registryPath), - createOption: values.createOption + createOption: values.createOption, + roles: values.roles } const regfilePath = await rpcClient.getMiDiagramRpcClient().createRegistryResource(regRequest); @@ -399,6 +417,26 @@ export function RegistryResourceForm(props: RegistryWizardProps) { {...register("templateType")} dropdownContainerSx={{ position: "relative", "z-index": 1000 }} /> + { watch("templateType") === "WS-Policy" && ( + <> + + {(["Username Token", + "Encrypt Only - Username Token Authentication", + "Sign and Encrypt - Username Token Authentication"]).includes(watch("policyType")) && ( + + )} + + )} )} {!createOptionValue && (<>
diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/SequenceForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/SequenceForm.tsx index dcd2d9e2a1b..dccb842a2a5 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/SequenceForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/SequenceForm.tsx @@ -76,10 +76,10 @@ export function SequenceWizard(props: SequenceWizardProps) { name: yup.string().required("Sequence name is required").matches(/^[a-zA-Z0-9_-]*$/, "Invalid characters in sequence name") .test('validateSequenceName', 'An artifact with same name already exists', value => { - return !workspaceFileNames.includes(value) + return !workspaceFileNames.includes(value.toLowerCase()) }).test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !artifactNames.includes(value) + return !artifactNames.includes(value.toLowerCase()) }), endpoint: yup.string().notRequired(), onErrorSequence: yup.string().notRequired(), @@ -93,10 +93,10 @@ export function SequenceWizard(props: SequenceWizardProps) { otherwise: () => yup.string().required("Artifact Name is required").test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }).test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -131,7 +131,7 @@ export function SequenceWizard(props: SequenceWizardProps) { useEffect(() => { (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -139,7 +139,7 @@ export function SequenceWizard(props: SequenceWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, []); @@ -160,6 +160,12 @@ export function SequenceWizard(props: SequenceWizardProps) { } const result = await rpcClient.getMiDiagramRpcClient().createSequence(createSequenceParams); + if (result.filePath === "") { + let registryDir = path.join(projectDir, 'src', 'main', 'wso2mi', 'resources', 'registry', values.registryType); + const registryPath = path.join(registryDir, values.registryPath); + result.filePath = path.join(registryPath, values.name + ".xml"); + } + if (watch("saveInReg")) { await saveToRegistry(rpcClient, props.path, values.registryType, values.name, result.fileContent, values.registryPath, values.artifactName); } diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/TaskForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/TaskForm.tsx index 2521220b14f..e9495b9adb5 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/TaskForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/TaskForm.tsx @@ -138,10 +138,10 @@ export function TaskForm(props: TaskFormProps) { .matches(/^[a-zA-Z0-9]*$/, "Invalid characters in Task name") .test('validateTaskName', 'An artifact with same name already exists', value => { - return !(workspaceFileNames.includes(value) && savedTaskName !== value) + return !(workspaceFileNames.includes(value.toLowerCase()) && savedTaskName !== value) }).test('validateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !(artifactNames.includes(value) && savedTaskName !== value) + return !(artifactNames.includes(value.toLowerCase()) && savedTaskName !== value) }), group: yup.string().required("Task group is required"), implementation: yup.string().required("Task Implementation is required"), @@ -253,11 +253,11 @@ export function TaskForm(props: TaskFormProps) { const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, }); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); const regArtifactRes = await rpcClient.getMiDiagramRpcClient().getAvailableRegistryResources({ path: props.path, }); - setArtifactNames(regArtifactRes.artifacts); + setArtifactNames(regArtifactRes.artifacts.map(name => name.toLowerCase())); })(); }, []); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/TemplateEndpointForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/TemplateEndpointForm.tsx index a623a1b00d3..61aa1913def 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/TemplateEndpointForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/TemplateEndpointForm.tsx @@ -98,11 +98,11 @@ export function TemplateEndpointWizard(props: TemplateEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }), uri: yup.string(), template: yup.string().required("Template is required"), @@ -118,11 +118,11 @@ export function TemplateEndpointWizard(props: TemplateEndpointWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -181,7 +181,7 @@ export function TemplateEndpointWizard(props: TemplateEndpointWizardProps) { } (async () => { const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -189,7 +189,7 @@ export function TemplateEndpointWizard(props: TemplateEndpointWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/TemplateForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/TemplateForm.tsx index 289dba0446e..efaec1eed18 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/TemplateForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/TemplateForm.tsx @@ -85,11 +85,11 @@ export function TemplateWizard(props: TemplateWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]$?'"<>{}() /]*$/, "Invalid characters in Template Name") .test('validateTemplateName', 'An artifact with same name already exists', value => { - return !isNewTemplate ? !(workspaceFileNames.includes(value) && value !== savedTemplateName) : !workspaceFileNames.includes(value); + return !isNewTemplate ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedTemplateName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateTemplateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewTemplate ? !(artifactNames.includes(value) && value !== savedTemplateName) : !artifactNames.includes(value); + return !isNewTemplate ? !(artifactNames.includes(value.toLowerCase()) && value !== savedTemplateName) : !artifactNames.includes(value.toLowerCase()); }), templateType: yup.string().default(""), address: yup.string().notRequired().default(""), @@ -109,11 +109,11 @@ export function TemplateWizard(props: TemplateWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -219,7 +219,7 @@ export function TemplateWizard(props: TemplateWizardProps) { } const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -227,7 +227,7 @@ export function TemplateWizard(props: TemplateWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Forms/Tests/TestSuiteForm.tsx b/workspaces/mi/mi-visualizer/src/views/Forms/Tests/TestSuiteForm.tsx index c5b2ae3b42b..d552248468a 100644 --- a/workspaces/mi/mi-visualizer/src/views/Forms/Tests/TestSuiteForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Forms/Tests/TestSuiteForm.tsx @@ -18,7 +18,7 @@ import styled from "@emotion/styled"; import { yupResolver } from "@hookform/resolvers/yup"; -import { EVENT_TYPE, MACHINE_VIEW, ProjectStructureArtifactResponse, GetSelectiveArtifactsResponse, GetUserAccessTokenResponse, ResourceType, RegistryArtifact } from "@wso2/mi-core"; +import { EVENT_TYPE, MACHINE_VIEW, ProjectStructureArtifactResponse, GetSelectiveArtifactsResponse, GetUserAccessTokenResponse, ResourceType, RegistryArtifact, GetAvailableConnectorResponse } from "@wso2/mi-core"; import { useVisualizerContext } from "@wso2/mi-rpc-client"; import { Button, ComponentCard, ContainerProps, ContextMenu, Dropdown, FormActions, FormGroup, FormView, Item, ProgressIndicator, TextField, Typography, Icon } from "@wso2/ui-toolkit"; import { useEffect, useState } from "react"; @@ -32,6 +32,8 @@ import { SelectMockService } from "./MockServices/SelectMockService"; import { MI_UNIT_TEST_GENERATION_BACKEND_URL } from "../../../constants"; import { getParamManagerFromValues, getParamManagerValues, ParamConfig, ParamField, ParamManager, ParamValue } from "@wso2/mi-diagram"; import { normalize } from "upath"; +import { compareVersions } from "@wso2/mi-diagram/lib/utils/commons"; +import { getProjectRuntimeVersion } from "../../AIPanel/utils"; interface TestSuiteFormProps { stNode?: UnitTest; @@ -88,6 +90,7 @@ export function TestSuiteForm(props: TestSuiteFormProps) { const [testCases, setTestCases] = useState([]); const [mockServices, setMockServices] = useState([]); + const [connectorData, setConnectorData] = useState([]); const [currentTestCase, setCurrentTestCase] = useState(undefined); const [currentMockService, setCurrentMockService] = useState(undefined); @@ -101,6 +104,8 @@ export function TestSuiteForm(props: TestSuiteFormProps) { const isWindows = props.isWindows; const fileName = filePath ? filePath.split(isWindows ? path.win32.sep : path.sep).pop().split(".xml")[0] : undefined; + const [miVersion, setMiVersion] = useState(""); + const paramConfigs: ParamField = { id: 0, type: "KeyLookup", @@ -113,7 +118,7 @@ export function TestSuiteForm(props: TestSuiteFormProps) { id: 0, type: "KeyLookup", label: "Name", - filterType: ["sequence", "endpoint", "sequenceTemplate", "endpointTemplate", "localEntry", "registry"] as ResourceType[], + filterType: ["sequence", "endpoint", "sequenceTemplate", "endpointTemplate", "localEntry", "unitTestRegistry"] as ResourceType[], isRequired: true, artifactTypes: { registryArtifacts: true, artifacts: false } }; @@ -135,6 +140,7 @@ export function TestSuiteForm(props: TestSuiteFormProps) { artifactType: yup.string().required("Artifact type is required"), artifact: yup.string().required("Artifact is required"), supportiveArtifacts: yup.object(), + connectorResources: yup.object(), registryResources: yup.object() }); @@ -158,6 +164,10 @@ export function TestSuiteForm(props: TestSuiteFormProps) { paramValues: [], paramFields: [paramConfigs] }, + connectorResources: { + paramValues: [], + paramFields: [] + }, registryResources: { paramValues: [], paramFields: [registryParamConfigs] @@ -282,10 +292,28 @@ export function TestSuiteForm(props: TestSuiteFormProps) { const testSuites = await rpcClient.getMiDiagramRpcClient().getAllTestSuites(); setAllTestSuites(testSuites.testSuites); + const miRuntimeVersion = await getProjectRuntimeVersion(rpcClient); + setMiVersion(miRuntimeVersion); + + const projectConnectors = await rpcClient.getMiDiagramRpcClient().getAvailableConnectors({ + documentUri: "", + connectorName: null + }) + const connectorList = projectConnectors.connectors ? projectConnectors.connectors : []; + setConnectorData(connectorList); + const connectorParamConfigs: ParamField = { + id: 0, + type: "AutoComplete", + label: "Connector Name", + values: connectorList.map(item => item.displayName), + isRequired: true + }; + if (syntaxTree && filePath) { let artifactType = ""; let artifactPath = ""; let supportiveArtifacts: any[] = []; + let connectorResources: any[] = []; let registryResources: any[] = []; if (syntaxTree.unitTestArtifacts.testArtifact.artifact) { @@ -316,12 +344,20 @@ export function TestSuiteForm(props: TestSuiteFormProps) { for (let i = 0; i < resourcesWithoutPropertiesFiles.length; i++) { const param = resourcesWithoutPropertiesFiles[i]; registryResources.push([{ - value: getKeyFromRegistryArtifactPath(param), + value: getKeyFromRegistryArtifactPath(param, miRuntimeVersion), additionalData: { path: param }, }]); } } + if (syntaxTree.unitTestArtifacts.connectorResources?.connectorResources) { + syntaxTree.unitTestArtifacts.connectorResources.connectorResources.map((resource: any) => { + connectorResources.push([{ + value: getDisplayNameFromValue(resource.textNode, connectorList) + }]); + }); + } + // get test cases if (syntaxTree.testCases?.testCases) { const testCases = getTestCases(syntaxTree.testCases.testCases); @@ -342,12 +378,18 @@ export function TestSuiteForm(props: TestSuiteFormProps) { paramValues: getParamManagerFromValues(supportiveArtifacts, undefined, 0), paramFields: [paramConfigs] }, + connectorResources: { + paramValues: getParamManagerFromValues(connectorResources, undefined, 0), + paramFields: [connectorParamConfigs] + }, registryResources: { paramValues: getParamManagerFromValues(registryResources, undefined, 0), paramFields: [registryParamConfigs] } }); } else { + setTestCases([]); + setMockServices([]); reset({ name: "", artifactType: "API", @@ -356,6 +398,10 @@ export function TestSuiteForm(props: TestSuiteFormProps) { paramValues: [], paramFields: [paramConfigs] }, + connectorResources: { + paramValues: [], + paramFields: [connectorParamConfigs] + }, registryResources: { paramValues: [], paramFields: [registryParamConfigs] @@ -405,6 +451,48 @@ export function TestSuiteForm(props: TestSuiteFormProps) { setShowAddMockService(true); } + function getConnectorResources(connectorResources: any[]): string[] { + return connectorResources.map(item => { + if (Array.isArray(item)) { + if (typeof item[0] === 'object' && item[0] !== null && 'value' in item[0]) { + return item[0].value; + } + return item[0]; + } + return item; + }); + } + + function processConnectors(names: string[]): string[] { + return names.map(name => { + const match = connectorData.find(item => item.displayName === name); + if (match) { + return getRelativeSrcPath(match.connectorZipPath) ?? match.artifactId + "-" + match.version; + } + return name; + }); + } + + function getRelativeSrcPath(fullPath: string): string { + if (fullPath === undefined) { + return; + } + const normalizedPath = path.normalize(fullPath); + const parts = normalizedPath.split(path.sep + "src" + path.sep); + + if (parts.length > 1) { + return path.join("src", parts[1]); + } + return normalizedPath; + } + + function getDisplayNameFromValue(value: string, connectorList: GetAvailableConnectorResponse[]): string | undefined { + const match = connectorList.find((item: GetAvailableConnectorResponse) => + getRelativeSrcPath(item.connectorZipPath) === value || item.artifactId + "-" + item.version === value + ); + return match?.displayName ; + } + const submitForm = async (values: any) => { values.testCases = testCases; values.artifact = values.artifact.startsWith(path.sep) ? values.artifact.slice(1) : values.artifact; @@ -416,6 +504,10 @@ export function TestSuiteForm(props: TestSuiteFormProps) { const registryResources = getParamManagerValues(values.registryResources, true); values.registryResources = await getRegistryArtifactDetails(registryResources); + const connectorResources = getParamManagerValues(values.connectorResources, false); + const connectors = getConnectorResources(connectorResources); + values.connectorResources = processConnectors(connectors); + const mockServicePaths = []; const mockServicesDirs = ["src", "test", "resources", "mock-services"]; for (let i = 0; i < mockServices.length; i++) { @@ -437,10 +529,19 @@ export function TestSuiteForm(props: TestSuiteFormProps) { ]; const miDiagramRpcClient = rpcClient.getMiDiagramRpcClient(); - const [allRegistryResources, allRegistryPaths] = await Promise.all([ - (await miDiagramRpcClient.getAvailableRegistryResources({ path: projectUri, withAdditionalData: true })).artifactsWithAdditionalData, - miDiagramRpcClient.getAllRegistryPaths({ path: projectUri }), - ]); + + let allRegistryResources, allRegistryPaths, allResourcePaths; + if(compareVersions(miVersion, "4.4.0") >= 0) { + [allRegistryResources, allResourcePaths] = await Promise.all([ + (await miDiagramRpcClient.getAvailableRegistryResources({ path: projectUri, withAdditionalData: true })).artifactsWithAdditionalData, + miDiagramRpcClient.getAllResourcePaths(), + ]); + }else{ + [allRegistryResources, allRegistryPaths] = await Promise.all([ + (await miDiagramRpcClient.getAvailableRegistryResources({ path: projectUri, withAdditionalData: true })).artifactsWithAdditionalData, + miDiagramRpcClient.getAllRegistryPaths({path: projectUri}), + ]); + } const artifacts = allRegistryResources.map((artifact) => ({ ...artifact, @@ -456,28 +557,46 @@ export function TestSuiteForm(props: TestSuiteFormProps) { for (const param of params) { const paramKey = param[0]; - const artifact = artifacts.find((a) => a.key === paramKey.value); - if (!artifact) continue; + const artifact = artifacts.find((a) => { + + let artifactFileName, paramFileName; + const normalizedKey = a.key.endsWith('.dmc') ? a.key.replace(/\.dmc$/, '') : a.key; + if(compareVersions(miVersion, "4.4.0") >= 0) { + artifactFileName = normalizedKey.replace("gov:mi-resources/", ""); + paramFileName = paramKey.value.replace("resources:", ""); + } else { + artifactFileName = normalizedKey; + paramFileName = paramKey.value; + } + return artifactFileName === paramFileName; + + }); + if (!artifact) { + continue; + } + const registryPath = artifact.path.endsWith('/') && artifact.path.length > 1 ? artifact.path.slice(0, -1) : artifact.path; - const artifactPath = normalize(param[0].additionalData.path).replace(`${normalize(projectUri)}/`, '') + const artifactPath = normalize(param[0].additionalData.path).replace(`${normalize(projectUri)}/`, ''); + + const artifactFile = artifact.file.endsWith('.dmc') ? artifact.file.replace(/\.dmc$/, '.ts') : artifact.file; const newArtifact = { - fileName: artifact.file, + fileName: artifactFile, artifact: artifactPath, registryPath, mediaType: artifact.mediaType, }; - artifactDetails.push(newArtifact); const relativePath = findArtifactRelativePath(paramKey.value); if ( relativePath && - allRegistryPaths.registryPaths.includes(`${relativePath}.properties`) + ((compareVersions(miVersion, "4.4.0") >= 0 && allResourcePaths.resourcePaths.includes(`${relativePath}.properties`)) || + (compareVersions(miVersion, "4.4.0") < 0 && allRegistryPaths.registryPaths.includes(`${relativePath}.properties`))) ) { artifactDetails.push({ fileName: `${newArtifact.fileName}.properties`, @@ -491,7 +610,9 @@ export function TestSuiteForm(props: TestSuiteFormProps) { function parseParamValue(paramValue: string): { type: string; fileName: string } | null { const match = paramValue.match(/(conf|gov):(.+)/); - if (!match) return null; + if (!match) { + return null; + } const [, type, fileName] = match; return { type, fileName }; } @@ -532,14 +653,19 @@ export function TestSuiteForm(props: TestSuiteFormProps) { return true; }); } - function getKeyFromRegistryArtifactPath(filePath: string) { - const BASE_PATH = 'src/main/wso2mi/resources/registry/'; - const VALID_PREFIXES = ['gov', 'conf']; + function getKeyFromRegistryArtifactPath(filePath: string, version:string) { + let BASE_PATH; + if (compareVersions(version, "4.4.0") >= 0) { + BASE_PATH = 'src/main/wso2mi/'; + }else{ + BASE_PATH = 'src/main/wso2mi/resources/registry/'; + } + + const VALID_PREFIXES = ['gov', 'conf','resources']; if (!filePath.startsWith(BASE_PATH)) { throw new Error('Invalid base path in file path.'); } - const relativePath = filePath.slice(BASE_PATH.length); const pathComponents = relativePath.split('/'); @@ -553,8 +679,10 @@ export function TestSuiteForm(props: TestSuiteFormProps) { // Reconstruct the remaining path const remainingPath = pathComponents.join('/'); + const cleanedRemainingPath = remainingPath.replace(/\.ts$/, ''); + // Build the transformed path - return `${prefix}:${remainingPath}`; + return `${prefix}:${cleanedRemainingPath}`; } const getTestCases = (testCases: TestCase[]): TestCaseEntry[] => { return testCases.map((testCase) => { @@ -747,6 +875,28 @@ export function TestSuiteForm(props: TestSuiteFormProps) { )} />
+ ( + { + values.paramValues = values.paramValues.map((param: any, index: number) => { + const property: ParamValue[] = param.paramValues; + param.key = index + 1; + param.value = (property[0]?.value as any)?.value ?? property[0]?.value; + param.icon = 'query'; + + return param; + }); + onChange(values); + }} + /> + )} + />
{}() /]*$/, "Invalid characters in Endpoint Name") .test('validateEndpointName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateEndpointArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().required("Endpoint Name is required") .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Endpoint Name"), @@ -101,11 +101,11 @@ export function WsdlEndpointWizard(props: WsdlEndpointWizardProps) { .matches(/^[^@\\^+;:!%&,=*#[\]?'"<>{}() /]*$/, "Invalid characters in Template Name") .test('validateTemplateName', 'An artifact with same name already exists', value => { - return !isNewEndpoint ? !(workspaceFileNames.includes(value) && value !== savedEPName) : !workspaceFileNames.includes(value); + return !isNewEndpoint ? !(workspaceFileNames.includes(value.toLowerCase()) && value !== savedEPName) : !workspaceFileNames.includes(value.toLowerCase()); }) .test('validateTemplateArtifactName', 'A registry resource with this artifact name already exists', value => { - return !isNewEndpoint ? !(artifactNames.includes(value) && value !== savedEPName) : !artifactNames.includes(value); + return !isNewEndpoint ? !(artifactNames.includes(value.toLowerCase()) && value !== savedEPName) : !artifactNames.includes(value.toLowerCase()); }) : yup.string().notRequired().default(""), requireTemplateParameters: yup.boolean(), @@ -119,11 +119,11 @@ export function WsdlEndpointWizard(props: WsdlEndpointWizardProps) { yup.string().required("Artifact Name is required") .test('validateArtifactName', 'Artifact name already exists', value => { - return !artifactNames.includes(value); + return !artifactNames.includes(value.toLowerCase()); }) .test('validateFileName', 'A file already exists in the workspace with this artifact name', value => { - return !workspaceFileNames.includes(value); + return !workspaceFileNames.includes(value.toLowerCase()); }), }), registryPath: yup.string().when('saveInReg', { @@ -206,7 +206,7 @@ export function WsdlEndpointWizard(props: WsdlEndpointWizardProps) { } const result = await getArtifactNamesAndRegistryPaths(props.path, rpcClient); - setArtifactNames(result.artifactNamesArr); + setArtifactNames(result.artifactNamesArr.map(name => name.toLowerCase())); setRegistryPaths(result.registryPaths); const artifactRes = await rpcClient.getMiDiagramRpcClient().getAllArtifacts({ path: props.path, @@ -214,7 +214,7 @@ export function WsdlEndpointWizard(props: WsdlEndpointWizardProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); const runtimeVersion = response.primaryDetails.runtimeVersion.value; setIsRegistryContentVisible(compareVersions(runtimeVersion, RUNTIME_VERSION_440) < 0); - setWorkspaceFileNames(artifactRes.artifacts); + setWorkspaceFileNames(artifactRes.artifacts.map(name => name.toLowerCase())); })(); }, [props.path]); diff --git a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ProjectInformationForm.tsx b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ProjectInformationForm.tsx index 13cff4b7dd7..975bb33e731 100644 --- a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ProjectInformationForm.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ProjectInformationForm.tsx @@ -50,6 +50,11 @@ const treeViewSelectedStyle = { margin: "0px 0px 3px", whiteSpace: "nowrap", ove const treeViewStyle = { ...treeViewSelectedStyle, opacity: 0.8 }; const sectionTitleStyle = { margin: 0, paddingLeft: 20 }; +// Field name to pom property name mapping +export const fieldToPomPropertyMap: Record = { + "buildDetails-enableFatCar": "fat.car.enable", +}; + export function ProjectInformationForm(props: ProjectInformationFormProps) { const { rpcClient } = useVisualizerContext(); const [projectDetails, setProjectDetails] = useState(); @@ -64,6 +69,7 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { "primaryDetails-runtimeVersion": yup.string().required("Runtime version is required"), "buildDetails-dockerDetails-dockerFileBaseImage": yup.string().required("Base image is required"), "buildDetails-dockerDetails-dockerName": yup.string().required("Docker name is required"), + "buildDetails-enableFatCar": yup.boolean(), "buildDetails-dockerDetails-enableCipherTool": yup.boolean(), "buildDetails-dockerDetails-keyStoreName": yup.string(), "buildDetails-dockerDetails-keyStoreAlias": yup.string(), @@ -169,6 +175,7 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { "primaryDetails-runtimeVersion": response.primaryDetails?.runtimeVersion?.value, "buildDetails-dockerDetails-dockerFileBaseImage": response.buildDetails?.dockerDetails?.dockerFileBaseImage?.value, "buildDetails-dockerDetails-dockerName": response.buildDetails?.dockerDetails?.dockerName.value, + "buildDetails-enableFatCar": response.buildDetails?.enableFatCar?.value === 'true', "buildDetails-dockerDetails-enableCipherTool": Boolean(response.buildDetails?.dockerDetails?.cipherToolEnable?.value), "buildDetails-dockerDetails-keyStoreName": response.buildDetails?.dockerDetails?.keyStoreName?.value, "buildDetails-dockerDetails-keyStoreAlias": response.buildDetails?.dockerDetails?.keyStoreAlias?.value, @@ -238,6 +245,7 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { const handleFormSubmit = async () => { try { const changes: any[] = []; + const fieldsToAdd: string[] = []; Object.entries(dirtyFields).forEach(async ([field]) => { if (field === "advanced-legacyExpressionSupport") { let isLegacyExpressionSupportEnabled = getValues("advanced-legacyExpressionSupport"); @@ -254,6 +262,9 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { } else { changes.push({ value: fieldValue, range }); } + } else { + // No range found, so this field needs to be newly added + fieldsToAdd.push(field); } }); @@ -263,6 +274,19 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { await rpcClient.getMiVisualizerRpcClient().updatePomValues({ pomValues: sortedChanges }); } + if (fieldsToAdd.length > 0) { + const newProperties = fieldsToAdd.map(field => { + const value = getValues(field as any); + const name = fieldToPomPropertyMap[field]; + if (!name) { + return null; // Skip if no mapping found + } + return { name, value: typeof value === "boolean" ? value.toString() : value }; + }); + if (newProperties.length > 0) { + await rpcClient.getMiVisualizerRpcClient().updateProperties({ properties: newProperties }); + } + } let isRemoteDeploymentSupportEnabled = getValues("deployment-deployOnRemoteServer"); await rpcClient.getMiVisualizerRpcClient().updateProjectSettingsConfig({ configName: "REMOTE_DEPLOYMENT_ENABLED", @@ -473,6 +497,14 @@ export function ProjectInformationForm(props: ProjectInformationFormProps) { sx={fieldStyle} {...register("buildDetails-dockerDetails-dockerName")} /> + ({ + paramValues: [], + paramFields: [] + }); const [otherDependencies, setOtherDependencies] = useState({ paramValues: [], paramFields: [] @@ -57,6 +61,7 @@ export function ProjectInformation(props: ProjectInformationProps) { const response = await rpcClient.getMiVisualizerRpcClient().getProjectDetails(); setProjectDetails(response); setDependencies(response.dependencies.connectorDependencies, setConnectorDependencies); + setDependencies(response.dependencies.integrationProjectDependencies, setIntegrationProjectDependencies); setDependencies(response.dependencies.otherDependencies, setOtherDependencies); } catch (error) { console.error("Error fetching project details:", error); @@ -198,6 +203,10 @@ export function ProjectInformation(props: ProjectInformationProps) { ; } + const reloadIntegrationProjectDependencies = () => { + rpcClient.getMiVisualizerRpcClient().reloadIntegrationProjectDependencies(); + } + const handleEditProjectInformation = (componentType: string) => { rpcClient.getMiVisualizerRpcClient().openView({ type: POPUP_EVENT_TYPE.OPEN_VIEW, @@ -253,6 +262,18 @@ export function ProjectInformation(props: ProjectInformationProps) { + + Integration Project Dependencies +
+ +
+
+ {Dependencies("Integration Project Dependencies", dependencies?.integrationProjectDependencies, "car", integrationProjectDependencies, setIntegrationProjectDependencies)} + + + Other Dependencies {Dependencies("Other Dependencies", dependencies?.otherDependencies, "jar", otherDependencies, setOtherDependencies)} diff --git a/workspaces/mi/syntax-tree/src/syntax-tree-interfaces.ts b/workspaces/mi/syntax-tree/src/syntax-tree-interfaces.ts index a711a941bf6..6eec9239049 100644 --- a/workspaces/mi/syntax-tree/src/syntax-tree-interfaces.ts +++ b/workspaces/mi/syntax-tree/src/syntax-tree-interfaces.ts @@ -1898,7 +1898,7 @@ export interface UnitTestArtifacts { testArtifact: TestArtifact; supportiveArtifacts: SupportiveArtifacts; registryResources: RegistryResources; - connectorResources: STNode; + connectorResources: ConnectorResources; } export interface SupportiveArtifacts extends STNode { @@ -1909,6 +1909,10 @@ export interface RegistryResources extends STNode { registryResources: Artifact[]; } +export interface ConnectorResources extends STNode { + connectorResources: Artifact[]; +} + export interface TestArtifact extends STNode { artifact: Artifact; } From c5fe448e6ccc7db4996fdf1141147fb9203bf1ef Mon Sep 17 00:00:00 2001 From: Kanushka Gayan Date: Mon, 14 Jul 2025 09:49:41 +0530 Subject: [PATCH 078/148] Update workspaces/ballerina/ballerina-extension/CHANGELOG.md Co-authored-by: Sachini Samson <30947080+sachiniSam@users.noreply.github.com> --- workspaces/ballerina/ballerina-extension/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/ballerina/ballerina-extension/CHANGELOG.md b/workspaces/ballerina/ballerina-extension/CHANGELOG.md index f21ce7b0622..4616ceb9226 100644 --- a/workspaces/ballerina/ballerina-extension/CHANGELOG.md +++ b/workspaces/ballerina/ballerina-extension/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to the "Ballerina" extension will be documented in this file - **Bundled Language Server**: Ballerina Language Server is now bundled with the extension, eliminating separate installation requirements and improving startup performance - **Configurable Editor v2**: Complete redesign of the configuration editor with enhanced UI/UX and improved functionality -- **Type Editor Revamp**: Comprehensive overhaul of the type editor with modern UI components and a better user experience +- **Type Editor Revamp**: A redesign of the type editor to improve feature discoverability and deliver a better user experience ### Added From 4f5ca6c0ee253cf863cdaba32936750515352a99 Mon Sep 17 00:00:00 2001 From: Anjana S Porawagama Date: Mon, 14 Jul 2025 10:47:27 +0530 Subject: [PATCH 079/148] Enhance artifact identification in BIFlowDiagram by allowing search by both id and name --- .../ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx index 1861a58fbc2..949333f4e61 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/FlowDiagram/index.tsx @@ -419,12 +419,12 @@ export function BIFlowDiagram(props: BIFlowDiagramProps) { // Find the correct artifact by currentIdentifier (id) let currentArtifact = artifacts.artifacts.at(0); artifacts.artifacts.forEach((artifact) => { - if (artifact.id === currentIdentifier) { + if (artifact.id === currentIdentifier || artifact.name === currentIdentifier) { currentArtifact = artifact; } // Check if artifact has resources and find within those if (artifact.resources && artifact.resources.length > 0) { - const resource = artifact.resources.find((resource) => resource.id === currentIdentifier); + const resource = artifact.resources.find((resource) => resource.id === currentIdentifier || resource.name === currentIdentifier); if (resource) { currentArtifact = resource; } From 6c65fa611fbb08785ba72668cd9b38d345fcb632 Mon Sep 17 00:00:00 2001 From: chathurangaj Date: Mon, 14 Jul 2025 07:32:29 +0530 Subject: [PATCH 080/148] Reload dependencies upon update dependencies btn click --- workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts | 2 +- .../mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts | 2 +- .../src/rpc-managers/mi-visualizer/rpc-handler.ts | 4 ++-- .../src/rpc-managers/mi-visualizer/rpc-manager.ts | 4 ++-- .../src/rpc-clients/mi-visualizer/rpc-client.ts | 6 +++--- .../Overview/ProjectInformation/ManageDependencies.tsx | 1 + .../src/views/Overview/ProjectInformation/index.tsx | 6 +++--- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts index 2adcc79eede..dbde60e243b 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/index.ts @@ -88,7 +88,7 @@ export interface MIVisualizerAPI { getSupportedMIVersionsHigherThan: (param:string) => Promise; getProjectDetails: () => Promise; updateProperties: (params: UpdatePropertiesRequest) => Promise; - reloadIntegrationProjectDependencies: () => Promise; + reloadDependencies: () => Promise; updateDependencies: (params: UpdateDependenciesRequest) => Promise; updatePomValues: (params: UpdatePomValuesRequest) => Promise; updateConfigFileValues: (params: UpdateConfigValuesRequest) => Promise; diff --git a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts index 4bbe0f181ba..0daec95ed07 100644 --- a/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts +++ b/workspaces/mi/mi-core/src/rpc-types/mi-visualizer/rpc-type.ts @@ -92,7 +92,7 @@ export const downloadMI: RequestType = { method: `${_ export const getSupportedMIVersionsHigherThan: RequestType = { method: `${_preFix}/getSupportedMIVersionsHigherThan` }; export const getProjectDetails: RequestType = { method: `${_preFix}/getProjectDetails` }; export const updateProperties: RequestType = { method: `${_preFix}/updateProperties` }; -export const reloadIntegrationProjectDependencies: RequestType = { method: `${_preFix}/reloadIntegrationProjectDependencies` }; +export const reloadDependencies: RequestType = { method: `${_preFix}/reloadDependencies` }; export const updateDependencies: RequestType = { method: `${_preFix}/updateDependencies` }; export const updatePomValues: RequestType = { method: `${_preFix}/updatePomValues` }; export const updateConfigFileValues: RequestType = { method: `${_preFix}/updateConfigFileValues` }; diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts index 4dd1a918ad5..56a75194576 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-handler.ts @@ -63,7 +63,7 @@ import { updateContext, getProjectDetails, updateProperties, - reloadIntegrationProjectDependencies, + reloadDependencies, updateDependencies, updatePomValues, updateConfigFileValues, @@ -127,7 +127,7 @@ export function registerMiVisualizerRpcHandlers(messenger: Messenger, projectUri messenger.onRequest(getSupportedMIVersionsHigherThan, (args: string) => rpcManger.getSupportedMIVersionsHigherThan(args)); messenger.onRequest(getProjectDetails, () => rpcManger.getProjectDetails()); messenger.onRequest(updateProperties, (args: UpdatePropertiesRequest) => rpcManger.updateProperties(args)); - messenger.onRequest(reloadIntegrationProjectDependencies, () => rpcManger.reloadIntegrationProjectDependencies()); + messenger.onRequest(reloadDependencies, () => rpcManger.reloadDependencies()); messenger.onRequest(updateDependencies, (args: UpdateDependenciesRequest) => rpcManger.updateDependencies(args)); messenger.onRequest(updatePomValues, (args: UpdatePomValuesRequest) => rpcManger.updatePomValues(args)); messenger.onRequest(updateConfigFileValues, (args: UpdateConfigValuesRequest) => rpcManger.updateConfigFileValues(args)); diff --git a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts index 25b397cf3b1..2fb620aa834 100644 --- a/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts +++ b/workspaces/mi/mi-extension/src/rpc-managers/mi-visualizer/rpc-manager.ts @@ -167,11 +167,11 @@ export class MiVisualizerRpcManager implements MIVisualizerAPI { } /** - * Reloads the integration project dependencies for the current integration project. + * Reloads the dependencies for the current integration project. * * @returns {Promise} A promise that resolves to `true` when all dependency reload operations are complete. */ - async reloadIntegrationProjectDependencies(): Promise { + async reloadDependencies(): Promise { return new Promise(async (resolve) => { const langClient = getStateMachine(this.projectUri).context().langClient!; const projectDetails = await langClient?.getProjectDetails(); diff --git a/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts b/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts index d170cb4b854..f5be2016491 100644 --- a/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts +++ b/workspaces/mi/mi-rpc-client/src/rpc-clients/mi-visualizer/rpc-client.ts @@ -81,7 +81,7 @@ import { updateContext, getProjectDetails, updateProperties, - reloadIntegrationProjectDependencies, + reloadDependencies, updateDependencies, updatePomValues, updateConfigFileValues, @@ -251,8 +251,8 @@ export class MiVisualizerRpcClient implements MIVisualizerAPI { updateProperties(params: UpdatePropertiesRequest): Promise { return this._messenger.sendRequest(updateProperties, HOST_EXTENSION, params); } - reloadIntegrationProjectDependencies(): Promise { - return this._messenger.sendRequest(reloadIntegrationProjectDependencies, HOST_EXTENSION); + reloadDependencies(): Promise { + return this._messenger.sendRequest(reloadDependencies, HOST_EXTENSION); } updateDependencies(params: UpdateDependenciesRequest): Promise { return this._messenger.sendRequest(updateDependencies, HOST_EXTENSION, params); diff --git a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ManageDependencies.tsx b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ManageDependencies.tsx index 177360a2dea..19a36479d7d 100644 --- a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ManageDependencies.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/ManageDependencies.tsx @@ -146,6 +146,7 @@ export function ManageDependencies(props: ManageDependenciesProps) { pomValues: removedDependencies.map(dep => ({ range: dep.range, value: '' })) }); } + await rpcClient.getMiVisualizerRpcClient().reloadDependencies(); onClose(); }; diff --git a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/index.tsx b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/index.tsx index fb919f10698..2e19097e30d 100644 --- a/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/index.tsx +++ b/workspaces/mi/mi-visualizer/src/views/Overview/ProjectInformation/index.tsx @@ -203,8 +203,8 @@ export function ProjectInformation(props: ProjectInformationProps) { ; } - const reloadIntegrationProjectDependencies = () => { - rpcClient.getMiVisualizerRpcClient().reloadIntegrationProjectDependencies(); + const reloadDependencies = () => { + rpcClient.getMiVisualizerRpcClient().reloadDependencies(); } const handleEditProjectInformation = (componentType: string) => { @@ -265,7 +265,7 @@ export function ProjectInformation(props: ProjectInformationProps) { Integration Project Dependencies
-
From 6d3f8db56bc82cc3d63d778b14e81220237e05e6 Mon Sep 17 00:00:00 2001 From: kaumini Date: Mon, 14 Jul 2025 11:25:32 +0530 Subject: [PATCH 081/148] Add advanced config editor e2e tests --- .../ViewConfigurableVariables/index.tsx | 2 + .../test/e2e-playwright-tests/ConfigEditor.ts | 25 ++++++++ .../configuration/configuration.spec.ts | 59 ++++++++++++++++--- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Configurables/ViewConfigurableVariables/index.tsx b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Configurables/ViewConfigurableVariables/index.tsx index ab06a1103eb..bf0d0018e9e 100644 --- a/workspaces/ballerina/ballerina-visualizer/src/views/BI/Configurables/ViewConfigurableVariables/index.tsx +++ b/workspaces/ballerina/ballerina-visualizer/src/views/BI/Configurables/ViewConfigurableVariables/index.tsx @@ -424,6 +424,7 @@ export function ViewConfigurableVariables(props?: ConfigProps) { }} />