diff --git a/vscode/microsoft-kiota/src/GeneratedOutputState.ts b/vscode/microsoft-kiota/src/GeneratedOutputState.ts new file mode 100644 index 0000000000..58bbf97446 --- /dev/null +++ b/vscode/microsoft-kiota/src/GeneratedOutputState.ts @@ -0,0 +1,4 @@ +export interface GeneratedOutputState { + outputPath: string; + clientClassName: string; +} diff --git a/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts new file mode 100644 index 0000000000..bdb6cf657d --- /dev/null +++ b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts @@ -0,0 +1,306 @@ +import TelemetryReporter from "@vscode/extension-telemetry"; +import * as path from "path"; +import * as vscode from "vscode"; + +import { extensionId, treeViewFocusCommand, treeViewId } from "../../constants"; +import { DependenciesViewProvider } from "../../dependenciesViewProvider"; +import { GenerationType, KiotaGenerationLanguage, KiotaPluginType } from "../../enums"; +import { ExtensionSettings, getExtensionSettings } from "../../extensionSettings"; +import { generateClient } from "../../generateClient"; +import { GeneratedOutputState } from "../../GeneratedOutputState"; +import { generatePlugin } from "../../generatePlugin"; +import { getLanguageInformation, getLanguageInformationForDescription } from "../../getLanguageInformation"; +import { setGenerationConfiguration } from "../../handlers/configurationHandler"; +import { clearDeepLinkParams, getDeepLinkParams } from "../../handlers/deepLinkParamsHandler"; +import { setWorkspaceGenerationType } from "../../handlers/workspaceGenerationTypeHandler"; +import { ConsumerOperation, generationLanguageToString, getLogEntriesForLevel, KiotaLogEntry, LogLevel } from "../../kiotaInterop"; +import { OpenApiTreeProvider } from "../../openApiTreeProvider"; +import { GenerateState, generateSteps } from "../../steps"; +import { getSanitizedString, getWorkspaceJsonDirectory, parseGenerationLanguage, parseGenerationType, parsePluginType, updateTreeViewIcons } from "../../util"; +import { isDeeplinkEnabled, transformToGenerationConfig } from "../../utilities/deep-linking"; +import { exportLogsAndShowErrors } from "../../utilities/logging"; +import { showUpgradeWarningMessage } from "../../utilities/messaging"; +import { Command } from "../Command"; +import { checkForSuccess, displayGenerationResults } from "./generation-util"; + +export class GenerateClientCommand extends Command { + private _openApiTreeProvider: OpenApiTreeProvider; + private _context: vscode.ExtensionContext; + private _dependenciesViewProvider: DependenciesViewProvider; + + constructor(openApiTreeProvider: OpenApiTreeProvider, context: vscode.ExtensionContext, dependenciesViewProvider: DependenciesViewProvider) { + super(); + this._openApiTreeProvider = openApiTreeProvider; + this._context = context; + this._dependenciesViewProvider = dependenciesViewProvider; + } + + public getName(): string { + return `${treeViewId}.generateClient`; + } + + public async execute(): Promise { + const deepLinkParams = getDeepLinkParams(); + const selectedPaths = this._openApiTreeProvider.getSelectedPaths(); + if (selectedPaths.length === 0) { + await vscode.window.showErrorMessage( + vscode.l10n.t("No endpoints selected, select endpoints first") + ); + return; + } + + let languagesInformation = await getLanguageInformation(this._context); + let availableStateInfo: Partial; + if (isDeeplinkEnabled(deepLinkParams)) { + if (!deepLinkParams.name && this._openApiTreeProvider.apiTitle) { + deepLinkParams.name = getSanitizedString(this._openApiTreeProvider.apiTitle); + } + availableStateInfo = transformToGenerationConfig(deepLinkParams); + } else { + const pluginName = getSanitizedString(this._openApiTreeProvider.apiTitle); + availableStateInfo = { + clientClassName: this._openApiTreeProvider.clientClassName, + clientNamespaceName: this._openApiTreeProvider.clientNamespaceName, + language: this._openApiTreeProvider.language, + outputPath: this._openApiTreeProvider.outputPath, + pluginName + }; + } + let config = await generateSteps( + availableStateInfo, + languagesInformation, + deepLinkParams + ); + setGenerationConfiguration(config); + const generationType = parseGenerationType(config.generationType); + const outputPath = typeof config.outputPath === "string" + ? config.outputPath + : "./output"; + await showUpgradeWarningMessage(outputPath, this._context); + if (!this._openApiTreeProvider.descriptionUrl) { + await vscode.window.showErrorMessage( + vscode.l10n.t("No description found, select a description first") + ); + return; + } + + const settings = getExtensionSettings(extensionId); + setWorkspaceGenerationType(config.generationType as string); + let result; + switch (generationType) { + case GenerationType.Client: + result = await this.generateClientAndRefreshUI(config, settings, outputPath, selectedPaths); + break; + case GenerationType.Plugin: + result = await this.generatePluginAndRefreshUI(config, settings, outputPath, selectedPaths); + break; + case GenerationType.ApiManifest: + result = await this.generateManifestAndRefreshUI(config, settings, outputPath, selectedPaths); + break; + default: + await vscode.window.showErrorMessage( + vscode.l10n.t("Invalid generation type") + ); + return; + } + if (result && getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length === 0) { + // Save state before opening the new window + const outputState = { + outputPath, + config, + clientClassName: config.clientClassName || config.pluginName + }; + void this._context.workspaceState.update('generatedOutput', outputState as GeneratedOutputState); + + const pathOfSpec = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-openapi.yml`); + const pathPluginManifest = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-apiplugin.json`); + if (deepLinkParams.source?.toLowerCase() === 'ttk') { + try { + await vscode.commands.executeCommand( + 'fx-extension.createprojectfromkiota', + [ + pathOfSpec, + pathPluginManifest, + deepLinkParams.ttkContext ? deepLinkParams.ttkContext : undefined + ] + ); + this._openApiTreeProvider.closeDescription(); + await updateTreeViewIcons(treeViewId, false); + } catch (error) { + const reporter = new TelemetryReporter(this._context.extension.packageJSON.telemetryInstrumentationKey); + reporter.sendTelemetryEvent("DeepLinked fx-extension.createprojectfromkiota", { + "error": JSON.stringify(error) + }); + } + } else { + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(config.workingDirectory ?? getWorkspaceJsonDirectory()), true); + } else { + await displayGenerationResults(this._openApiTreeProvider, config); + } + } + + clearDeepLinkParams(); // Clear the state after the generation + } + } + + private async generateManifestAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { + const pluginTypes = KiotaPluginType.ApiManifest; + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + cancellable: false, + title: vscode.l10n.t("Generating manifest...") + }, async (progress, _) => { + const start = performance.now(); + const result = await generatePlugin( + this._context, + this._openApiTreeProvider.descriptionUrl, + outputPath, + [pluginTypes], + selectedPaths, + [], + typeof config.pluginName === "string" + ? config.pluginName + : "ApiClient", + settings.clearCache, + settings.cleanOutput, + settings.disableValidationRules, + ConsumerOperation.Add, + config.workingDirectory + ); + 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}.generateManifest.completed`, { + "pluginType": pluginTypes.toString(), + "errorsCount": errorsCount.toString(), + }, { + "duration": duration, + }); + return result; + }); + if (result) { + const isSuccess = await checkForSuccess(result); + if (!isSuccess) { + await exportLogsAndShowErrors(result); + } + void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.')); + } + return result; + } + private async generatePluginAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { + const pluginTypes = Array.isArray(config.pluginTypes) ? parsePluginType(config.pluginTypes) : [KiotaPluginType.ApiPlugin]; + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + cancellable: false, + title: vscode.l10n.t("Generating plugin...") + }, async (progress, _) => { + const start = performance.now(); + const result = await generatePlugin( + this._context, + this._openApiTreeProvider.descriptionUrl, + outputPath, + pluginTypes, + selectedPaths, + [], + typeof config.pluginName === "string" + ? config.pluginName + : "ApiClient", + settings.clearCache, + settings.cleanOutput, + settings.disableValidationRules, + ConsumerOperation.Add, + config.workingDirectory + ); + 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}.generatePlugin.completed`, { + "pluginType": pluginTypes.toString(), + "errorsCount": errorsCount.toString(), + }, { + "duration": duration, + }); + return result; + }); + if (result) { + const isSuccess = await checkForSuccess(result); + if (!isSuccess) { + await exportLogsAndShowErrors(result); + } + const deepLinkParams = getDeepLinkParams(); + const isttkIntegration = deepLinkParams.source?.toLowerCase() === 'ttk'; + if (!isttkIntegration) { + void vscode.window.showInformationMessage(vscode.l10n.t('Plugin generated successfully.')); + } + } + return result; + } + private async generateClientAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { + const language = + typeof config.language === "string" + ? parseGenerationLanguage(config.language) + : KiotaGenerationLanguage.CSharp; + const result = await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + cancellable: false, + title: vscode.l10n.t("Generating client...") + }, async (progress, _) => { + const start = performance.now(); + const result = await generateClient( + this._context, + this._openApiTreeProvider.descriptionUrl, + outputPath, + language, + selectedPaths, + [], + typeof config.clientClassName === "string" + ? config.clientClassName + : "ApiClient", + typeof config.clientNamespaceName === "string" + ? config.clientNamespaceName + : "ApiSdk", + settings.backingStore, + settings.clearCache, + settings.cleanOutput, + settings.excludeBackwardCompatible, + settings.disableValidationRules, + settings.languagesSerializationConfiguration[language].serializers, + settings.languagesSerializationConfiguration[language].deserializers, + settings.structuredMimeTypes, + settings.includeAdditionalData, + ConsumerOperation.Add, + config.workingDirectory + ); + 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}.generateClient.completed`, { + "language": generationLanguageToString(language), + "errorsCount": errorsCount.toString(), + }, { + "duration": duration, + }); + return result; + }); + + let languagesInformation = await getLanguageInformationForDescription( + this._context, + this._openApiTreeProvider.descriptionUrl, + settings.clearCache + ); + if (languagesInformation) { + this._dependenciesViewProvider.update(languagesInformation, language); + await vscode.commands.executeCommand(treeViewFocusCommand); + } + if (result) { + const isSuccess = await checkForSuccess(result); + if (!isSuccess) { + await exportLogsAndShowErrors(result); + } + void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.')); + } + return result; + } + +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/commands/generate/generation-util.ts b/vscode/microsoft-kiota/src/commands/generate/generation-util.ts new file mode 100644 index 0000000000..844ba0b920 --- /dev/null +++ b/vscode/microsoft-kiota/src/commands/generate/generation-util.ts @@ -0,0 +1,28 @@ +import * as vscode from "vscode"; + +import { treeViewId } from "../../constants"; +import { KiotaLogEntry } from "../../kiotaInterop"; +import { OpenApiTreeProvider } from "../../openApiTreeProvider"; +import { getWorkspaceJsonPath, updateTreeViewIcons } from "../../util"; +import { loadWorkspaceFile } from "../../utilities/file"; + +export async function checkForSuccess(results: KiotaLogEntry[]) { + for (const result of results) { + if (result && result.message) { + if (result.message.includes("Generation completed successfully")) { + return true; + } + } + } + return false; +} + +export async function displayGenerationResults(openApiTreeProvider: OpenApiTreeProvider, config: any) { + const clientNameOrPluginName = config.clientClassName || config.pluginName; + openApiTreeProvider.refreshView(); + const workspaceJsonPath = getWorkspaceJsonPath(); + await loadWorkspaceFile({ fsPath: workspaceJsonPath }, openApiTreeProvider, clientNameOrPluginName); + await vscode.commands.executeCommand('kiota.workspace.refresh'); + openApiTreeProvider.resetInitialState(); + await updateTreeViewIcons(treeViewId, false, true); +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/extension.ts b/vscode/microsoft-kiota/src/extension.ts index 2edb17f965..1aab595878 100644 --- a/vscode/microsoft-kiota/src/extension.ts +++ b/vscode/microsoft-kiota/src/extension.ts @@ -1,12 +1,13 @@ // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import TelemetryReporter from '@vscode/extension-telemetry'; -import * as fs from 'fs'; import * as path from 'path'; import * as vscode from "vscode"; import { CodeLensProvider } from "./codelensProvider"; import { EditPathsCommand } from './commands/editPathsCommand'; +import { GenerateClientCommand } from './commands/generate/generateClientCommand'; +import { checkForSuccess, displayGenerationResults } from './commands/generate/generation-util'; import { MigrateFromLockFileCommand } from './commands/migrateFromLockFileCommand'; import { AddAllToSelectedEndpointsCommand } from './commands/open-api-tree-view/addAllToSelectedEndpointsCommand'; import { AddToSelectedEndpointsCommand } from './commands/open-api-tree-view/addToSelectedEndpointsCommand'; @@ -15,33 +16,34 @@ import { OpenDocumentationPageCommand } from './commands/open-api-tree-view/open import { RemoveAllFromSelectedEndpointsCommand } from './commands/open-api-tree-view/removeAllFromSelectedEndpointsCommand'; import { RemoveFromSelectedEndpointsCommand } from './commands/open-api-tree-view/removeFromSelectedEndpointsCommand'; import { SearchOrOpenApiDescriptionCommand } from './commands/open-api-tree-view/search-or-open-api-description/searchOrOpenApiDescriptionCommand'; -import { KIOTA_WORKSPACE_FILE, dependenciesInfo, extensionId, statusBarCommandId, treeViewFocusCommand, treeViewId } from "./constants"; +import { KIOTA_WORKSPACE_FILE, dependenciesInfo, extensionId, statusBarCommandId, treeViewId } from "./constants"; import { DependenciesViewProvider } from "./dependenciesViewProvider"; -import { GenerationType, KiotaGenerationLanguage, KiotaPluginType } from "./enums"; +import { KiotaGenerationLanguage, KiotaPluginType } from "./enums"; import { ExtensionSettings, getExtensionSettings } from "./extensionSettings"; import { generateClient } from "./generateClient"; +import { GeneratedOutputState } from './GeneratedOutputState'; import { generatePlugin } from "./generatePlugin"; import { getKiotaVersion } from "./getKiotaVersion"; -import { getLanguageInformation, getLanguageInformationForDescription } from "./getLanguageInformation"; -import { clearDeepLinkParams, getDeepLinkParams, setDeepLinkParams } from './handlers/deepLinkParamsHandler'; +import { getGenerationConfiguration, setGenerationConfiguration } from './handlers/configurationHandler'; +import { getDeepLinkParams, setDeepLinkParams } from './handlers/deepLinkParamsHandler'; +import { getWorkspaceGenerationType, setWorkspaceGenerationType } from './handlers/workspaceGenerationTypeHandler'; import { ClientOrPluginProperties, ConsumerOperation, - KiotaLogEntry, LogLevel, - generationLanguageToString, - getLogEntriesForLevel, + getLogEntriesForLevel } from "./kiotaInterop"; import { checkForLockFileAndPrompt } from "./migrateFromLockFile"; import { OpenApiTreeNode, OpenApiTreeProvider } from "./openApiTreeProvider"; -import { GenerateState, generateSteps } from "./steps"; import { updateClients } from "./updateClients"; import { - getSanitizedString, getWorkspaceJsonDirectory, getWorkspaceJsonPath, isClientType, isPluginType, parseGenerationLanguage, - parseGenerationType, parsePluginType, updateTreeViewIcons + parsePluginType, updateTreeViewIcons } from "./util"; -import { IntegrationParams, isDeeplinkEnabled, transformToGenerationConfig, validateDeepLinkQueryParams } from './utilities/deep-linking'; +import { IntegrationParams, validateDeepLinkQueryParams } from './utilities/deep-linking'; +import { loadWorkspaceFile } from './utilities/file'; +import { exportLogsAndShowErrors } from './utilities/logging'; +import { showUpgradeWarningMessage } from './utilities/messaging'; import { openTreeViewWithProgress } from './utilities/progress'; import { confirmOverride } from './utilities/regeneration'; import { loadTreeView } from "./workspaceTreeProvider"; @@ -50,13 +52,6 @@ let kiotaStatusBarItem: vscode.StatusBarItem; let kiotaOutputChannel: vscode.LogOutputChannel; let clientOrPluginKey: string; let clientOrPluginObject: ClientOrPluginProperties; -let workspaceGenerationType: string; -let config: Partial; -interface GeneratedOutputState { - outputPath: string; - clientClassName: string; -} - // This method is called when your extension is activated // Your extension is activated the very first time the command is executed export async function activate( @@ -80,6 +75,7 @@ export async function activate( const openDocumentationPageCommand = new OpenDocumentationPageCommand(); const editPathsCommand = new EditPathsCommand(openApiTreeProvider); const searchOrOpenApiDescriptionCommand = new SearchOrOpenApiDescriptionCommand(openApiTreeProvider, context); + const generateClientCommand = new GenerateClientCommand(openApiTreeProvider, context, dependenciesInfoProvider); await loadTreeView(context); await checkForLockFileAndPrompt(context); @@ -132,123 +128,19 @@ export async function activate( dependenciesInfo, dependenciesInfoProvider ), + vscode.window.registerTreeDataProvider(treeViewId, openApiTreeProvider), registerCommandWithTelemetry(reporter, openDocumentationPageCommand.getName(), async (openApiTreeNode: OpenApiTreeNode) => await openDocumentationPageCommand.execute(openApiTreeNode)), registerCommandWithTelemetry(reporter, addToSelectedEndpointsCommand.getName(), async (openApiTreeNode: OpenApiTreeNode) => await addToSelectedEndpointsCommand.execute(openApiTreeNode)), registerCommandWithTelemetry(reporter, addAllToSelectedEndpointsCommand.getName(), async (openApiTreeNode: OpenApiTreeNode) => await addAllToSelectedEndpointsCommand.execute(openApiTreeNode)), registerCommandWithTelemetry(reporter, removeFromSelectedEndpointsCommand.getName(), async (openApiTreeNode: OpenApiTreeNode) => await removeFromSelectedEndpointsCommand.execute(openApiTreeNode)), registerCommandWithTelemetry(reporter, removeAllFromSelectedEndpointsCommand.getName(), async (openApiTreeNode: OpenApiTreeNode) => await removeAllFromSelectedEndpointsCommand.execute(openApiTreeNode)), + registerCommandWithTelemetry(reporter, generateClientCommand.getName(), async () => await generateClientCommand.execute()), - registerCommandWithTelemetry(reporter, - `${treeViewId}.generateClient`, - async () => { - const deepLinkParams = getDeepLinkParams(); - const selectedPaths = openApiTreeProvider.getSelectedPaths(); - if (selectedPaths.length === 0) { - await vscode.window.showErrorMessage( - vscode.l10n.t("No endpoints selected, select endpoints first") - ); - return; - } - - let languagesInformation = await getLanguageInformation(context); - let availableStateInfo: Partial; - if (isDeeplinkEnabled(deepLinkParams)) { - if (!deepLinkParams.name && openApiTreeProvider.apiTitle) { - deepLinkParams.name = getSanitizedString(openApiTreeProvider.apiTitle); - } - availableStateInfo = transformToGenerationConfig(deepLinkParams); - } else { - const pluginName = getSanitizedString(openApiTreeProvider.apiTitle); - availableStateInfo = { - clientClassName: openApiTreeProvider.clientClassName, - clientNamespaceName: openApiTreeProvider.clientNamespaceName, - language: openApiTreeProvider.language, - outputPath: openApiTreeProvider.outputPath, - pluginName - }; - } - config = await generateSteps( - availableStateInfo, - languagesInformation, - deepLinkParams - ); - const generationType = parseGenerationType(config.generationType); - const outputPath = typeof config.outputPath === "string" - ? config.outputPath - : "./output"; - await showUpgradeWarningMessage(outputPath, context); - if (!openApiTreeProvider.descriptionUrl) { - await vscode.window.showErrorMessage( - vscode.l10n.t("No description found, select a description first") - ); - return; - } - - const settings = getExtensionSettings(extensionId); - workspaceGenerationType = config.generationType as string; - let result; - switch (generationType) { - case GenerationType.Client: - result = await generateClientAndRefreshUI(config, settings, outputPath, selectedPaths); - break; - case GenerationType.Plugin: - result = await generatePluginAndRefreshUI(config, settings, outputPath, selectedPaths); - break; - case GenerationType.ApiManifest: - result = await generateManifestAndRefreshUI(config, settings, outputPath, selectedPaths); - break; - default: - await vscode.window.showErrorMessage( - vscode.l10n.t("Invalid generation type") - ); - return; - } - if (result && getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length === 0) { - // Save state before opening the new window - const outputState = { - outputPath, - config, - clientClassName: config.clientClassName || config.pluginName - }; - void context.workspaceState.update('generatedOutput', outputState as GeneratedOutputState); - - const pathOfSpec = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-openapi.yml`); - const pathPluginManifest = path.join(outputPath, `${outputState.clientClassName?.toLowerCase()}-apiplugin.json`); - if (deepLinkParams.source && deepLinkParams.source.toLowerCase() === 'ttk') { - try { - await vscode.commands.executeCommand( - 'fx-extension.createprojectfromkiota', - [ - pathOfSpec, - pathPluginManifest, - deepLinkParams.ttkContext ? deepLinkParams.ttkContext : undefined - ] - ); - openApiTreeProvider.closeDescription(); - await updateTreeViewIcons(treeViewId, false); - } catch (error) { - reporter.sendTelemetryEvent("DeepLinked fx-extension.createprojectfromkiota", { - "error": JSON.stringify(error) - }); - } - } else { - if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { - await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(config.workingDirectory ?? getWorkspaceJsonDirectory()), true); - } else { - await displayGenerationResults(context, openApiTreeProvider, config); - } - } - - clearDeepLinkParams(); // Clear the state after the generation - } - } - ), vscode.workspace.onDidChangeWorkspaceFolders(async () => { const generatedOutput = context.workspaceState.get('generatedOutput'); if (generatedOutput) { - const { outputPath } = generatedOutput; - await displayGenerationResults(context, openApiTreeProvider, config); + await displayGenerationResults(openApiTreeProvider, getGenerationConfiguration()); // Clear the state void context.workspaceState.update('generatedOutput', undefined); } @@ -273,24 +165,25 @@ export async function activate( registerCommandWithTelemetry(reporter, editPathsCommand.getName(), async (clientKey: string, clientObject: ClientOrPluginProperties, generationType: string) => { clientOrPluginKey = clientKey; clientOrPluginObject = clientObject; - workspaceGenerationType = generationType; + setWorkspaceGenerationType(generationType); await editPathsCommand.execute({ clientKey, clientObject }); }), registerCommandWithTelemetry(reporter, `${treeViewId}.regenerateButton`, async () => { + let configuration = getGenerationConfiguration(); const regenerate = await confirmOverride(); if (!regenerate) { return; } if (!clientOrPluginKey || clientOrPluginKey === '') { - clientOrPluginKey = config.clientClassName || config.pluginName || ''; + clientOrPluginKey = configuration.clientClassName || configuration.pluginName || ''; } - if (!config) { - config = { + if (!configuration) { + setGenerationConfiguration({ outputPath: clientOrPluginObject.outputPath, clientClassName: clientOrPluginKey, - }; + }); } const settings = getExtensionSettings(extensionId); const selectedPaths = openApiTreeProvider.getSelectedPaths(); @@ -300,11 +193,12 @@ export async function activate( ); return; } + const workspaceGenerationType = getWorkspaceGenerationType(); if (isClientType(workspaceGenerationType)) { - await regenerateClient(clientOrPluginKey, config, settings, selectedPaths); + await regenerateClient(clientOrPluginKey, getGenerationConfiguration(), settings, selectedPaths); } if (isPluginType(workspaceGenerationType)) { - await regeneratePlugin(clientOrPluginKey, config, settings, selectedPaths); + await regeneratePlugin(clientOrPluginKey, getGenerationConfiguration(), settings, selectedPaths); } }), registerCommandWithTelemetry(reporter, `${extensionId}.regenerate`, async (clientKey: string, clientObject: ClientOrPluginProperties, generationType: string) => { @@ -332,171 +226,7 @@ export async function activate( registerCommandWithTelemetry(reporter, migrateFromLockFileCommand.getName(), async (uri: vscode.Uri) => await migrateFromLockFileCommand.execute(uri)), ); - async function generateManifestAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { - const pluginTypes = KiotaPluginType.ApiManifest; - const result = await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: vscode.l10n.t("Generating manifest...") - }, async (progress, _) => { - const start = performance.now(); - const result = await generatePlugin( - context, - openApiTreeProvider.descriptionUrl, - outputPath, - [pluginTypes], - selectedPaths, - [], - typeof config.pluginName === "string" - ? config.pluginName - : "ApiClient", - settings.clearCache, - settings.cleanOutput, - settings.disableValidationRules, - ConsumerOperation.Add, - config.workingDirectory - ); - const duration = performance.now() - start; - const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0; - reporter.sendRawTelemetryEvent(`${extensionId}.generateManifest.completed`, { - "pluginType": pluginTypes.toString(), - "errorsCount": errorsCount.toString(), - }, { - "duration": duration, - }); - return result; - }); - if (result) { - const isSuccess = await checkForSuccess(result); - if (!isSuccess) { - await exportLogsAndShowErrors(result); - } - void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.')); - } - return result; - } - async function generatePluginAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { - const pluginTypes = Array.isArray(config.pluginTypes) ? parsePluginType(config.pluginTypes) : [KiotaPluginType.ApiPlugin]; - const result = await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: vscode.l10n.t("Generating plugin...") - }, async (progress, _) => { - const start = performance.now(); - const result = await generatePlugin( - context, - openApiTreeProvider.descriptionUrl, - outputPath, - pluginTypes, - selectedPaths, - [], - typeof config.pluginName === "string" - ? config.pluginName - : "ApiClient", - settings.clearCache, - settings.cleanOutput, - settings.disableValidationRules, - ConsumerOperation.Add, - config.workingDirectory - ); - const duration = performance.now() - start; - const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0; - reporter.sendRawTelemetryEvent(`${extensionId}.generatePlugin.completed`, { - "pluginType": pluginTypes.toString(), - "errorsCount": errorsCount.toString(), - }, { - "duration": duration, - }); - return result; - }); - if (result) { - const isSuccess = await checkForSuccess(result); - if (!isSuccess) { - await exportLogsAndShowErrors(result); - } - const deepLinkParams = getDeepLinkParams(); - const isttkIntegration = deepLinkParams.source && deepLinkParams.source.toLowerCase() === 'ttk'; - if (!isttkIntegration) { - void vscode.window.showInformationMessage(vscode.l10n.t('Plugin generated successfully.')); - } - } - return result; - } - async function generateClientAndRefreshUI(config: Partial, settings: ExtensionSettings, outputPath: string, selectedPaths: string[]): Promise { - const language = - typeof config.language === "string" - ? parseGenerationLanguage(config.language) - : KiotaGenerationLanguage.CSharp; - const result = await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: vscode.l10n.t("Generating client...") - }, async (progress, _) => { - const start = performance.now(); - const result = await generateClient( - context, - openApiTreeProvider.descriptionUrl, - outputPath, - language, - selectedPaths, - [], - typeof config.clientClassName === "string" - ? config.clientClassName - : "ApiClient", - typeof config.clientNamespaceName === "string" - ? config.clientNamespaceName - : "ApiSdk", - settings.backingStore, - settings.clearCache, - settings.cleanOutput, - settings.excludeBackwardCompatible, - settings.disableValidationRules, - settings.languagesSerializationConfiguration[language].serializers, - settings.languagesSerializationConfiguration[language].deserializers, - settings.structuredMimeTypes, - settings.includeAdditionalData, - ConsumerOperation.Add, - config.workingDirectory - ); - const duration = performance.now() - start; - const errorsCount = result ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error).length : 0; - reporter.sendRawTelemetryEvent(`${extensionId}.generateClient.completed`, { - "language": generationLanguageToString(language), - "errorsCount": errorsCount.toString(), - }, { - "duration": duration, - }); - return result; - }); - - let languagesInformation = await getLanguageInformationForDescription( - context, - openApiTreeProvider.descriptionUrl, - settings.clearCache - ); - if (languagesInformation) { - dependenciesInfoProvider.update(languagesInformation, language); - await vscode.commands.executeCommand(treeViewFocusCommand); - } - if (result) { - const isSuccess = await checkForSuccess(result); - if (!isSuccess) { - await exportLogsAndShowErrors(result); - } - void vscode.window.showInformationMessage(vscode.l10n.t('Generation completed successfully.')); - } - return result; - } - async function displayGenerationResults(context: vscode.ExtensionContext, openApiTreeProvider: OpenApiTreeProvider, config: any) { - const clientNameOrPluginName = config.clientClassName || config.pluginName; - openApiTreeProvider.refreshView(); - const workspaceJsonPath = getWorkspaceJsonPath(); - await loadWorkspaceFile({ fsPath: workspaceJsonPath }, openApiTreeProvider, clientNameOrPluginName); - await vscode.commands.executeCommand('kiota.workspace.refresh'); - openApiTreeProvider.resetInitialState(); - await updateTreeViewIcons(treeViewId, false, true); - } async function regenerateClient(clientKey: string, clientObject: any, settings: ExtensionSettings, selectedPaths?: string[]): Promise { const language = typeof clientObject.language === "string" @@ -651,68 +381,10 @@ function registerCommandWithTelemetry(reporter: TelemetryReporter, command: stri }, thisArg); } -async function showUpgradeWarningMessage(clientPath: string, context: vscode.ExtensionContext): Promise { - const kiotaVersion = context.extension.packageJSON.kiotaVersion.toLocaleLowerCase(); - const workspaceFilePath = path.join(clientPath, KIOTA_WORKSPACE_FILE); - if (!fs.existsSync(workspaceFilePath)) { - return; - } - const workspaceFileData = await vscode.workspace.fs.readFile(vscode.Uri.file(workspaceFilePath)); - const workspaceFile = JSON.parse(workspaceFileData.toString()) as { kiotaVersion: string }; - // don't fail if kiotaVersion isn't in the workspace config file - if (workspaceFile.kiotaVersion) { - const clientVersion = workspaceFile.kiotaVersion.toLocaleLowerCase(); - if (clientVersion !== kiotaVersion) { - await vscode.window.showWarningMessage(vscode.l10n.t("Client will be upgraded from version {0} to {1}, upgrade your dependencies", clientVersion, kiotaVersion)); - } - } -} - -async function loadWorkspaceFile(node: { fsPath: string }, openApiTreeProvider: OpenApiTreeProvider, clientOrPluginName?: string): Promise { - await openTreeViewWithProgress(() => openApiTreeProvider.loadWorkspaceFile(node.fsPath, clientOrPluginName)); - await updateTreeViewIcons(treeViewId, true); -} - async function loadEditPaths(clientOrPluginKey: string, clientObject: any, openApiTreeProvider: OpenApiTreeProvider): Promise { await openTreeViewWithProgress(() => openApiTreeProvider.loadEditPaths(clientOrPluginKey, clientObject)); } -async function exportLogsAndShowErrors(result: KiotaLogEntry[]): Promise { - const errorMessages = result - ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error) - : []; - - result.forEach((element) => { - logFromLogLevel(element); - }); - if (errorMessages.length > 0) { - await Promise.all(errorMessages.map((element) => { - return vscode.window.showErrorMessage(element.message); - })); - } -} - -function logFromLogLevel(entry: KiotaLogEntry): void { - switch (entry.level) { - case LogLevel.critical: - case LogLevel.error: - kiotaOutputChannel.error(entry.message); - break; - case LogLevel.warning: - kiotaOutputChannel.warn(entry.message); - break; - case LogLevel.debug: - kiotaOutputChannel.debug(entry.message); - break; - case LogLevel.trace: - kiotaOutputChannel.trace(entry.message); - break; - default: - kiotaOutputChannel.info(entry.message); - break; - } -} - async function updateStatusBarItem(context: vscode.ExtensionContext): Promise { try { const version = await getKiotaVersion(context, kiotaOutputChannel); @@ -744,17 +416,6 @@ function getQueryParameters(uri: vscode.Uri): Record { }); return parameters; } -async function checkForSuccess(results: KiotaLogEntry[]) { - for (const result of results) { - if (result && result.message) { - if (result.message.includes("Generation completed successfully")) { - return true; - } - } - } - return false; -} - // This method is called when your extension is deactivated export function deactivate() { } diff --git a/vscode/microsoft-kiota/src/handlers/configurationHandler.ts b/vscode/microsoft-kiota/src/handlers/configurationHandler.ts new file mode 100644 index 0000000000..96afb4c22e --- /dev/null +++ b/vscode/microsoft-kiota/src/handlers/configurationHandler.ts @@ -0,0 +1,8 @@ +import { GenerateState } from "../steps"; + +let configuration: Partial; + +export const getGenerationConfiguration = () => configuration; +export const setGenerationConfiguration = (config: Partial) => { + configuration = config; +}; \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/handlers/workspaceGenerationTypeHandler.ts b/vscode/microsoft-kiota/src/handlers/workspaceGenerationTypeHandler.ts new file mode 100644 index 0000000000..23c8ab9764 --- /dev/null +++ b/vscode/microsoft-kiota/src/handlers/workspaceGenerationTypeHandler.ts @@ -0,0 +1,6 @@ +let workspaceGenerationType: string; + +export const getWorkspaceGenerationType = () => workspaceGenerationType; +export const setWorkspaceGenerationType = (generationType: string) => { + workspaceGenerationType = generationType; +}; \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/utilities/file.ts b/vscode/microsoft-kiota/src/utilities/file.ts new file mode 100644 index 0000000000..07c85b3b7b --- /dev/null +++ b/vscode/microsoft-kiota/src/utilities/file.ts @@ -0,0 +1,9 @@ +import { treeViewId } from "../constants"; +import { OpenApiTreeProvider } from "../openApiTreeProvider"; +import { updateTreeViewIcons } from "../util"; +import { openTreeViewWithProgress } from "./progress"; + +export async function loadWorkspaceFile(node: { fsPath: string }, openApiTreeProvider: OpenApiTreeProvider, clientOrPluginName?: string): Promise { + await openTreeViewWithProgress(() => openApiTreeProvider.loadWorkspaceFile(node.fsPath, clientOrPluginName)); + await updateTreeViewIcons(treeViewId, true); +} \ No newline at end of file diff --git a/vscode/microsoft-kiota/src/utilities/logging.ts b/vscode/microsoft-kiota/src/utilities/logging.ts new file mode 100644 index 0000000000..616e822965 --- /dev/null +++ b/vscode/microsoft-kiota/src/utilities/logging.ts @@ -0,0 +1,43 @@ +import * as vscode from 'vscode'; +import { getLogEntriesForLevel, KiotaLogEntry, LogLevel } from '../kiotaInterop'; + +let kiotaOutputChannel: vscode.LogOutputChannel; +kiotaOutputChannel = vscode.window.createOutputChannel("Kiota", { + log: true, +}); + +export async function exportLogsAndShowErrors(result: KiotaLogEntry[]): Promise { + const errorMessages = result + ? getLogEntriesForLevel(result, LogLevel.critical, LogLevel.error) + : []; + + result.forEach((element) => { + logFromLogLevel(element); + }); + if (errorMessages.length > 0) { + await Promise.all(errorMessages.map((element) => { + return vscode.window.showErrorMessage(element.message); + })); + } +} + +function logFromLogLevel(entry: KiotaLogEntry): void { + switch (entry.level) { + case LogLevel.critical: + case LogLevel.error: + kiotaOutputChannel.error(entry.message); + break; + case LogLevel.warning: + kiotaOutputChannel.warn(entry.message); + break; + case LogLevel.debug: + kiotaOutputChannel.debug(entry.message); + break; + case LogLevel.trace: + kiotaOutputChannel.trace(entry.message); + break; + default: + kiotaOutputChannel.info(entry.message); + break; + } +} diff --git a/vscode/microsoft-kiota/src/utilities/messaging.ts b/vscode/microsoft-kiota/src/utilities/messaging.ts new file mode 100644 index 0000000000..ec64b837c2 --- /dev/null +++ b/vscode/microsoft-kiota/src/utilities/messaging.ts @@ -0,0 +1,22 @@ +import * as vscode from "vscode"; +import * as fs from "fs"; +import * as path from "path"; + +import { KIOTA_WORKSPACE_FILE } from "../constants"; + +export async function showUpgradeWarningMessage(clientPath: string, context: vscode.ExtensionContext): Promise { + const kiotaVersion = context.extension.packageJSON.kiotaVersion.toLocaleLowerCase(); + const workspaceFilePath = path.join(clientPath, KIOTA_WORKSPACE_FILE); + if (!fs.existsSync(workspaceFilePath)) { + return; + } + const workspaceFileData = await vscode.workspace.fs.readFile(vscode.Uri.file(workspaceFilePath)); + const workspaceFile = JSON.parse(workspaceFileData.toString()) as { kiotaVersion: string }; + // don't fail if kiotaVersion isn't in the workspace config file + if (workspaceFile.kiotaVersion) { + const clientVersion = workspaceFile.kiotaVersion.toLocaleLowerCase(); + if (clientVersion !== kiotaVersion) { + await vscode.window.showWarningMessage(vscode.l10n.t("Client will be upgraded from version {0} to {1}, upgrade your dependencies", clientVersion, kiotaVersion)); + } + } +} \ No newline at end of file