From 6e38e48368c76cd70168cd623546ae19374b62d7 Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Mon, 17 Jun 2024 13:34:54 +0300 Subject: [PATCH 1/6] feat: allow access `Joule` from VS Code --- packages/app-studio-toolkit/package.json | 16 +++ .../src/devspace-manager/instance.ts | 19 ++- .../devspace-manager/landscape/landscape.ts | 57 ++++++--- .../src/devspace-manager/landscape/set.ts | 12 +- .../src/public-api/outbound-connectivity.ts | 119 ++++++++++++++++++ 5 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts diff --git a/packages/app-studio-toolkit/package.json b/packages/app-studio-toolkit/package.json index 85ab029d..f22b8b76 100644 --- a/packages/app-studio-toolkit/package.json +++ b/packages/app-studio-toolkit/package.json @@ -59,6 +59,14 @@ "title": "Add landscape", "icon": "$(add)" }, + { + "command": "local-extension.landscape.set-ai-purpose", + "title": "Set landscape for AI purpose" + }, + { + "command": "local-extension.send-outbound-request", + "title": "Send outbound request" + }, { "command": "local-extension.landscape.delete", "title": "Remove landscape" @@ -182,6 +190,14 @@ } ], "commandPalette": [ + { + "when": "true", + "command": "local-extension.landscape.set-ai-purpose" + }, + { + "when": "true", + "command": "local-extension.send-outbound-request" + }, { "when": "false", "command": "local-extension.landscape.add" diff --git a/packages/app-studio-toolkit/src/devspace-manager/instance.ts b/packages/app-studio-toolkit/src/devspace-manager/instance.ts index 50a39d38..7c7e117c 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/instance.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/instance.ts @@ -18,7 +18,10 @@ import { BasRemoteAuthenticationProvider } from "../authentication/authProvider" import { cmdLoginToLandscape } from "./landscape/landscape"; import { getBasUriHandler } from "./handler/basHandler"; import { cmdOpenInVSCode } from "./devspace/open"; -import { getJwt } from "../authentication/auth-utils"; +import { + sendRequest, + setLandscapeForAiPurpose, +} from "../public-api/outbound-connectivity"; export function initBasRemoteExplorer(context: ExtensionContext): void { context.subscriptions.push( @@ -30,6 +33,20 @@ export function initBasRemoteExplorer(context: ExtensionContext): void { const devSpaceExplorer = new DevSpacesExplorer(context.extensionPath); /* istanbul ignore next */ + context.subscriptions.push( + commands.registerCommand( + "local-extension.landscape.set-ai-purpose", + setLandscapeForAiPurpose + ) + ); + + context.subscriptions.push( + commands.registerCommand( + "local-extension.send-outbound-request", + sendRequest + ) + ); + context.subscriptions.push( commands.registerCommand("local-extension.tree.refresh", () => devSpaceExplorer.refreshTree() diff --git a/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts b/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts index 6ebd7108..79c0844a 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts @@ -5,7 +5,7 @@ import { commands, workspace, } from "vscode"; -import { compact, isEmpty, size, trim, uniq } from "lodash"; +import { compact, isEmpty, size, trim, uniqBy } from "lodash"; import { hasJwt, timeUntilJwtExpires } from "../../authentication/auth-utils"; import { URL } from "node:url"; import { getLogger } from "../../../src/logger/logger"; @@ -40,44 +40,61 @@ export interface LandscapeInfo { isLoggedIn: boolean; } +export type LandscapeConfig = { url: string; ai?: boolean }; + function isLandscapeLoggedIn(url: string): Promise { return hasJwt(url); } -export function getLanscapesConfig(): string[] { - return uniq( +export function getLanscapesConfig(): LandscapeConfig[] { + let config = + workspace.getConfiguration().get("sap-remote.landscape-name") ?? ""; + // check if it is an old format - replace `,` with `|` - TODO: remove this in future (backward compatibility) + if (!/.*\{.+\}.*/.test(config)) { + config = config.replace(/,/g, "|"); + } + // split by | and parse each landscape + return uniqBy( compact( - ( - workspace.getConfiguration().get("sap-remote.landscape-name") ?? - "" - ) - .split(",") - .map((value) => (value ? new URL(trim(value)).toString() : value)) - ) + config.split("|").map((landscape) => { + try { + const item: LandscapeConfig = JSON.parse(landscape); + return Object.assign( + { url: item.url }, + item.ai ? { ai: item.ai } : {} + ); + } catch (e) { + // if not a valid JSON - consider it as a URL - TODO: remove this in future (backward compatibility) + if (trim(landscape).length > 0) { + return { url: landscape }; + } + } + }) + ), + "url" ); } -export async function updateLandscapesConfig(value: string[]): Promise { +export async function updateLandscapesConfig( + values: LandscapeConfig[] +): Promise { + const value = values.map((item) => JSON.stringify(item)).join("|"); return workspace .getConfiguration() - .update( - "sap-remote.landscape-name", - value.join(","), - ConfigurationTarget.Global - ) + .update("sap-remote.landscape-name", value, ConfigurationTarget.Global) .then(() => { - getLogger().debug(`Landscapes config updated: ${value.toString()}`); + getLogger().debug(`Landscapes config updated: ${value}`); }); } export async function getLandscapes(): Promise { const lands: LandscapeInfo[] = []; for (const landscape of getLanscapesConfig()) { - const url = new URL(landscape); + const url = new URL(landscape.url); lands.push({ name: url.hostname, url: url.toString(), - isLoggedIn: await isLandscapeLoggedIn(landscape), + isLoggedIn: await isLandscapeLoggedIn(landscape.url), }); } return lands; @@ -88,7 +105,7 @@ export async function removeLandscape(landscapeName: string): Promise { if (size(config) > 0) { const toRemove = new URL(landscapeName).toString(); const updated = config.filter( - (landscape) => new URL(landscape).toString() !== toRemove + (landscape) => new URL(landscape.url).toString() !== toRemove ); if (size(updated) !== size(config)) { return updateLandscapesConfig(updated); diff --git a/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts b/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts index 5d019d4b..85cd9bd4 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts @@ -17,9 +17,17 @@ export async function cmdLandscapeSet(): Promise { }); if (landscape) { + // const isDefault = await window.showQuickPick(["set as default"], { + // placeHolder: "Whether to set this landscape as the default for outbound connectivity", + // canPickMany: true, + // ignoreFocusOut: true + // }); + // if(isDefault) { + // continue if user not cancelled return addLandscape(landscape).finally( () => void commands.executeCommand("local-extension.tree.refresh") ); + // } } } @@ -27,9 +35,9 @@ export async function addLandscape(landscapeName: string): Promise { const toAdd = new URL(landscapeName).toString(); const landscapes = getLanscapesConfig(); if ( - !landscapes.find((landscape) => new URL(landscape).toString() === toAdd) + !landscapes.find((landscape) => new URL(landscape.url).toString() === toAdd) ) { - landscapes.push(landscapeName); + landscapes.push({ url: toAdd }); return updateLandscapesConfig(landscapes); } } diff --git a/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts new file mode 100644 index 00000000..d548d292 --- /dev/null +++ b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts @@ -0,0 +1,119 @@ +import { + AuthenticationGetSessionOptions, + QuickPickItem, + QuickPickItemKind, + authentication, + commands, + window, +} from "vscode"; +import { + LandscapeInfo, + getLandscapes, + getLanscapesConfig, + updateLandscapesConfig, +} from "../devspace-manager/landscape/landscape"; +import { getLogger } from "../logger/logger"; +import { BasRemoteAuthenticationProvider } from "../authentication/authProvider"; +import { hasJwt } from "../authentication/auth-utils"; +import { isEmpty } from "lodash"; + +const LBL_ADD_LANDSCAPE = "Add landscape"; + +function getAiLandscape(): string { + return getLanscapesConfig().find((landscape) => landscape.ai)?.url ?? ""; +} + +async function setAiLandscape(value: string): Promise { + const configs = getLanscapesConfig(); + // reset ai flag for all landscapes if exists + configs.forEach((landscape) => { + delete landscape.ai; + }); + // update landscape if it exists in the list or add it + const index = configs.findIndex((landscape) => landscape.url === value); + if (index != -1) { + // exists + configs[index].ai = true; + } else { + // not exists : add the landscape to the list + configs.push({ url: value, ai: true }); + } + return updateLandscapesConfig(configs); +} + +function selectLandscape( + landscapes: LandscapeInfo[] +): Promise { + const items: QuickPickItem[] = landscapes.map((landscape) => ({ + url: landscape.url, + label: landscape.name, + })); + items.unshift({ label: "Available", kind: QuickPickItemKind.Separator }); // existing items section separator + items.push({ label: "Initiate", kind: QuickPickItemKind.Separator }); // action section separator + items.push({ label: LBL_ADD_LANDSCAPE }); + return window.showQuickPick(items, { + placeHolder: "Choose the landscape to use for AI purposes", + ignoreFocusOut: true, + }) as Promise; +} + +export async function setLandscapeForAiPurpose(): Promise { + const outboundLandscape = getAiLandscape(); + // select landscape for outbound connectivity + let selectedLandscape: QuickPickItem | undefined; + do { + // remove selected ai landscape from the list + const landscapes = (await getLandscapes()).filter( + (item) => item.url !== outboundLandscape + ); + selectedLandscape = await selectLandscape(landscapes); + if (selectedLandscape?.label === LBL_ADD_LANDSCAPE) { + await commands.executeCommand("local-extension.landscape.add"); + } + } while (selectedLandscape?.label === LBL_ADD_LANDSCAPE); + + if (selectedLandscape) { + await setAiLandscape((selectedLandscape as any).url); + } + return !!selectedLandscape; +} + +export async function sendRequest(request: any): Promise { + async function verifyLandscape(): Promise { + try { + let outboundLandscape = getAiLandscape(); + if (!outboundLandscape) { + // if landscape is not set - it means that user has not selected any landscape + if (!(await setLandscapeForAiPurpose())) { + throw new Error("AI landscape is not set"); + } + outboundLandscape = getAiLandscape(); + } + const found = (await getLandscapes()).filter( + (item) => item.url === outboundLandscape + ); + if (isEmpty(found)) { + throw new Error("invalid landscape"); + } + if (found[0].isLoggedIn) { + return true; + } + // landscape is set - log in to it + await authentication.getSession( + BasRemoteAuthenticationProvider.id, + [outboundLandscape], + { forceNewSession: true } as AuthenticationGetSessionOptions + ); + return hasJwt(outboundLandscape); + } catch (error) { + getLogger().error(error); + return false; + } + } + + if (await verifyLandscape()) { + void window.showInformationMessage( + `Sending request to ${getAiLandscape()}` + ); + } +} From 4e5ada96eb1274474b9290ea14079c25b888c77f Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Tue, 18 Jun 2024 18:34:30 +0300 Subject: [PATCH 2/6] refactor: logic improved, strings updated --- packages/app-studio-toolkit/package.json | 34 +++++++++--- .../src/devspace-manager/common/messages.ts | 7 ++- .../src/devspace-manager/instance.ts | 18 +++++- .../devspace-manager/landscape/landscape.ts | 16 ++++-- .../tree/devSpacesProvider.ts | 15 +++-- .../src/public-api/outbound-connectivity.ts | 55 ++++++++++++------- 6 files changed, 106 insertions(+), 39 deletions(-) diff --git a/packages/app-studio-toolkit/package.json b/packages/app-studio-toolkit/package.json index f22b8b76..d7e61536 100644 --- a/packages/app-studio-toolkit/package.json +++ b/packages/app-studio-toolkit/package.json @@ -59,10 +59,6 @@ "title": "Add landscape", "icon": "$(add)" }, - { - "command": "local-extension.landscape.set-ai-purpose", - "title": "Set landscape for AI purpose" - }, { "command": "local-extension.send-outbound-request", "title": "Send outbound request" @@ -127,6 +123,16 @@ "light": "resources/devspace/login.svg", "dark": "resources/devspace/login.svg" } + }, + { + "command": "local-extension.landscape.enable-ai", + "title": "Make this the default landscape for Joule", + "icon": "$(sparkle)" + }, + { + "command": "local-extension.landscape.disable-ai", + "title": "Remove this landscape as the default for Joule", + "icon": "$(sparkle-filled)" } ], "configuration": [ @@ -191,8 +197,12 @@ ], "commandPalette": [ { - "when": "true", - "command": "local-extension.landscape.set-ai-purpose" + "when": "false", + "command": "local-extension.landscape.enable-ai" + }, + { + "when": "false", + "command": "local-extension.landscape.disable-ai" }, { "when": "true", @@ -318,7 +328,17 @@ }, { "command": "local-extension.login", - "when": "view == dev-spaces && viewItem =~ /.*log-out.*/", + "when": "view == dev-spaces && viewItem =~ /.*landscape-log-out.*/", + "group": "inline" + }, + { + "command": "local-extension.landscape.enable-ai", + "when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-disabled.*/", + "group": "inline" + }, + { + "command": "local-extension.landscape.disable-ai", + "when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-enabled.*/", "group": "inline" } ] diff --git a/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts b/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts index cb93fdc4..b937aada 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts @@ -6,8 +6,10 @@ export const messages = { `Could not find an icon named '${iconName}'. Make sure you imported the matching file.`, lbl_logged_in: `Logged in`, lbl_not_logged_in: `Not logged in`, - lbl_landscape_context_status: (isLoggedIn: boolean) => - `landscape-${isLoggedIn ? "log-in" : "log-out"}`, + lbl_landscape_context_status: (isLoggedIn: boolean, isAiEnabled?: boolean) => + `landscape-log-${isLoggedIn ? "in" : "out"}-ai-${ + isAiEnabled ? "enabled" : "disabled" + }`, lbl_devspace_status_runnig: `running`, lbl_devspace_status_not_runnig: `not_running`, lbl_devspace_status_error: `error`, @@ -22,6 +24,7 @@ export const messages = { ` Are you sure you want to delete the '${label}' (${id}) dev space?`, lbl_yes: `Yes`, lbl_no: `No`, + lbl_ai_enabled: `Default landscape for Joule`, err_incorrect_jwt: (url: string) => `Incorrect token recieved for ${url}. Login failed.`, diff --git a/packages/app-studio-toolkit/src/devspace-manager/instance.ts b/packages/app-studio-toolkit/src/devspace-manager/instance.ts index 7c7e117c..f5a02552 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/instance.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/instance.ts @@ -19,9 +19,11 @@ import { cmdLoginToLandscape } from "./landscape/landscape"; import { getBasUriHandler } from "./handler/basHandler"; import { cmdOpenInVSCode } from "./devspace/open"; import { + clearAiLandscape, sendRequest, setLandscapeForAiPurpose, } from "../public-api/outbound-connectivity"; +import { LandscapeNode } from "./tree/treeItems"; export function initBasRemoteExplorer(context: ExtensionContext): void { context.subscriptions.push( @@ -35,8 +37,20 @@ export function initBasRemoteExplorer(context: ExtensionContext): void { /* istanbul ignore next */ context.subscriptions.push( commands.registerCommand( - "local-extension.landscape.set-ai-purpose", - setLandscapeForAiPurpose + "local-extension.landscape.enable-ai", + async (node: LandscapeNode): Promise => { + return setLandscapeForAiPurpose(node.url); + } + ) + ); + + context.subscriptions.push( + commands.registerCommand( + "local-extension.landscape.disable-ai", + async (node: LandscapeNode): Promise => { + await clearAiLandscape(); + void commands.executeCommand("local-extension.tree.refresh"); + } ) ); diff --git a/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts b/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts index 79c0844a..5cb86d33 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/landscape/landscape.ts @@ -38,6 +38,7 @@ export interface LandscapeInfo { name: string; url: string; isLoggedIn: boolean; + ai?: boolean; } export type LandscapeConfig = { url: string; ai?: boolean }; @@ -91,11 +92,16 @@ export async function getLandscapes(): Promise { const lands: LandscapeInfo[] = []; for (const landscape of getLanscapesConfig()) { const url = new URL(landscape.url); - lands.push({ - name: url.hostname, - url: url.toString(), - isLoggedIn: await isLandscapeLoggedIn(landscape.url), - }); + lands.push( + Object.assign( + { + name: url.hostname, + url: url.toString(), + isLoggedIn: await isLandscapeLoggedIn(landscape.url), + }, + landscape.ai ? { ai: landscape.ai } : {} + ) + ); } return lands; } diff --git a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts index ea4968cb..0d3f72f8 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts @@ -53,18 +53,25 @@ export class DevSpaceDataProvider implements TreeDataProvider { const landscapes = await getLandscapes(); const rootNodes = map(landscapes, (landscape) => { + let tooltip = landscape.isLoggedIn + ? messages.lbl_logged_in + : messages.lbl_not_logged_in; + if (landscape.ai) { + tooltip += `, ${messages.lbl_ai_enabled}`; + } return new LandscapeNode( this.extensionPath, landscape.name, TreeItemCollapsibleState.Expanded, iconPath, "", - landscape.isLoggedIn - ? messages.lbl_logged_in - : messages.lbl_not_logged_in, + tooltip, landscape.name, landscape.url, - messages.lbl_landscape_context_status(landscape.isLoggedIn) + messages.lbl_landscape_context_status( + landscape.isLoggedIn, + landscape.ai + ) ); }); diff --git a/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts index d548d292..21bd1165 100644 --- a/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts +++ b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts @@ -7,6 +7,7 @@ import { window, } from "vscode"; import { + LandscapeConfig, LandscapeInfo, getLandscapes, getLanscapesConfig, @@ -17,28 +18,39 @@ import { BasRemoteAuthenticationProvider } from "../authentication/authProvider" import { hasJwt } from "../authentication/auth-utils"; import { isEmpty } from "lodash"; -const LBL_ADD_LANDSCAPE = "Add landscape"; +const LBL_ADD_LANDSCAPE = "Add another landscape"; function getAiLandscape(): string { return getLanscapesConfig().find((landscape) => landscape.ai)?.url ?? ""; } -async function setAiLandscape(value: string): Promise { +export async function clearAiLandscape( + update = true +): Promise { const configs = getLanscapesConfig(); // reset ai flag for all landscapes if exists configs.forEach((landscape) => { delete landscape.ai; }); + update && (await updateLandscapesConfig(configs)); + return configs; +} + +async function setAiLandscape(landscapeUrl: string): Promise { + const configs = await clearAiLandscape(false); // update landscape if it exists in the list or add it - const index = configs.findIndex((landscape) => landscape.url === value); + const index = configs.findIndex( + (landscape) => landscape.url === landscapeUrl + ); if (index != -1) { // exists configs[index].ai = true; } else { // not exists : add the landscape to the list - configs.push({ url: value, ai: true }); + configs.push({ url: landscapeUrl, ai: true }); } - return updateLandscapesConfig(configs); + await updateLandscapesConfig(configs); + void commands.executeCommand("local-extension.tree.refresh"); } function selectLandscape( @@ -52,26 +64,31 @@ function selectLandscape( items.push({ label: "Initiate", kind: QuickPickItemKind.Separator }); // action section separator items.push({ label: LBL_ADD_LANDSCAPE }); return window.showQuickPick(items, { - placeHolder: "Choose the landscape to use for AI purposes", + placeHolder: "Select the landscape in which you want to use Joule", ignoreFocusOut: true, }) as Promise; } -export async function setLandscapeForAiPurpose(): Promise { - const outboundLandscape = getAiLandscape(); +export async function setLandscapeForAiPurpose( + landscape?: string +): Promise { // select landscape for outbound connectivity let selectedLandscape: QuickPickItem | undefined; - do { - // remove selected ai landscape from the list - const landscapes = (await getLandscapes()).filter( - (item) => item.url !== outboundLandscape - ); - selectedLandscape = await selectLandscape(landscapes); - if (selectedLandscape?.label === LBL_ADD_LANDSCAPE) { - await commands.executeCommand("local-extension.landscape.add"); - } - } while (selectedLandscape?.label === LBL_ADD_LANDSCAPE); - + if (landscape) { + selectedLandscape = { url: landscape } as any; + } else { + const outboundLandscape = getAiLandscape(); + do { + // remove selected ai landscape from the list + const landscapes = (await getLandscapes()).filter( + (item) => item.url !== outboundLandscape + ); + selectedLandscape = await selectLandscape(landscapes); + if (selectedLandscape?.label === LBL_ADD_LANDSCAPE) { + await commands.executeCommand("local-extension.landscape.add"); + } + } while (selectedLandscape?.label === LBL_ADD_LANDSCAPE); + } if (selectedLandscape) { await setAiLandscape((selectedLandscape as any).url); } From afb5030f431dd95702653fe597c866a402486f69 Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Wed, 19 Jun 2024 14:31:05 +0300 Subject: [PATCH 3/6] refactor: grouping inline actions, different icon for ai landscape, add landscape url validation --- packages/app-studio-toolkit/package.json | 6 +++--- .../src/devspace-manager/landscape/set.ts | 5 ++++- .../src/devspace-manager/tree/devSpacesProvider.ts | 6 ++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/app-studio-toolkit/package.json b/packages/app-studio-toolkit/package.json index d7e61536..509cd319 100644 --- a/packages/app-studio-toolkit/package.json +++ b/packages/app-studio-toolkit/package.json @@ -329,17 +329,17 @@ { "command": "local-extension.login", "when": "view == dev-spaces && viewItem =~ /.*landscape-log-out.*/", - "group": "inline" + "group": "inline@2" }, { "command": "local-extension.landscape.enable-ai", "when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-disabled.*/", - "group": "inline" + "group": "inline@1" }, { "command": "local-extension.landscape.disable-ai", "when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-enabled.*/", - "group": "inline" + "group": "inline@1" } ] }, diff --git a/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts b/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts index 85cd9bd4..debf66b4 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts @@ -9,7 +9,10 @@ export async function cmdLandscapeSet(): Promise { ignoreFocusOut: true, validateInput: (value: string) => { try { - new URL(value); + const url = new URL(value); + if (url.pathname.length > 1 || url.search || url.hash) { + return "Enter the URL origin without any paths or parameters"; + } } catch (e) { return (e as Error).toString(); } diff --git a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts index 0d3f72f8..e5b133da 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts @@ -49,7 +49,6 @@ export class DevSpaceDataProvider implements TreeDataProvider { } private async getTreeTopLevelChildren(): Promise> { - const iconPath = getSvgIconPath(this.extensionPath, "landscape"); const landscapes = await getLandscapes(); const rootNodes = map(landscapes, (landscape) => { @@ -63,7 +62,10 @@ export class DevSpaceDataProvider implements TreeDataProvider { this.extensionPath, landscape.name, TreeItemCollapsibleState.Expanded, - iconPath, + getSvgIconPath( + this.extensionPath, + `landscape${landscape.ai ? "_ai" : ""}` + ), "", tooltip, landscape.name, From a070162baef4403b8a37428b9145558f96c31ddf Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Fri, 21 Jun 2024 16:54:45 +0300 Subject: [PATCH 4/6] refactor: landscape-ai icon added --- .../resources/common/dark/land-ai.svg | 14 ++++++++++++++ .../resources/common/light/land-ai.svg | 14 ++++++++++++++ .../src/devspace-manager/tree/treeItems.ts | 1 + 3 files changed, 29 insertions(+) create mode 100644 packages/app-studio-toolkit/resources/common/dark/land-ai.svg create mode 100644 packages/app-studio-toolkit/resources/common/light/land-ai.svg diff --git a/packages/app-studio-toolkit/resources/common/dark/land-ai.svg b/packages/app-studio-toolkit/resources/common/dark/land-ai.svg new file mode 100644 index 00000000..09ed9e75 --- /dev/null +++ b/packages/app-studio-toolkit/resources/common/dark/land-ai.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/app-studio-toolkit/resources/common/light/land-ai.svg b/packages/app-studio-toolkit/resources/common/light/land-ai.svg new file mode 100644 index 00000000..f65d8327 --- /dev/null +++ b/packages/app-studio-toolkit/resources/common/light/land-ai.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/app-studio-toolkit/src/devspace-manager/tree/treeItems.ts b/packages/app-studio-toolkit/src/devspace-manager/tree/treeItems.ts index b865ae3f..3ad829c3 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/tree/treeItems.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/tree/treeItems.ts @@ -17,6 +17,7 @@ export function getSvgIconPath( ): IconPath { const icons = { landscape: { path: "common", name: "land.svg" }, + landscape_ai: { path: "common", name: "land-ai.svg" }, basic_error: { path: "devspace", name: "basic_error.svg" }, basic_running: { path: "devspace", name: "basic_running.svg" }, basic_not_running: { path: "devspace", name: "basic_not_running.svg" }, From deb97e39f0892cd9af46a9dfd8a967f7ac177a74 Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Sun, 23 Jun 2024 14:11:04 +0300 Subject: [PATCH 5/6] refactor: review adjustments --- .../src/devspace-manager/common/messages.ts | 6 +++--- .../src/devspace-manager/tree/devSpacesProvider.ts | 8 +++----- .../src/public-api/outbound-connectivity.ts | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts b/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts index b937aada..cb558a31 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/common/messages.ts @@ -4,8 +4,8 @@ export const messages = { lbl_dev_space_explorer_loading: `Loading...`, lbl_icon_missing: (iconName: string): string => `Could not find an icon named '${iconName}'. Make sure you imported the matching file.`, - lbl_logged_in: `Logged in`, - lbl_not_logged_in: `Not logged in`, + lbl_logged_in: `Logged in.`, + lbl_not_logged_in: `Not logged in.`, lbl_landscape_context_status: (isLoggedIn: boolean, isAiEnabled?: boolean) => `landscape-log-${isLoggedIn ? "in" : "out"}-ai-${ isAiEnabled ? "enabled" : "disabled" @@ -24,7 +24,7 @@ export const messages = { ` Are you sure you want to delete the '${label}' (${id}) dev space?`, lbl_yes: `Yes`, lbl_no: `No`, - lbl_ai_enabled: `Default landscape for Joule`, + lbl_ai_enabled: `Default landscape for Joule.`, err_incorrect_jwt: (url: string) => `Incorrect token recieved for ${url}. Login failed.`, diff --git a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts index e5b133da..67adffcb 100644 --- a/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts +++ b/packages/app-studio-toolkit/src/devspace-manager/tree/devSpacesProvider.ts @@ -52,12 +52,10 @@ export class DevSpaceDataProvider implements TreeDataProvider { const landscapes = await getLandscapes(); const rootNodes = map(landscapes, (landscape) => { - let tooltip = landscape.isLoggedIn + const tooltip = landscape.isLoggedIn ? messages.lbl_logged_in : messages.lbl_not_logged_in; - if (landscape.ai) { - tooltip += `, ${messages.lbl_ai_enabled}`; - } + return new LandscapeNode( this.extensionPath, landscape.name, @@ -67,7 +65,7 @@ export class DevSpaceDataProvider implements TreeDataProvider { `landscape${landscape.ai ? "_ai" : ""}` ), "", - tooltip, + landscape.ai ? `${tooltip} ${messages.lbl_ai_enabled}` : tooltip, landscape.name, landscape.url, messages.lbl_landscape_context_status( diff --git a/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts index 21bd1165..9a933645 100644 --- a/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts +++ b/packages/app-studio-toolkit/src/public-api/outbound-connectivity.ts @@ -60,8 +60,8 @@ function selectLandscape( url: landscape.url, label: landscape.name, })); - items.unshift({ label: "Available", kind: QuickPickItemKind.Separator }); // existing items section separator - items.push({ label: "Initiate", kind: QuickPickItemKind.Separator }); // action section separator + items.unshift({ label: "", kind: QuickPickItemKind.Separator }); // existing items section separator + items.push({ label: "", kind: QuickPickItemKind.Separator }); // action section separator items.push({ label: LBL_ADD_LANDSCAPE }); return window.showQuickPick(items, { placeHolder: "Select the landscape in which you want to use Joule", From 1ed093f71ecadc1d1ed26a1239ad0d28a987cb32 Mon Sep 17 00:00:00 2001 From: Alexander Gilin Date: Tue, 25 Jun 2024 16:08:52 +0300 Subject: [PATCH 6/6] refactor: joule inline icons fit --- packages/app-studio-toolkit/package.json | 10 ++++++++-- .../app-studio-toolkit/resources/common/joule-full.svg | 3 +++ packages/app-studio-toolkit/resources/common/joule.svg | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 packages/app-studio-toolkit/resources/common/joule-full.svg create mode 100644 packages/app-studio-toolkit/resources/common/joule.svg diff --git a/packages/app-studio-toolkit/package.json b/packages/app-studio-toolkit/package.json index 509cd319..ca0736cf 100644 --- a/packages/app-studio-toolkit/package.json +++ b/packages/app-studio-toolkit/package.json @@ -127,12 +127,18 @@ { "command": "local-extension.landscape.enable-ai", "title": "Make this the default landscape for Joule", - "icon": "$(sparkle)" + "icon": { + "light": "resources/common/joule.svg", + "dark": "resources/common/joule.svg" + } }, { "command": "local-extension.landscape.disable-ai", "title": "Remove this landscape as the default for Joule", - "icon": "$(sparkle-filled)" + "icon": { + "light": "resources/common/joule-full.svg", + "dark": "resources/common/joule-full.svg" + } } ], "configuration": [ diff --git a/packages/app-studio-toolkit/resources/common/joule-full.svg b/packages/app-studio-toolkit/resources/common/joule-full.svg new file mode 100644 index 00000000..68557c87 --- /dev/null +++ b/packages/app-studio-toolkit/resources/common/joule-full.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/app-studio-toolkit/resources/common/joule.svg b/packages/app-studio-toolkit/resources/common/joule.svg new file mode 100644 index 00000000..df94ab71 --- /dev/null +++ b/packages/app-studio-toolkit/resources/common/joule.svg @@ -0,0 +1,3 @@ + + +