diff --git a/src/kiota/Rpc/IServer.cs b/src/kiota/Rpc/IServer.cs index de7a284c0a..2ec90beb7a 100644 --- a/src/kiota/Rpc/IServer.cs +++ b/src/kiota/Rpc/IServer.cs @@ -14,4 +14,6 @@ internal interface IServer Task InfoForDescriptionAsync(string descriptionPath, bool clearCache, CancellationToken cancellationToken); Task> GeneratePluginAsync(string openAPIFilePath, string outputPath, PluginType[] pluginTypes, string[] includePatterns, string[] excludePatterns, string clientClassName, bool cleanOutput, bool clearCache, string[] disabledValidationRules, ConsumerOperation operation, CancellationToken cancellationToken); Task> MigrateFromLockFileAsync(string lockDirectoryPath, CancellationToken cancellationToken); + Task> RemoveClientAsync(string clientName, bool cleanOutput, CancellationToken cancellationToken); + Task> RemovePluginAsync(string pluginName, bool cleanOutput, CancellationToken cancellationToken); } diff --git a/src/kiota/Rpc/Server.cs b/src/kiota/Rpc/Server.cs index e866eab942..b60155710b 100644 --- a/src/kiota/Rpc/Server.cs +++ b/src/kiota/Rpc/Server.cs @@ -299,4 +299,29 @@ private static string NormalizeSlashesInPath(string path) return path.Replace('/', '\\'); return path.Replace('\\', '/'); } + + public Task> RemoveClientAsync(string clientName, bool cleanOutput, CancellationToken cancellationToken) + => RemoveClientOrPluginAsync(clientName, cleanOutput, "Client", (workspaceManagementService, clientName, cleanOutput, cancellationToken) => workspaceManagementService.RemoveClientAsync(clientName, cleanOutput, cancellationToken), cancellationToken); + + private static async Task> RemoveClientOrPluginAsync(string clientName, bool cleanOutput, string typeName, Func removal, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(clientName); + ArgumentException.ThrowIfNullOrEmpty(typeName); + ArgumentNullException.ThrowIfNull(removal); + var logger = new ForwardedLogger(); + try + { + var workspaceManagementService = new WorkspaceManagementService(logger, httpClient, IsConfigPreviewEnabled.Value); + await removal(workspaceManagementService, clientName, cleanOutput, cancellationToken).ConfigureAwait(false); + logger.LogInformation("{TypeName} {ClientName} removed successfully!", typeName, clientName); + } + catch (Exception ex) + { + logger.LogCritical(ex, "error removing the {TypeName}: {ExceptionMessage}", typeName.ToLowerInvariant(), ex.Message); + } + return logger.LogEntries; + } + + public async Task> RemovePluginAsync(string pluginName, bool cleanOutput, CancellationToken cancellationToken) + => await RemoveClientOrPluginAsync(pluginName, cleanOutput, "Plugin", (workspaceManagementService, pluginName, cleanOutput, cancellationToken) => workspaceManagementService.RemovePluginAsync(pluginName, cleanOutput, cancellationToken), cancellationToken); } diff --git a/vscode/microsoft-kiota/package-lock.json b/vscode/microsoft-kiota/package-lock.json index 4cc68c294d..12305789f0 100644 --- a/vscode/microsoft-kiota/package-lock.json +++ b/vscode/microsoft-kiota/package-lock.json @@ -1,12 +1,12 @@ { "name": "kiota", - "version": "1.18.100000001", + "version": "1.21.100000001", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kiota", - "version": "1.18.100000001", + "version": "1.21.100000001", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/vscode/microsoft-kiota/package.json b/vscode/microsoft-kiota/package.json index 789703dfe9..266efb9622 100644 --- a/vscode/microsoft-kiota/package.json +++ b/vscode/microsoft-kiota/package.json @@ -3,8 +3,8 @@ "displayName": "Microsoft Kiota", "publisher": "ms-graph", "description": "Client generator for HTTP REST APIs described by OpenAPI which helps eliminate the need to take a dependency on a different API client for every API that you need to call, as well as limiting the generation to the exact API surface area you're interested in, thanks to a filtering capability.", - "version": "1.18.100000001", - "kiotaVersion": "1.18.0", + "version": "1.21.100000001", + "kiotaVersion": "1.21.0", "telemetryInstrumentationKey": "4c6357e0-daf9-42b5-bdfb-67878f8957b5", "icon": "images/logo.png", "engines": { @@ -217,12 +217,14 @@ "views": { "kiota-openapi-explorer": [ { - "id": "kiota.openApiExplorer", - "name": "%kiota.openApiExplorer.name%" + "id": "kiota.workspace", + "name": "%kiota.workspace.name%", + "order": 1 }, { - "id": "kiota.workspace", - "name": "%kiota.workspace.name%" + "id": "kiota.openApiExplorer", + "name": "%kiota.openApiExplorer.name%", + "order": 2 } ], "kiota-dependencies-info": [ @@ -301,6 +303,16 @@ "command": "kiota.openApiExplorer.removeAllFromSelectedEndpoints", "when": "view == kiota.openApiExplorer && viewItem != clientNameOrPluginName", "group": "inline@5" + }, + { + "command": "kiota.workspace.selectItem", + "when": "viewItem == item", + "group": "inline@1" + }, + { + "command": "kiota.workspace.deleteItem", + "when": "viewItem == item", + "group": "inline@2" } ], "commandPalette": [ @@ -437,6 +449,18 @@ { "command": "kiota.migrateFromLockFile", "title": "%kiota.migrateClients.title%" + }, + { + "command": "kiota.workspace.selectItem", + "title": "%kiota.openApiExplorer.editPaths.title%", + "category": "Kiota", + "icon": "$(bracket)" + }, + { + "command": "kiota.workspace.deleteItem", + "title": "%kiota.openApiExplorer.removeFromSelectedEndpoints.title%", + "category": "Kiota", + "icon": "$(trash)" } ], "languages": [ diff --git a/vscode/microsoft-kiota/package.nls.json b/vscode/microsoft-kiota/package.nls.json index 5c7baf619a..d1c0f02166 100644 --- a/vscode/microsoft-kiota/package.nls.json +++ b/vscode/microsoft-kiota/package.nls.json @@ -29,7 +29,7 @@ "kiota.openApiExplorer.openFile.title": "Open file", "kiota.workspace.name": "My Workspace", "kiota.openApiExplorer.regenerateButton.title": "Re-generate", - "kiota.openApiExplorer.editPaths.title": "Edit paths", + "kiota.openApiExplorer.editPaths.title": "Select", "kiota.openApiExplorer.refresh.title": "Refresh", "kiota.migrateClients.title": "Migrate API clients" } diff --git a/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/deleteWorkspaceItemCommand.ts b/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/deleteWorkspaceItemCommand.ts new file mode 100644 index 0000000000..bc6dd91a41 --- /dev/null +++ b/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/deleteWorkspaceItemCommand.ts @@ -0,0 +1,80 @@ +import TelemetryReporter from "@vscode/extension-telemetry"; +import * as vscode from "vscode"; + +import { extensionId } from "../../constants"; +import { getLogEntriesForLevel, KiotaLogEntry, LogLevel } from "../../kiotaInterop"; +import { WorkspaceTreeItem } from "../../providers/workspaceTreeProvider"; +import { isPluginType } from "../../util"; +import { exportLogsAndShowErrors } from "../../utilities/logging"; +import { Command } from "../Command"; +import { removeClient, removePlugin } from "./removeItem"; + +export class DeleteWorkspaceItemCommand extends Command { + constructor( + private _context: vscode.ExtensionContext, + private _kiotaOutputChannel: vscode.LogOutputChannel + ) { + super(); + } + + public getName(): string { + return `${extensionId}.workspace.deleteItem`; + } + + public async execute(workspaceTreeItem: WorkspaceTreeItem): Promise { + const type = workspaceTreeItem.category && isPluginType(workspaceTreeItem.category) ? "plugin" : "client"; + const yesAnswer: vscode.MessageItem = { title: vscode.l10n.t("Yes") }; + const noAnswer: vscode.MessageItem = { title: vscode.l10n.t("No") }; + + const response = await vscode.window.showWarningMessage( + vscode.l10n.t("Do you want to delete this item?"), + yesAnswer, + noAnswer + ); + + if (response?.title === yesAnswer.title) { + const result = await this.deleteItem(type, workspaceTreeItem); + if (result) { + const isSuccess = result.some(k => k.message.includes('removed successfully')); + if (isSuccess) { + void vscode.window.showInformationMessage(vscode.l10n.t('{0} removed successfully.', workspaceTreeItem.label)); + await vscode.commands.executeCommand('kiota.workspace.refresh'); + } else { + await exportLogsAndShowErrors(result, this._kiotaOutputChannel); + } + } + } + } + + private async deleteItem(type: string, workspaceTreeItem: WorkspaceTreeItem): Promise { + const itemName = workspaceTreeItem.label; + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + cancellable: false, + title: vscode.l10n.t(`Removing ${type}...`) + }, async (progress, _) => { + const start = performance.now(); + const result = type === "plugin" ? await removePlugin( + this._context, + itemName, + false, + ) : await removeClient( + this._context, + itemName, + false, + ); + const duration = performance.now() - start; + const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0; + const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey); + reporter.sendRawTelemetryEvent(`${extensionId}.remove${type}.completed`, { + "pluginType": itemName, + "errorsCount": errorsCount.toString(), + }, { + "duration": duration, + }); + return result; + }); + return result; + } +} + diff --git a/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/removeItem.ts b/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/removeItem.ts new file mode 100644 index 0000000000..8b906d30e9 --- /dev/null +++ b/vscode/microsoft-kiota/src/commands/deleteWorkspaceItem/removeItem.ts @@ -0,0 +1,32 @@ +import * as vscode from "vscode"; +import * as rpc from "vscode-jsonrpc/node"; + +import { connectToKiota, KiotaLogEntry } from "../../kiotaInterop"; + +export function removePlugin(context: vscode.ExtensionContext, pluginName: string, cleanOutput: boolean): Promise { + return connectToKiota(context, async (connection) => { + const request = new rpc.RequestType2( + "RemovePlugin" + ); + const result = await connection.sendRequest( + request, + pluginName, + cleanOutput + ); + return result; + }); +}; + +export function removeClient(context: vscode.ExtensionContext, clientName: string, cleanOutput: boolean): Promise { + return connectToKiota(context, async (connection) => { + const request = new rpc.RequestType2( + "RemoveClient" + ); + const result = await connection.sendRequest( + request, + clientName, + cleanOutput + ); + return result; + }); +}; \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/commands/editPathsCommand.ts b/vscode/microsoft-kiota/src/commands/editPathsCommand.ts index 311215a8ab..2775d21d86 100644 --- a/vscode/microsoft-kiota/src/commands/editPathsCommand.ts +++ b/vscode/microsoft-kiota/src/commands/editPathsCommand.ts @@ -1,3 +1,5 @@ +import * as vscode from "vscode"; + import { extensionId, treeViewId } from "../constants"; import { ClientOrPluginProperties } from "../kiotaInterop"; import { OpenApiTreeProvider } from "../providers/openApiTreeProvider"; @@ -8,11 +10,8 @@ import { Command } from "./Command"; export class EditPathsCommand extends Command { - private _openApiTreeProvider: OpenApiTreeProvider; - - public constructor(openApiTreeProvider: OpenApiTreeProvider) { + public constructor(private _openApiTreeProvider: OpenApiTreeProvider) { super(); - this._openApiTreeProvider = openApiTreeProvider; } public getName(): string { @@ -23,6 +22,7 @@ export class EditPathsCommand extends Command { await this.loadEditPaths(clientOrPluginKey!, clientOrPluginObject!); this._openApiTreeProvider.resetInitialState(); await updateTreeViewIcons(treeViewId, false, true); + await vscode.commands.executeCommand('kiota.workspace.refresh'); } private async loadEditPaths(clientOrPluginKey: string, clientOrPluginObject: ClientOrPluginProperties) { diff --git a/vscode/microsoft-kiota/src/extension.ts b/vscode/microsoft-kiota/src/extension.ts index d681521038..748f13d760 100644 --- a/vscode/microsoft-kiota/src/extension.ts +++ b/vscode/microsoft-kiota/src/extension.ts @@ -4,6 +4,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import * as vscode from "vscode"; import { CloseDescriptionCommand } from './commands/closeDescriptionCommand'; +import { DeleteWorkspaceItemCommand } from './commands/deleteWorkspaceItem/deleteWorkspaceItemCommand'; import { EditPathsCommand } from './commands/editPathsCommand'; import { GenerateClientCommand } from './commands/generate/generateClientCommand'; import { displayGenerationResults } from './commands/generate/generation-util'; @@ -27,10 +28,12 @@ import { UriHandler } from './handlers/uriHandler'; import { ClientOrPluginProperties } from "./kiotaInterop"; +import { WorkspaceContentService } from './modules/workspace'; import { CodeLensProvider } from './providers/codelensProvider'; import { DependenciesViewProvider } from "./providers/dependenciesViewProvider"; import { OpenApiTreeNode, OpenApiTreeProvider } from "./providers/openApiTreeProvider"; -import { loadTreeView } from './providers/workspaceTreeProvider'; +import { SharedService } from './providers/sharedService'; +import { loadTreeView, WorkspaceTreeItem, WorkspaceTreeProvider } from './providers/workspaceTreeProvider'; import { getExtensionSettings } from "./types/extensionSettings"; import { GeneratedOutputState } from './types/GeneratedOutputState'; import { WorkspaceGenerationContext } from "./types/WorkspaceGenerationContext"; @@ -50,11 +53,14 @@ export async function activate( kiotaOutputChannel = vscode.window.createOutputChannel("Kiota", { log: true, }); - const openApiTreeProvider = new OpenApiTreeProvider(context, () => getExtensionSettings(extensionId)); + const sharedService = SharedService.getInstance(); + const workspaceContentService = new WorkspaceContentService(); + const openApiTreeProvider = new OpenApiTreeProvider(context, () => getExtensionSettings(extensionId), sharedService); const dependenciesInfoProvider = new DependenciesViewProvider( context.extensionUri ); const reporter = new TelemetryReporter(context.extension.packageJSON.telemetryInstrumentationKey); + const workspaceTreeProvider = new WorkspaceTreeProvider(workspaceContentService, sharedService); const setWorkspaceGenerationContext = (params: Partial) => { workspaceGenerationContext = { ...workspaceGenerationContext, ...params }; @@ -76,9 +82,10 @@ export async function activate( const closeDescriptionCommand = new CloseDescriptionCommand(openApiTreeProvider); const statusCommand = new StatusCommand(); const selectLockCommand = new SelectLockCommand(openApiTreeProvider); + const deleteWorkspaceItemCommand = new DeleteWorkspaceItemCommand(context, kiotaOutputChannel); const updateClientsCommand = new UpdateClientsCommand(context, kiotaOutputChannel); - await loadTreeView(context); + await loadTreeView(context, workspaceTreeProvider); await checkForLockFileAndPrompt(context); let codeLensProvider = new CodeLensProvider(); context.subscriptions.push( @@ -125,6 +132,8 @@ export async function activate( await regenerateCommand.execute({ clientOrPluginKey, clientOrPluginObject, generationType }); }), registerCommandWithTelemetry(reporter, migrateFromLockFileCommand.getName(), async (uri: vscode.Uri) => await migrateFromLockFileCommand.execute(uri)), + registerCommandWithTelemetry(reporter, deleteWorkspaceItemCommand.getName(), async (workspaceTreeItem: WorkspaceTreeItem) => await deleteWorkspaceItemCommand.execute(workspaceTreeItem)), + ); // create a new status bar item that we can now manage diff --git a/vscode/microsoft-kiota/src/modules/workspace/index.ts b/vscode/microsoft-kiota/src/modules/workspace/index.ts new file mode 100644 index 0000000000..a7c863ab8e --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/workspace/index.ts @@ -0,0 +1,9 @@ +import { ClientOrPluginProperties } from "../../kiotaInterop"; +import WorkspaceContentService from "./workspaceContentService"; + +export interface WorkspaceContent { + version: string; + clients: Record; + plugins: Record; +} +export { WorkspaceContentService }; diff --git a/vscode/microsoft-kiota/src/modules/workspace/workspaceContentService.ts b/vscode/microsoft-kiota/src/modules/workspace/workspaceContentService.ts new file mode 100644 index 0000000000..a7997b2444 --- /dev/null +++ b/vscode/microsoft-kiota/src/modules/workspace/workspaceContentService.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; + +import { WorkspaceContent } from "."; +import { KIOTA_WORKSPACE_FILE } from "../../constants"; +import { getWorkspaceJsonPath } from '../../util'; + +class WorkspaceContentService { + constructor() { } + + public async load(): Promise { + const isWorkspacePresent = await this.isKiotaWorkspaceFilePresent(); + if (!isWorkspacePresent) { + return; + } + try { + const workspaceJson = vscode.workspace.textDocuments.find(doc => doc.fileName.endsWith(KIOTA_WORKSPACE_FILE)); + if (!workspaceJson) { + throw new Error('Workspace file not found'); + } + const content = workspaceJson.getText(); + return JSON.parse(content); + } catch (error) { + console.error('Error loading workspace.json:', error); + } + } + + async isKiotaWorkspaceFilePresent(): Promise { + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + return false; + } + const workspaceFileDir = path.resolve(getWorkspaceJsonPath()); + try { + await fs.promises.access(workspaceFileDir); + } catch (error) { + return false; + } + return true; + } +} + +export default WorkspaceContentService; \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/providers/openApiTreeProvider.ts b/vscode/microsoft-kiota/src/providers/openApiTreeProvider.ts index 26386edcb1..56622108b4 100644 --- a/vscode/microsoft-kiota/src/providers/openApiTreeProvider.ts +++ b/vscode/microsoft-kiota/src/providers/openApiTreeProvider.ts @@ -21,20 +21,24 @@ import { } from '../kiotaInterop'; import { ExtensionSettings } from '../types/extensionSettings'; import { updateTreeViewIcons } from '../util'; +import { SharedService } from './sharedService'; export class OpenApiTreeProvider implements vscode.TreeDataProvider { private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; public apiTitle?: string; private initialStateHash: string = ''; + constructor( private readonly context: vscode.ExtensionContext, private readonly settingsGetter: () => ExtensionSettings, + private readonly sharedService: SharedService, private _descriptionUrl?: string, public includeFilters: string[] = [], - public excludeFilters: string[] = []) { - + public excludeFilters: string[] = [], + ) { } + private _workspaceFilePath?: string; private _workspaceFile?: ConfigurationFile | Partial = {}; public get isWorkspaceFileLoaded(): boolean { @@ -332,6 +336,7 @@ export class OpenApiTreeProvider implements vscode.TreeDataProvider = new Map(); + + private constructor() { } + + public static getInstance(): SharedService { + if (!SharedService.instance) { + SharedService.instance = new SharedService(); + } + return SharedService.instance; + } + + public get(key: K): SharedState[K] | undefined { + return this.state.get(key); + } + + public set(key: K, value: SharedState[K]): void { + this.state.set(key, value); + } + + public clear(key: K): void { + this.state.delete(key); + } + + // Method to reset the singleton instance for testing + public static resetInstance(): void { + SharedService.instance = new SharedService(); + } +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/providers/workspaceTreeProvider.ts b/vscode/microsoft-kiota/src/providers/workspaceTreeProvider.ts index 0f7ec33fa7..70c9540f13 100644 --- a/vscode/microsoft-kiota/src/providers/workspaceTreeProvider.ts +++ b/vscode/microsoft-kiota/src/providers/workspaceTreeProvider.ts @@ -1,71 +1,142 @@ import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; - -import { KIOTA_WORKSPACE_FILE } from '../constants'; -import { getWorkspaceJsonPath } from '../util'; - -export class WorkspaceTreeProvider implements vscode.TreeDataProvider { - public isWSPresent: boolean; - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - constructor(isWSPresent: boolean) { - this.isWSPresent = isWSPresent; + +import { CLIENTS, KIOTA_WORKSPACE_FILE, PLUGINS } from '../constants'; +import { ClientOrPluginProperties } from '../kiotaInterop'; +import { WorkspaceContent, WorkspaceContentService } from '../modules/workspace'; +import { getWorkspaceJsonPath, isClientType, isPluginType } from '../util'; +import { SharedService } from './sharedService'; + +export class WorkspaceTreeItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly type: 'root' | 'category' | 'item' | 'info', + public readonly category?: string, + public readonly properties?: ClientOrPluginProperties, + public command?: vscode.Command + ) { + super(label, collapsibleState); + this.contextValue = type; + } +} + +export class WorkspaceTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private workspaceContent: WorkspaceContent | undefined = undefined; + + constructor( + private workspaceContentService: WorkspaceContentService, + private sharedService: SharedService + ) { + void this.loadContent(); } async refreshView(): Promise { + await this.loadContent(); this._onDidChangeTreeData.fire(); } - async getChildren(element?: vscode.TreeItem): Promise { - if (!this.isWSPresent) { + async loadContent(): Promise { + this.workspaceContent = await this.workspaceContentService.load(); + } + + async getChildren(element?: WorkspaceTreeItem): Promise { + if (!this.workspaceContent) { return []; } + if (!element) { - return [new vscode.TreeItem(KIOTA_WORKSPACE_FILE, vscode.TreeItemCollapsibleState.None)]; + const hasClients = this.workspaceContent?.clients && Object.keys(this.workspaceContent.clients).length > 0; + const hasPlugins = this.workspaceContent?.plugins && Object.keys(this.workspaceContent.plugins).length > 0; + const collapsibleState = (hasClients || hasPlugins) ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed; + return [ + new WorkspaceTreeItem(KIOTA_WORKSPACE_FILE, collapsibleState, 'root') + ]; + } + + if (this.workspaceContent) { + if (element.label === KIOTA_WORKSPACE_FILE) { + const children: WorkspaceTreeItem[] = []; + if (Object.keys(this.workspaceContent.clients).length > 0) { + children.push(new WorkspaceTreeItem(CLIENTS, vscode.TreeItemCollapsibleState.Expanded, 'category')); + } + if (Object.keys(this.workspaceContent.plugins).length > 0) { + children.push(new WorkspaceTreeItem(PLUGINS, vscode.TreeItemCollapsibleState.Expanded, 'category')); + } + if (children.length === 0) { + children.push(new WorkspaceTreeItem(vscode.l10n.t("No clients or plugins are available"), vscode.TreeItemCollapsibleState.None, 'info')); + } + return children; + } + + if (isClientType(element.label)) { + return Object.keys(this.workspaceContent.clients).map(clientName => + new WorkspaceTreeItem(clientName, vscode.TreeItemCollapsibleState.None, 'item', CLIENTS, this.getProperties(clientName, CLIENTS)) + ); + } + + if (isPluginType(element.label)) { + return Object.keys(this.workspaceContent.plugins).map(pluginName => + new WorkspaceTreeItem(pluginName, vscode.TreeItemCollapsibleState.None, 'item', PLUGINS, this.getProperties(pluginName, CLIENTS)) + ); + } } return []; } - getTreeItem(element: vscode.TreeItem): vscode.TreeItem { - if (element) { - element.command = { - command: 'kiota.workspace.openWorkspaceFile', - title: vscode.l10n.t("Open File"), - arguments: [vscode.Uri.file(getWorkspaceJsonPath())] - }; - element.contextValue = 'file'; + getProperties(name: string, category: string): ClientOrPluginProperties | undefined { + if (category && category === CLIENTS) { + return this.workspaceContent?.clients[name]; + } + return this.workspaceContent?.plugins[name]; + } + + getTreeItem(element: WorkspaceTreeItem): WorkspaceTreeItem { + if (!element) { + return element; + } + + switch (element.type) { + case 'root': + element.command = { + command: 'kiota.workspace.openWorkspaceFile', + title: vscode.l10n.t("Open File"), + arguments: [vscode.Uri.file(getWorkspaceJsonPath())] + }; + element.contextValue = 'folder'; + break; + + case 'item': + const key = element.label; + const clientOrPluginKey = this.sharedService.get('clientOrPluginKey'); + element.iconPath = (clientOrPluginKey && clientOrPluginKey === key) ? + new vscode.ThemeIcon('folder-opened') : + new vscode.ThemeIcon('folder'); + break; } return element; } + } async function openResource(resource: vscode.Uri): Promise { await vscode.window.showTextDocument(resource); } -async function isKiotaWorkspaceFilePresent(): Promise { - if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { - return false; - } - const workspaceFileDir = path.resolve(getWorkspaceJsonPath()); - try { - await fs.promises.access(workspaceFileDir); - } catch (error) { - return false; - } - return true; -} -export async function loadTreeView(context: vscode.ExtensionContext): Promise { - const treeDataProvider = new WorkspaceTreeProvider(await isKiotaWorkspaceFilePresent()); +export async function loadTreeView(context: vscode.ExtensionContext, treeDataProvider: WorkspaceTreeProvider): Promise { context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(async () => { - treeDataProvider.isWSPresent = await isKiotaWorkspaceFilePresent(); await vscode.commands.executeCommand('kiota.workspace.refresh'); // Refresh the tree view when workspace folders change })); context.subscriptions.push(vscode.window.createTreeView('kiota.workspace', { treeDataProvider })); context.subscriptions.push(vscode.commands.registerCommand('kiota.workspace.openWorkspaceFile', openResource)); context.subscriptions.push(vscode.commands.registerCommand('kiota.workspace.refresh', async () => { - treeDataProvider.isWSPresent = await isKiotaWorkspaceFilePresent(); await treeDataProvider.refreshView(); })); -} + context.subscriptions.push( + vscode.commands.registerCommand('kiota.workspace.selectItem', async (workspaceTreeItem: WorkspaceTreeItem) => { + const { label, properties, category } = workspaceTreeItem; + await vscode.commands.executeCommand('kiota.editPaths', label, properties, category); + }) + ); +}; \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/test/suite/commands/deleteWorkspaceItemCommand.test.ts b/vscode/microsoft-kiota/src/test/suite/commands/deleteWorkspaceItemCommand.test.ts new file mode 100644 index 0000000000..cc721a2566 --- /dev/null +++ b/vscode/microsoft-kiota/src/test/suite/commands/deleteWorkspaceItemCommand.test.ts @@ -0,0 +1,44 @@ +import assert from "assert"; +import * as sinon from "sinon"; +import * as vscode from 'vscode'; + +import { DeleteWorkspaceItemCommand } from '../../../commands/deleteWorkspaceItem/deleteWorkspaceItemCommand'; +import { WorkspaceTreeItem } from '../../../providers/workspaceTreeProvider'; + +suite('DeleteWorkspaceItemCommand Tests', () => { + let context: vscode.ExtensionContext; + let outputChannel: vscode.LogOutputChannel; + let command: DeleteWorkspaceItemCommand; + let workspaceTreeItem: WorkspaceTreeItem; + + setup(() => { + context = { extension: { packageJSON: { telemetryInstrumentationKey: 'test-key' } } } as any; + outputChannel = { appendLine: sinon.stub() } as any; + command = new DeleteWorkspaceItemCommand(context, outputChannel); + workspaceTreeItem = { label: 'test-item', category: 'plugin' } as any; + }); + + teardown(() => { + sinon.restore(); + }); + + test('getName should return correct command name', () => { + assert.strictEqual("kiota.workspace.deleteItem", command.getName()); + }); + + test('execute should show success message and refresh workspace on success', async () => { + const yesAnswer: vscode.MessageItem = { title: vscode.l10n.t("Yes") }; + + const showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').resolves(yesAnswer); + const showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').resolves(); + const executeCommandStub = sinon.stub(vscode.commands, 'executeCommand').resolves(); + const deleteItemStub = sinon.stub(command as any, 'deleteItem').resolves([{ message: 'removed successfully' }]); + + await command.execute(workspaceTreeItem); + + assert.strictEqual(showWarningMessageStub.calledOnce, true); + assert.strictEqual(showInformationMessageStub.calledOnce, true); + assert.strictEqual(executeCommandStub.calledWith('kiota.workspace.refresh'), true); + assert.strictEqual(deleteItemStub.calledOnce, true); + }); +}); \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/util.ts b/vscode/microsoft-kiota/src/util.ts index c61ab8fcb4..b07930e64d 100644 --- a/vscode/microsoft-kiota/src/util.ts +++ b/vscode/microsoft-kiota/src/util.ts @@ -171,3 +171,5 @@ export function isValidUrl(url: string): boolean { return false; } } + +