From b912daf18c290cfb6bdfd80c52b58a57b85d6ba4 Mon Sep 17 00:00:00 2001 From: wenyt <75360946+wenytang-ms@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:53:36 +0800 Subject: [PATCH] feat: export api spec (#91) * feat: export open api * feat: restore support export * feat: support export api * feat: export open api * feat: restore support export * feat: update * feat: update * feat: update * feat: update method name * feat: update * feat: support command palette to export api * test: add test cases * test: add extension test cases * test: revert extension tests * test: update * feat: update * feat: add * feat: update --- package.json | 12 ++-- package.nls.json | 2 +- src/azure/ApiCenter/contracts.ts | 4 ++ src/commands/exportApi.ts | 60 ++++++++++++++++++++ src/commands/exportOpenApi.ts | 26 --------- src/extension.ts | 4 +- src/test/unit/commands/exportApi.test.ts | 71 ++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 37 deletions(-) create mode 100644 src/commands/exportApi.ts delete mode 100644 src/commands/exportOpenApi.ts create mode 100644 src/test/unit/commands/exportApi.test.ts diff --git a/package.json b/package.json index 75fb30d1..87c2bf06 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,8 @@ "category": "Azure API Center" }, { - "command": "azure-api-center.exportOpenApi", - "title": "%azure-api-center.commands.exportOpenApi.title%", + "command": "azure-api-center.exportApi", + "title": "%azure-api-center.commands.exportApi.title%", "category": "Azure API Center" }, { @@ -194,8 +194,8 @@ "when": "view == apiCenterTreeView && viewItem =~ /never/" }, { - "command": "azure-api-center.exportOpenApi", - "when": "view == apiCenterTreeView && viewItem =~ /never/" + "command": "azure-api-center.exportApi", + "when": "view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/" }, { "command": "azure-api-center.showOpenApi", @@ -233,10 +233,6 @@ "command": "azure-api-center.importOpenApiByLink", "when": "never" }, - { - "command": "azure-api-center.exportOpenApi", - "when": "never" - }, { "command": "azure-api-center.showOpenApi", "when": "never" diff --git a/package.nls.json b/package.nls.json index 610590d2..cdc8be78 100644 --- a/package.nls.json +++ b/package.nls.json @@ -6,7 +6,7 @@ "azure-api-center.commands.generate-api-client.title": "Generate API Client", "azure-api-center.commands.importOpenApiByFile.title": "Import OpenAPI from File", "azure-api-center.commands.importOpenApiByLink.title": "Import OpenAPI from Link", - "azure-api-center.commands.exportOpenApi.title": "Export OpenAPI", + "azure-api-center.commands.exportApi.title": "Export API", "azure-api-center.commands.showOpenApi.title": "Edit OpenAPI", "azure-api-center.commands.generateHttpFile.title": "Generate HTTP File", "azure-api-center.commands.registerApi.title": "Register API", diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index 8ccb6716..609a1713 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -117,3 +117,7 @@ export enum SpecificationName { other = 'Other', }; +export enum ApiSpecExportResultFormat { + inline = 'inline', + link = 'link', +}; diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts new file mode 100644 index 00000000..caf3fedb --- /dev/null +++ b/src/commands/exportApi.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import * as fs from "fs-extra"; +import * as path from "path"; +import * as vscode from "vscode"; +import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; +import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; +import { TelemetryClient } from '../common/telemetryClient'; +import { ext } from "../extensionVariables"; +import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { createTemporaryFolder } from "../utils/fsUtil"; +export namespace ExportAPI { + export async function exportApi( + context: IActionContext, + node?: ApiVersionDefinitionTreeItem): Promise { + if (!node) { + node = await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiVersionDefinitionTreeItem.contextValue}*`), context); + } + + const apiCenterService = new ApiCenterService( + node?.subscription!, + getResourceGroupFromId(node?.id!), + node?.apiCenterName!); + const exportedSpec = await apiCenterService.exportSpecification( + node?.apiCenterApiName!, + node?.apiCenterApiVersionName!, + node?.apiCenterApiVersionDefinition.name!); + await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); + } + + function getFolderName(treeItem: ApiVersionDefinitionTreeItem): string { + return `${treeItem.apiCenterName}-${treeItem.apiCenterApiName}`; + } + + function getFilename(treeItem: ApiVersionDefinitionTreeItem): string { + return `${treeItem.apiCenterApiVersionDefinition.name}`; + } + + async function writeToTempFile(node: ApiVersionDefinitionTreeItem, specFormat: string, specValue: string) { + if (specFormat === ApiSpecExportResultFormat.inline) { + await ExportAPI.showTempFile(node, specValue); + } else { + // Currently at server side did not exist link, so just monitor this event. + TelemetryClient.sendEvent("azure-api-center.exportApi", { format: specFormat }); + } + } + + export async function showTempFile(node: ApiVersionDefinitionTreeItem, fileContent: string) { + const folderName = getFolderName(node); + const folderPath = await createTemporaryFolder(folderName); + const fileName = getFilename(node); + const localFilePath: string = path.join(folderPath, fileName); + await fs.ensureFile(localFilePath); + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); + await vscode.workspace.fs.writeFile(vscode.Uri.file(localFilePath), Buffer.from(fileContent)); + await vscode.window.showTextDocument(document); + } +} diff --git a/src/commands/exportOpenApi.ts b/src/commands/exportOpenApi.ts deleted file mode 100644 index d91733cd..00000000 --- a/src/commands/exportOpenApi.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import { IActionContext } from "@microsoft/vscode-azext-utils"; -import { ApiVersionDefinitionsTreeItem } from "../tree/ApiVersionDefinitionsTreeItem"; -import { OpenDialogOptions, ProgressLocation, Uri, window, workspace } from "vscode"; -import { ext } from "../extensionVariables"; -import * as fse from 'fs-extra'; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; -import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; -import { ApiCenterApiVersionDefinitionImport } from "../azure/ApiCenter/contracts"; - -export async function exportOpenApi( - context: IActionContext, - node?: ApiVersionDefinitionTreeItem): Promise { - - const apiCenterService = new ApiCenterService( - node?.subscription!, - getResourceGroupFromId(node?.id!), - node?.apiCenterName!); - - const exportedSpec = await apiCenterService.exportSpecification( - node?.apiCenterApiName!, - node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.name!); -} diff --git a/src/extension.ts b/src/extension.ts index 54edf0b6..54f7f350 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,7 @@ import { registerAzureUtilsExtensionVariables } from '@microsoft/vscode-azext-az import { AzExtTreeDataProvider, AzExtTreeItem, CommandCallback, IActionContext, IParsedError, createAzExtOutputChannel, isUserCancelledError, parseError, registerCommand, registerEvent } from '@microsoft/vscode-azext-utils'; import { cleanupSearchResult } from './commands/cleanUpSearch'; import { showOpenApi } from './commands/editOpenApi'; -import { exportOpenApi } from './commands/exportOpenApi'; +import { ExportAPI } from './commands/exportApi'; import { generateApiLibrary } from './commands/generateApiLibrary'; import { generateHttpFile } from './commands/generateHttpFile'; import { importOpenApi } from './commands/importOpenApi'; @@ -69,7 +69,7 @@ export async function activate(context: vscode.ExtensionContext) { // TODO: move all three to their separate files registerCommandWithTelemetry('azure-api-center.importOpenApiByFile', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem) => { await importOpenApi(context, node, false); }); registerCommandWithTelemetry('azure-api-center.importOpenApiByLink', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem) => { await importOpenApi(context, node, true); }); - registerCommandWithTelemetry('azure-api-center.exportOpenApi', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem) => { await exportOpenApi(context, node); }); + registerCommandWithTelemetry('azure-api-center.exportApi', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem) => { await ExportAPI.exportApi(context, node); }); // TODO: move this to a separate file const openApiEditor: OpenApiEditor = new OpenApiEditor(); diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts new file mode 100644 index 00000000..c24b961d --- /dev/null +++ b/src/test/unit/commands/exportApi.test.ts @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; +import * as sinon from "sinon"; +import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; +import { ApiCenterApiVersionDefinition } from "../../../azure/ApiCenter/contracts"; +import { ExportAPI } from "../../../commands/exportApi"; +import { TelemetryClient } from "../../../common/telemetryClient"; +import { ApiVersionDefinitionTreeItem } from "../../../tree/ApiVersionDefinitionTreeItem"; +abstract class ParentTreeItemBase extends AzExtParentTreeItem { + private _childIndex: number = 0; + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + const children: AzExtTreeItem[] = []; + return children; + } + public hasMoreChildrenImpl(): boolean { + return this._childIndex < 10; + } + protected abstract createChildTreeItem(index: number): AzExtTreeItem; +} + +class RootTreeItem extends ParentTreeItemBase { + public label: string = 'root'; + public contextValue: string = 'root'; + + protected createChildTreeItem(index: number): AzExtTreeItem { + return new ApiVersionDefinitionTreeItem(this, "fakeApiCenterName", "fakeApiCenterApiName", "fakeApiCenterApiVersionName", {} as ApiCenterApiVersionDefinition); + } +} + +suite("export API test cases", () => { + let sandbox = null as any; + let root: RootTreeItem; + let node: ApiVersionDefinitionTreeItem; + suiteSetup(() => { + sandbox = sinon.createSandbox(); + sinon.stub(TelemetryClient, "sendEvent").returns(); + }); + setup(() => { + root = new RootTreeItem(undefined); + node = new ApiVersionDefinitionTreeItem(root, + "fakeApiCenterName", + "fakeApiCenterApiName", + "fakeApiCenterApiVersionName", + { + properties: { + specification: { + name: "fakeName" + } + } + } as ApiCenterApiVersionDefinition + ); + sandbox.stub(node, "subscription").value("fakeSub"); + sandbox.stub(node, "id").value("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test/providers/Microsoft.ApiCenter/services/test/workspaces/default/apis/test/versions/v1/definitions/openapi"); + }); + teardown(() => { + sandbox.restore(); + }); + test('export API happy path with link type', async () => { + const spyShowTempFile = sandbox.spy(ExportAPI, "showTempFile"); + sandbox.stub(ApiCenterService.prototype, "exportSpecification").resolves({ format: "link", value: "fakeValue" }); + await ExportAPI.exportApi({} as IActionContext, node); + sandbox.assert.notCalled(spyShowTempFile); + }); + test('export API happy path with inline type', async () => { + let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); + sandbox.stub(ApiCenterService.prototype, "exportSpecification").resolves({ format: "inline", value: "fakeValue" }); + await ExportAPI.exportApi({} as IActionContext, node); + sandbox.assert.calledOnce(stubShowTempFile); + }); +});