Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow access Joule from VS Code #319

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions packages/app-studio-toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"title": "Add landscape",
"icon": "$(add)"
},
{
"command": "local-extension.send-outbound-request",
"title": "Send outbound request"
},
{
"command": "local-extension.landscape.delete",
"title": "Remove landscape"
Expand Down Expand Up @@ -119,6 +123,22 @@
"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": {
"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": {
"light": "resources/common/joule-full.svg",
"dark": "resources/common/joule-full.svg"
}
}
],
"configuration": [
Expand Down Expand Up @@ -182,6 +202,18 @@
}
],
"commandPalette": [
{
"when": "false",
"command": "local-extension.landscape.enable-ai"
},
{
"when": "false",
"command": "local-extension.landscape.disable-ai"
},
{
"when": "true",
"command": "local-extension.send-outbound-request"
},
{
"when": "false",
"command": "local-extension.landscape.add"
Expand Down Expand Up @@ -302,8 +334,18 @@
},
{
"command": "local-extension.login",
"when": "view == dev-spaces && viewItem =~ /.*log-out.*/",
"group": "inline"
"when": "view == dev-spaces && viewItem =~ /.*landscape-log-out.*/",
"group": "inline@2"
},
{
"command": "local-extension.landscape.enable-ai",
"when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-disabled.*/",
"group": "inline@1"
},
{
"command": "local-extension.landscape.disable-ai",
"when": "view == dev-spaces && viewItem =~ /.*landscape-.*-ai-enabled.*/",
"group": "inline@1"
}
]
},
Expand Down
14 changes: 14 additions & 0 deletions packages/app-studio-toolkit/resources/common/dark/land-ai.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/app-studio-toolkit/resources/common/joule-full.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/app-studio-toolkit/resources/common/joule.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions packages/app-studio-toolkit/resources/common/light/land-ai.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ 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_landscape_context_status: (isLoggedIn: boolean) =>
`landscape-${isLoggedIn ? "log-in" : "log-out"}`,
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"
}`,
lbl_devspace_status_runnig: `running`,
lbl_devspace_status_not_runnig: `not_running`,
lbl_devspace_status_error: `error`,
Expand All @@ -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.`,
Expand Down
33 changes: 32 additions & 1 deletion packages/app-studio-toolkit/src/devspace-manager/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ 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 {
clearAiLandscape,
sendRequest,
setLandscapeForAiPurpose,
} from "../public-api/outbound-connectivity";
import { LandscapeNode } from "./tree/treeItems";

export function initBasRemoteExplorer(context: ExtensionContext): void {
context.subscriptions.push(
Expand All @@ -30,6 +35,32 @@ export function initBasRemoteExplorer(context: ExtensionContext): void {
const devSpaceExplorer = new DevSpacesExplorer(context.extensionPath);

/* istanbul ignore next */
context.subscriptions.push(
commands.registerCommand(
"local-extension.landscape.enable-ai",
async (node: LandscapeNode): Promise<boolean> => {
return setLandscapeForAiPurpose(node.url);
}
)
);

context.subscriptions.push(
commands.registerCommand(
"local-extension.landscape.disable-ai",
async (node: LandscapeNode): Promise<void> => {
await clearAiLandscape();
void commands.executeCommand("local-extension.tree.refresh");
}
)
);

context.subscriptions.push(
commands.registerCommand(
"local-extension.send-outbound-request",
sendRequest
)
);

context.subscriptions.push(
commands.registerCommand("local-extension.tree.refresh", () =>
devSpaceExplorer.refreshTree()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -38,47 +38,70 @@ export interface LandscapeInfo {
name: string;
url: string;
isLoggedIn: boolean;
ai?: boolean;
}

export type LandscapeConfig = { url: string; ai?: boolean };

function isLandscapeLoggedIn(url: string): Promise<boolean> {
return hasJwt(url);
}

export function getLanscapesConfig(): string[] {
return uniq(
export function getLanscapesConfig(): LandscapeConfig[] {
let config =
workspace.getConfiguration().get<string>("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<string>("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<void> {
export async function updateLandscapesConfig(
values: LandscapeConfig[]
): Promise<void> {
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<LandscapeInfo[]> {
const lands: LandscapeInfo[] = [];
for (const landscape of getLanscapesConfig()) {
const url = new URL(landscape);
lands.push({
name: url.hostname,
url: url.toString(),
isLoggedIn: await isLandscapeLoggedIn(landscape),
});
const url = new URL(landscape.url);
lands.push(
Object.assign(
{
name: url.hostname,
url: url.toString(),
isLoggedIn: await isLandscapeLoggedIn(landscape.url),
},
landscape.ai ? { ai: landscape.ai } : {}
)
);
}
return lands;
}
Expand All @@ -88,7 +111,7 @@ export async function removeLandscape(landscapeName: string): Promise<void> {
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);
Expand Down
17 changes: 14 additions & 3 deletions packages/app-studio-toolkit/src/devspace-manager/landscape/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,38 @@ export async function cmdLandscapeSet(): Promise<void> {
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();
}
},
});

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")
);
// }
}
}

export async function addLandscape(landscapeName: string): Promise<void> {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,29 @@ export class DevSpaceDataProvider implements TreeDataProvider<TreeItem> {
}

private async getTreeTopLevelChildren(): Promise<Thenable<TreeNode[]>> {
const iconPath = getSvgIconPath(this.extensionPath, "landscape");
const landscapes = await getLandscapes();

const rootNodes = map(landscapes, (landscape) => {
const tooltip = landscape.isLoggedIn
? messages.lbl_logged_in
: messages.lbl_not_logged_in;

return new LandscapeNode(
this.extensionPath,
landscape.name,
TreeItemCollapsibleState.Expanded,
iconPath,
getSvgIconPath(
this.extensionPath,
`landscape${landscape.ai ? "_ai" : ""}`
),
"",
landscape.isLoggedIn
? messages.lbl_logged_in
: messages.lbl_not_logged_in,
landscape.ai ? `${tooltip} ${messages.lbl_ai_enabled}` : tooltip,
landscape.name,
landscape.url,
messages.lbl_landscape_context_status(landscape.isLoggedIn)
messages.lbl_landscape_context_status(
landscape.isLoggedIn,
landscape.ai
)
);
});

Expand Down
Loading
Loading