From 96cdd450ad4d745576c9cd3742d55a3add570052 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Wed, 5 Jun 2024 21:30:43 +0800 Subject: [PATCH 01/29] feat: rbac demo --- package.json | 66 +++- src/azure/azureLogin/authTypes.ts | 8 + src/azure/azureLogin/azureAuth.ts | 3 +- .../azureLogin/dataPlaneSessionProvider.ts | 257 +++++++++++++ src/commands/exportApi.ts | 68 +++- src/commands/removeDataplaneApi.ts | 26 ++ src/commands/workspaceApis.ts | 51 +++ src/extension.ts | 57 ++- src/extensionVariables.ts | 13 +- src/tree/DataPlaneAccount.ts | 352 ++++++++++++++++++ src/tree/Editors/openApi/OpenApiEditor.ts | 1 - tsconfig.json | 3 +- 12 files changed, 886 insertions(+), 19 deletions(-) create mode 100644 src/azure/azureLogin/dataPlaneSessionProvider.ts create mode 100644 src/commands/removeDataplaneApi.ts create mode 100644 src/commands/workspaceApis.ts create mode 100644 src/tree/DataPlaneAccount.ts diff --git a/package.json b/package.json index 32906f2b..fb78b839 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,36 @@ "command": "azure-api-center.selectTenant", "title": "%azure-api-center.commands.selectTenant.title%", "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.addApis", + "title": "Add Api", + "icon": "$(add)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.refresh", + "title": "Refresh", + "icon": "$(refresh)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.logout", + "title": "Sign out", + "icon": "$(sign-out)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.collapse", + "title": "Collapse", + "icon": "$(collapse-all)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "title": "Remove Dataplane", + "icon": "$(close)", + "category": "Azure API Center" } ], "views": { @@ -173,6 +203,11 @@ "name": "API Center", "icon": "media/api-center-icon.svg", "contextualTitle": "Azure API Center" + }, + { + "id": "apiCenterWorkspace", + "name": "API Center Workspace", + "visibility": "visible" } ] }, @@ -191,6 +226,26 @@ "command": "azure-api-center.apiCenterTreeView.refresh", "when": "view == apiCenterTreeView", "group": "navigation" + }, + { + "command": "azure-api-center.apiCenterWorkspace.addApis", + "when": "view == apiCenterWorkspace", + "group": "navigation@2" + }, + { + "command": "azure-api-center.apiCenterWorkspace.refresh", + "when": "view == apiCenterWorkspace", + "group": "navigation@1" + }, + { + "command": "azure-api-center.apiCenterWorkspace.logout", + "when": "view == apiCenterWorkspace", + "group": "navigation@0" + }, + { + "command": "azure-api-center.apiCenterWorkspace.collapse", + "when": "view == apiCenterWorkspace", + "group": "navigation@3" } ], "view/item/context": [ @@ -221,7 +276,7 @@ }, { "command": "azure-api-center.exportApi", - "when": "view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/" + "when": "(view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/) || (view == apiCenterWorkspace && viewItem == WorkspaceAPICenter-Definition)" }, { "command": "azure-api-center.showOpenApi", @@ -277,6 +332,11 @@ "command": "azure-api-center.deleteCustomFunction", "when": "view == apiCenterTreeView && viewItem == azureApiCenterFunction", "group": "function@1" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "when": "view == apiCenterWorkspace && viewItem == WorkspaceAPICenter-Server", + "group": "inline@0" } ], "copilot": [ @@ -358,6 +418,10 @@ { "command": "azure-api-center.deleteCustomFunction", "when": "never" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "when": "never" } ] }, diff --git a/src/azure/azureLogin/authTypes.ts b/src/azure/azureLogin/authTypes.ts index ed55b191..139d8a18 100644 --- a/src/azure/azureLogin/authTypes.ts +++ b/src/azure/azureLogin/authTypes.ts @@ -50,6 +50,14 @@ export type AzureSessionProvider = { dispose(): void; }; +export type AzureDataSessionProvider = { + signIn(): Promise; + signInStatus: SignInStatus; + signInStatusChangeEvent: Event; + getAuthSession(options?: GetAuthSessionOptions): Promise>; + dispose(): void; +} + export type ReadyAzureSessionProvider = AzureSessionProvider & { signInStatus: SignInStatus.SignedIn; selectedTenant: Tenant; diff --git a/src/azure/azureLogin/azureAuth.ts b/src/azure/azureLogin/azureAuth.ts index 78076343..6af233ba 100644 --- a/src/azure/azureLogin/azureAuth.ts +++ b/src/azure/azureLogin/azureAuth.ts @@ -140,7 +140,8 @@ export namespace AzureAuth { type AuthProviderId = "microsoft" | "microsoft-sovereign-cloud"; export function getConfiguredAuthProviderId(): AuthProviderId { - return AzureAuth.getConfiguredAzureEnv().name === Environment.AzureCloud.name ? "microsoft" : "microsoft-sovereign-cloud"; + const type = AzureAuth.getConfiguredAzureEnv().name === Environment.AzureCloud.name ? "microsoft" : "microsoft-sovereign-cloud"; + return type; } export function isReady(provider: AzureSessionProvider): provider is ReadyAzureSessionProvider { diff --git a/src/azure/azureLogin/dataPlaneSessionProvider.ts b/src/azure/azureLogin/dataPlaneSessionProvider.ts new file mode 100644 index 00000000..ff6a56e0 --- /dev/null +++ b/src/azure/azureLogin/dataPlaneSessionProvider.ts @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AuthenticationGetSessionOptions, + AuthenticationSession, + Event, + EventEmitter, + ExtensionContext, + Disposable as VsCodeDisposable, + authentication, + l10n +} from "vscode"; +import { UiStrings } from "../../uiStrings"; +import { GeneralUtils } from "../../utils/generalUtils"; +import { AzureAuthenticationSession, AzureDataSessionProvider, GetAuthSessionOptions, SignInStatus, Tenant } from "./authTypes"; +import { AzureAccount } from "./azureAccount"; +import { AzureAuth } from "./azureAuth"; + +enum AuthScenario { + Initialization, + SignIn, + GetSession, +} + +export namespace DataPlaneSessionProviderHelper { + + let sessionProvider: AzureDataSessionProvider; + + export function activateAzureSessionProvider(context: ExtensionContext) { + sessionProvider = new DataPlaneSessionProviderImpl(); + context.subscriptions.push(sessionProvider); + } + + export function getSessionProvider(): AzureDataSessionProvider { + return sessionProvider; + } + + class DataPlaneSessionProviderImpl extends VsCodeDisposable implements AzureDataSessionProvider { + private readonly initializePromise: Promise; + private handleSessionChanges: boolean = true; + private tenants: Tenant[] = []; + private selectedTenantValue: Tenant | null = null; + + public readonly onSignInStatusChangeEmitter = new EventEmitter(); + public signInStatusValue: SignInStatus = SignInStatus.Initializing; + + public constructor() { + const disposable = authentication.onDidChangeSessions(async (e) => { + // Ignore events for non-microsoft providers + if (e.provider.id !== AzureAuth.getConfiguredAuthProviderId()) { + return; + } + + // Ignore events that we triggered. + if (!this.handleSessionChanges) { + return; + } + + // Silently check authentication status and tenants + await this.signInAndUpdateTenants(AuthScenario.Initialization); + }); + + super(() => { + this.onSignInStatusChangeEmitter.dispose(); + disposable.dispose(); + }); + + this.initializePromise = this.initialize(); + } + + public get signInStatus(): SignInStatus { + return this.signInStatusValue; + } + + public get signInStatusChangeEvent(): Event { + return this.onSignInStatusChangeEmitter.event; + } + + private async initialize(): Promise { + await this.signInAndUpdateTenants(AuthScenario.Initialization); + } + + /** + * Sign in to Azure interactively, i.e. prompt the user to sign in even if they have an active session. + * This allows the user to choose a different account or tenant. + */ + public async signIn(): Promise { + await this.initializePromise; + + const newSignInStatus = SignInStatus.SigningIn; + if (newSignInStatus !== this.signInStatusValue) { + this.signInStatusValue = newSignInStatus; + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + + await this.signInAndUpdateTenants(AuthScenario.SignIn); + } + + private async signInAndUpdateTenants(authScenario: AuthScenario): Promise { + // Initially, try to get a session using the 'organizations' tenant/authority: + // https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority + // This allows the user to sign in to the Microsoft provider and list tenants, + // but the resulting session will not allow tenant-level operations. For that, + // we need to get a session for a specific tenant. + const orgTenantId = "organizations"; + const scopes = AzureAuth.getScopes(orgTenantId, {}); + const getSessionResult = await this.getArmSession(orgTenantId, scopes, authScenario); + + // Get the tenants + const getTenantsResult = await GeneralUtils.bindAsync(getSessionResult, (session) => AzureAccount.getTenants(session)); + const newTenants = GeneralUtils.succeeded(getTenantsResult) ? getTenantsResult.result : []; + const tenantsChanged = AzureAccount.getIdString(newTenants) !== AzureAccount.getIdString(this.tenants); + + // Determine which tenant should be selected. We can't force the user to choose at this stage, + // so this can be null, and will be set when the user tries to get a session. + const newSelectedTenant = await this.getNewSelectedTenant(newTenants, this.selectedTenantValue, authScenario); + const selectedTenantChanged = newSelectedTenant?.id !== this.selectedTenantValue?.id; + + // Get the overall sign-in status. If the user has access to any tenants they are signed in. + const newSignInStatus = newTenants.length > 0 ? SignInStatus.SignedIn : SignInStatus.SignedOut; + const signInStatusChanged = newSignInStatus !== this.signInStatusValue; + + // Update the state and fire event if anything has changed. + this.selectedTenantValue = newSelectedTenant; + this.tenants = newTenants; + this.signInStatusValue = newSignInStatus; + if (signInStatusChanged || tenantsChanged || selectedTenantChanged) { + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + } + + /** + * Get the current Azure session, silently if possible. + * @returns The current Azure session, if available. If the user is not signed in, or there are no tenants, + * an error message is returned. + */ + public async getAuthSession(options?: GetAuthSessionOptions): Promise> { + await this.initializePromise; + if (this.signInStatusValue !== SignInStatus.SignedIn) { + return { succeeded: false, error: l10n.t(UiStrings.NotSignInStatus, this.signInStatusValue) }; + } + + if (this.tenants.length === 0) { + return { succeeded: false, error: UiStrings.NoTenantFound }; + } + + if (!this.selectedTenantValue) { + if (this.tenants.length > 1) { + const selectedTenant = await AzureAuth.quickPickTenant(this.tenants); + if (!selectedTenant) { + return { succeeded: false, error: UiStrings.NoTenantSelected }; + } + + this.selectedTenantValue = selectedTenant; + } else { + this.selectedTenantValue = this.tenants[0]; + } + } + + // Get a session for a specific tenant. + const tenantId = this.selectedTenantValue.id; + const scopes = AzureAuth.getScopes(tenantId, options || {}); + return await this.getArmSession(tenantId, scopes, AuthScenario.GetSession); + } + + private async getNewSelectedTenant( + newTenants: Tenant[], + currentSelectedTenant: Tenant | null, + authScenario: AuthScenario, + ): Promise { + // For sign-in we ignore the current selected tenant because the user must be able to change it. + // For all other scenarios, we prefer to retain the current selected tenant if it is still valid. + const ignoreCurrentSelection = authScenario === AuthScenario.SignIn; + if (!ignoreCurrentSelection && currentSelectedTenant !== null) { + const isCurrentSelectedTenantValid = newTenants.some((t) => t.id === currentSelectedTenant.id); + if (isCurrentSelectedTenantValid) { + return currentSelectedTenant; + } + } + + // For sign-in, if there are multiple tenants, we should prompt the user to select one. + if (authScenario === AuthScenario.SignIn && newTenants.length > 1) { + return null; + } + + // For all other (non-interactive) scenarios, see if we can determine a default tenant to use. + const defaultTenant = await this.getDefaultTenantId(newTenants); + return defaultTenant; + } + + private async getDefaultTenantId(tenants: Tenant[]): Promise { + if (tenants.length === 1) { + return tenants[0]; + } + + // It may be the case that the user has access to multiple tenants, but only has a valid token for one of them. + // This might happen if the user has signed in to one recently, but not the others. In this case, we would want + // to default to the tenant that the user has a valid token for. + // Use the 'Initialization' scenario to ensure this is silent (no user interaction). + const getSessionPromises = tenants.map((t) => + this.getArmSession(t.id, AzureAuth.getScopes(t.id, {}), AuthScenario.Initialization), + ); + const results = await Promise.all(getSessionPromises); + const accessibleTenants = results.filter(GeneralUtils.succeeded).map((r) => r.result); + return accessibleTenants.length === 1 ? AzureAccount.findTenant(tenants, accessibleTenants[0].tenantId) : null; + } + + private async getArmSession( + tenantId: string, + scopes: string[], + authScenario: AuthScenario, + ): Promise> { + this.handleSessionChanges = false; + try { + let options: AuthenticationGetSessionOptions; + let silentFirst = false; + switch (authScenario) { + case AuthScenario.Initialization: + options = { createIfNone: false, clearSessionPreference: false, silent: true }; + break; + case AuthScenario.SignIn: + options = { createIfNone: true, clearSessionPreference: true, silent: false }; + break; + case AuthScenario.GetSession: + // the 'createIfNone' option cannot be used with 'silent', but really we want both + // flags here (i.e. create a session silently, but do create one if it doesn't exist). + // To allow this, we first try to get a session silently. + silentFirst = true; + options = { createIfNone: true, clearSessionPreference: false, silent: false }; + break; + } + + let session: AuthenticationSession | undefined; + if (silentFirst) { + // The 'silent' option is incompatible with most other options, so we completely replace the options object here. + session = await authentication.getSession("microsoft", scopes, { silent: true }); + } + + if (!session) { + session = await authentication.getSession("microsoft", scopes, options); + } + + if (!session) { + return { succeeded: false, error: UiStrings.NoAzureSessionFound }; + } + + return { succeeded: true, result: Object.assign(session, { tenantId }) }; + } catch (e) { + return { succeeded: false, error: l10n.t(UiStrings.FailedTo, GeneralUtils.getErrorMessage(e)) }; + } finally { + this.handleSessionChanges = true; + } + } + } + +} diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index caf3fedb..9a3ff1ab 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -3,6 +3,8 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fs from "fs-extra"; +import * as http from 'http'; +import * as https from 'https'; import * as path from "path"; import * as vscode from "vscode"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; @@ -10,24 +12,42 @@ import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; import { TelemetryClient } from '../common/telemetryClient'; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { ApiDefinitionTreeItem, fetchApiCenterServer } from "../tree/DataPlaneAccount"; import { createTemporaryFolder } from "../utils/fsUtil"; +import { getSessionToken } from "./workspaceApis"; export namespace ExportAPI { export async function exportApi( context: IActionContext, - node?: ApiVersionDefinitionTreeItem): Promise { + node?: ApiVersionDefinitionTreeItem | ApiDefinitionTreeItem): 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); + if (node instanceof ApiVersionDefinitionTreeItem) { + 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); + } else if (node instanceof ApiDefinitionTreeItem) { + let accessToken = await getSessionToken(node.account.clientId, node.account.tenantId); + if (accessToken) { + let server = new fetchApiCenterServer(node.account.domain, accessToken); + let results = await server.exportDefinitionLink(node.apiName, node.apiVersion, node.label); + if (results) { + const folderName = `${node.apiCenterName}-${node.apiName}`; + const folderPath = await createTemporaryFolder(folderName); + const localFilePath: string = path.join(folderPath, node.label); + await fs.ensureFile(localFilePath); + await downloadFile(results, localFilePath); + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); + await vscode.window.showTextDocument(document); + } + } + } } function getFolderName(treeItem: ApiVersionDefinitionTreeItem): string { @@ -47,6 +67,32 @@ export namespace ExportAPI { } } + async function downloadFile(url: string, filePath: string): Promise { + return new Promise((resolve, reject) => { + const client = url.startsWith('https') ? https : http; + + client.get(url, (response) => { + if (response.statusCode !== 200) { + reject(new Error(`request failed with status code: ${response.statusCode}`)); + return; + } + + const fileStream = fs.createWriteStream(filePath); + + response.pipe(fileStream); + + fileStream.on('finish', () => { + console.log('finished!!!!!!!!'); + fileStream.close(); + resolve(); + }); + + }).on('error', (err) => { + reject(new Error('download error: ' + err.message)); + }); + }); + } + export async function showTempFile(node: ApiVersionDefinitionTreeItem, fileContent: string) { const folderName = getFolderName(node); const folderPath = await createTemporaryFolder(folderName); diff --git a/src/commands/removeDataplaneApi.ts b/src/commands/removeDataplaneApi.ts new file mode 100644 index 00000000..0792f5f3 --- /dev/null +++ b/src/commands/removeDataplaneApi.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from "vscode"; +import { exitFromSession } from "../commands/workspaceApis"; +import { ext } from "../extensionVariables"; +import { ApiServerItem } from "../tree/DataPlaneAccount"; + +export async function removeDataplaneAPI(context: IActionContext, node: ApiServerItem) { + let accounts = ext.dataPlaneAccounts; + let indexToRemove = accounts.findIndex(account => + account.domain === node.apiAccount.domain && + account.tenantId === node.apiAccount.tenantId && + account.clientId === node.apiAccount.clientId + ); + if (indexToRemove !== -1) { + accounts.splice(indexToRemove, 1); + } + let isExist = accounts.some(account => + account.tenantId === node.apiAccount.tenantId && account.clientId === node.apiAccount.clientId + ); + if (!isExist) { + await exitFromSession(node.apiAccount.clientId, node.apiAccount.tenantId); + } + vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh'); +} diff --git a/src/commands/workspaceApis.ts b/src/commands/workspaceApis.ts new file mode 100644 index 00000000..de1b6501 --- /dev/null +++ b/src/commands/workspaceApis.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from 'vscode'; +import { DataPlaneAccount, ext } from "../extensionVariables"; +export async function getDataPlaneApis(context: IActionContext): Promise { + const endpointUrl = await vscode.window.showInputBox({ title: "Input Runtime URL", ignoreFocusOut: true }); + const clientid = await vscode.window.showInputBox({ title: "Input Client ID", ignoreFocusOut: true }); + const tenantid = await vscode.window.showInputBox({ title: "Input Tenant ID", ignoreFocusOut: true }); + if (!endpointUrl || !clientid || !tenantid) { + return; + } + // return await getSessionToken(clientid, tenantid); + return setAccountToExt(endpointUrl, clientid, tenantid); +} +export function setAccountToExt(domain: string, clientId: string, tenantId: string) { + function pushIfNotExist(array: DataPlaneAccount[], element: DataPlaneAccount) { + if (!array.some(item => item.domain === element.domain)) { + array.push(element); + } + } + pushIfNotExist(ext.dataPlaneAccounts, { domain: domain, tenantId: tenantId, clientId: clientId }); +} + +export async function getSessionToken(clientId: string, tenantId: string) { + const session = await vscode.authentication.getSession('microsoft', [ + `VSCODE_CLIENT_ID:${clientId}`, // Replace by your client id + `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant + "offline_access", // Required for the refresh token. + "https://azure-apicenter.net/user_impersonation" + ], { createIfNone: true }); + if (session?.accessToken) { + return session.accessToken; + } else { + vscode.window.showErrorMessage("Please login your Microsoft Account first!"); + } +} + +export async function exitFromSession(clientId: string, tenantId: string) { + const session = await vscode.authentication.getSession('microsoft', [ + `VSCODE_CLIENT_ID:${clientId}`, // Replace by your client id + `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant + "offline_access", // Required for the refresh token. + "https://azure-apicenter.net/user_impersonation" + ], { clearSessionPreference: true }); + if (session?.accessToken) { + return session.accessToken; + } else { + vscode.window.showErrorMessage("Please login your Microsoft Account first!"); + } +} diff --git a/src/extension.ts b/src/extension.ts index b94d132a..1d1a7314 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,13 +34,16 @@ import { renameCustomFunction } from './commands/rules/renameCustomFunction'; import { searchApi } from './commands/searchApi'; import { setApiRuleset } from './commands/setApiRuleset'; import { testInPostman } from './commands/testInPostman'; -import { ErrorProperties, TelemetryProperties } from './common/telemetryEvent'; import { doubleClickDebounceDelay, selectedNodeKey } from './constants'; import { ext } from './extensionVariables'; import { ApiVersionDefinitionTreeItem } from './tree/ApiVersionDefinitionTreeItem'; import { createAzureAccountTreeItem } from "./tree/AzureAccountTreeItem"; import { OpenApiEditor } from './tree/Editors/openApi/OpenApiEditor'; - +// Copilot Chat +import { removeDataplaneAPI } from './commands/removeDataplaneApi'; +import { exitFromSession, getDataPlaneApis, setAccountToExt } from "./commands/workspaceApis"; +import { ErrorProperties, TelemetryProperties } from './common/telemetryEvent'; +import { ApiDefinitionTreeItem, DataPlanAccountManagerTreeItem } from './tree/DataPlaneAccount'; export async function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "azure-api-center" is now active!'); @@ -59,6 +62,9 @@ export async function activate(context: vscode.ExtensionContext) { const azureAccountTreeItem = createAzureAccountTreeItem(sessionProvider); context.subscriptions.push(azureAccountTreeItem); + ext.treeItem = azureAccountTreeItem; + // var a = ext.treeItem.subscription; + ext.dataPlaneAccounts = []; const treeDataProvider = new AzExtTreeDataProvider(azureAccountTreeItem, "appService.loadMore"); ext.treeItem = azureAccountTreeItem; @@ -68,6 +74,17 @@ export async function activate(context: vscode.ExtensionContext) { const treeView = vscode.window.createTreeView("apiCenterTreeView", { treeDataProvider }); context.subscriptions.push(treeView); + // DataPlaneSessionProviderHelper.activateAzureSessionProvider(context); + // const dataPlaneSessionProvider = AzureSessionProviderHelper.getSessionProvider(); + const dataPlanAccountManagerTreeItem = new DataPlanAccountManagerTreeItem(); + // context.subscriptions.push(dataPlanAccountManagerTreeItem); + ext.workspaceItem = dataPlanAccountManagerTreeItem; + + const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); + ext.workspaceProvider = workspaceTreeDataProvider; + + vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); + treeView.onDidChangeSelection((e: vscode.TreeViewSelectionChangeEvent) => { const selectedNode = e.selection[0]; ext.outputChannel.appendLine(selectedNode.id!); @@ -79,7 +96,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.exportApi', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem) => { await ExportAPI.exportApi(context, node); }); + registerCommandWithTelemetry('azure-api-center.exportApi', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem | ApiDefinitionTreeItem) => { await ExportAPI.exportApi(context, node); }); // TODO: move this to a separate file const openApiEditor: OpenApiEditor = new OpenApiEditor(); @@ -139,6 +156,40 @@ export async function activate(context: vscode.ExtensionContext) { registerCommandWithTelemetry('azure-api-center.openUrl', async (context: IActionContext, node?: AzExtTreeItem) => { await openUrlFromTreeNode(context, node); }); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.refresh', async (context: IActionContext) => ext.workspaceItem.refresh(context)); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.addApis', async (context: IActionContext) => { + await getDataPlaneApis(context); + ext.workspaceItem.refresh(context); + }); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.logout', async (context: IActionContext) => { + if (ext.dataPlaneAccounts) { + for (let account of ext.dataPlaneAccounts) { + await exitFromSession(account.clientId, account.tenantId); + } + } + vscode.commands.executeCommand('workbench.actions.treeView.apiCenterWorkspace.collapseAll'); + ext.workspaceItem.refresh(context); + }); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.collapse', () => { + vscode.commands.executeCommand('workbench.actions.treeView.apiCenterWorkspace.collapseAll'); + }); + + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.removeApi', removeDataplaneAPI); + + const handleUri = async (uri: vscode.Uri) => { + const queryParams = new URLSearchParams(uri.query); + let tenantId = queryParams.get('tenantId') as string; + let clientId = queryParams.get('clientId') as string; + let runtimeUrl = queryParams.get('runtimeUrl') as string; + setAccountToExt(runtimeUrl, clientId, tenantId); + vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh') + }; + + context.subscriptions.push( + vscode.window.registerUriHandler({ + handleUri + }) + ); } async function registerCommandWithTelemetry(commandId: string, callback: CommandCallback, debounce?: number): Promise { diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 08bc6959..4291acfc 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -3,11 +3,18 @@ import { AzExtParentTreeItem, AzExtTreeDataProvider, IAzExtOutputChannel } from "@microsoft/vscode-azext-utils"; import { ExtensionContext } from "vscode"; import { ApiVersionDefinitionTreeItem } from "./tree/ApiVersionDefinitionTreeItem"; +import { DataPlanAccountManagerTreeItem } from "./tree/DataPlaneAccount"; import { OpenApiEditor } from "./tree/Editors/openApi/OpenApiEditor"; - /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts */ + +export interface DataPlaneAccount { + readonly domain: string; + readonly tenantId: string; + readonly clientId: string; +} + export namespace ext { export let prefix: string = 'azureAPICenter'; @@ -17,4 +24,8 @@ export namespace ext { export let outputChannel: IAzExtOutputChannel; export let openApiEditor: OpenApiEditor; export let selectedApiVersionDefinitionTreeItem: ApiVersionDefinitionTreeItem; + + export let dataPlaneAccounts: DataPlaneAccount[]; + export let workspaceProvider: AzExtTreeDataProvider; + export let workspaceItem: DataPlanAccountManagerTreeItem; } diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts new file mode 100644 index 00000000..08511ef4 --- /dev/null +++ b/src/tree/DataPlaneAccount.ts @@ -0,0 +1,352 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; +import * as vscode from "vscode"; +import { SignInStatus } from "../azure/azureLogin/authTypes"; +import { getSessionToken } from "../commands/workspaceApis"; +import { DataPlaneAccount, ext } from "../extensionVariables"; +import { UiStrings } from "../uiStrings"; +import { treeUtils } from "../utils/treeUtils"; + +// export function createDataPlaneAccountTreeItem( +// sessionProvider: AzureSessionProvider, +// ): DataPlanAccountManagerTreeItem { +// return new DataPlanAccountManagerTreeItem(sessionProvider); +// } + +export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { + public contextValue: string = DataPlanAccountManagerTreeItem.contextValue; + private signInStatus: SignInStatus = SignInStatus.Initializing; + constructor() { + super(undefined); + this.autoSelectInTreeItemPicker = true; + + // const onStatusChange = this.sessionProvider.signInStatusChangeEvent; + // registerEvent("azureAccountDataPlaneTreeItem.onSignInStatusChange", onStatusChange, (context) => this.refresh(context)); + } + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + const accounts = ext.dataPlaneAccounts; + return await this.createTreeItemsWithErrorHandling( + accounts, + 'inValidResource', + async account => new ApiServerItem(this, account), + account => account.domain.split('0')[0] + ) + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + public static contextValue: string = "APICenterWorkspaceDataPlane-AccountManager"; + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("versions"); + } + + public get label(): string { + return "dataPlaneAccount"; + } + + public resetStatus(): void { + this.signInStatus = SignInStatus.SignedOut; + } +} + +export class ApiServerItem extends AzExtParentTreeItem { + public label: string; + public readonly apisTreeItem: ApiTreesItem; + public readonly apiAccount: DataPlaneAccount; + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + return [this.apisTreeItem] + } + public hasMoreChildrenImpl(): boolean { + return false; + } + constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount) { + super(parent); + this.label = account.domain.split('.')[0]; + this.apiAccount = account; + this.apisTreeItem = new ApiTreesItem(this, account); + } + public get id(): string { + return this.label; + } + public contextValue: string = ApiServerItem.contextValue; + public static contextValue: string = "WorkspaceAPICenter-Server"; + public get iconPath(): TreeItemIconPath { + return treeUtils.getIconPath('apiCenter'); + } +} + +export class ApiTreesItem extends AzExtParentTreeItem { + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + const apis = await this.getApis(); + return await this.createTreeItemsWithErrorHandling( + apis, + 'invalidResource', + async apic => new ApiTreeItem(this, this.account, this.label, apic), + apic => apic.name + ); + } + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + public static contextValue: string = "workspaceApiCenterApis"; + public contextValue: string = ApiTreesItem.contextValue; + private _nextLink: string | undefined; + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("library"); + } + public get label(): string { + return UiStrings.TreeitemLabelApis; + } + constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount) { + super(parent); + } + private async getApis(): Promise { + let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); + if (accessToken) { + let server = new fetchApiCenterServer(this.account.domain, accessToken); + const res = await server.getApis(); + if (res) { + this._nextLink = res.nextLink; + return res.value; + } + } + return []; + } +} + +export class ApiTreeItem extends AzExtParentTreeItem { + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + return [this.apiVersionsTreeItem] + } + public hasMoreChildrenImpl(): boolean { + return false; + } + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("library"); + } + public label: string; + public contextValue: string; + public readonly apiVersionsTreeItem: ApiVersionsTreeItem; + constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount, apiName: string, apiCenter: ApiCenter) { + super(parent); + this.label = apiCenter.name; + this.contextValue = ApiTreeItem.contextValue; + this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, account, apiName, apiCenter); + } + public static contextValue: string = "WorkspaceAPICenter-API"; +} + +export class ApiVersionsTreeItem extends AzExtParentTreeItem { + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + const versions = await this.getVersions(); + return await this.createTreeItemsWithErrorHandling( + versions, + 'invalidResource', + async version => new ApiVersionTreeItem(this, this.account, this._apiCenterName, this._apiCenterApi.name, version), + version => version.name + ); + } + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("versions"); + } + public static contextValue: string = "workspaceApiCenterApiVersions"; + public readonly contextValue: string = ApiVersionsTreeItem.contextValue; + public get label(): string { + return UiStrings.TreeitemLabelVersions; + } + private readonly _apiCenterName: string; + private readonly _apiCenterApi: ApiCenter; + private _nextLink: string | undefined; + constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiCenterName: string, apiCenterApi: ApiCenter) { + super(parent); + this._apiCenterName = apiCenterName; + this._apiCenterApi = apiCenterApi; + } + private async getVersions(): Promise { + let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); + if (accessToken) { + let server = new fetchApiCenterServer(this.account.domain, accessToken); + let res = await server.getVersions(this._apiCenterApi.name); + if (res) { + this._nextLink = res.nextLink; + return res.value; + } + } + return []; + } +} +export class ApiVersionTreeItem extends AzExtParentTreeItem { + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + return [this.apiVersionDefinitionsTreeItem]; + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + public static contextValue: string = "WorkspaceAPICenter-Version"; + public label: string; + public contextValue: string; + public readonly apiVersionDefinitionsTreeItem: ApiDefinitionsTreeItem; + constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount, apiServerName: string, apiName: string, apiVersion: ApiVersion) { + super(parent); + this.apiVersionDefinitionsTreeItem = new ApiDefinitionsTreeItem(this, account, apiServerName, apiName, apiVersion); + this.label = apiVersion.name; + this.contextValue = ApiVersionTreeItem.contextValue; + } + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("versions"); + } +} +export class ApiDefinitionsTreeItem extends AzExtParentTreeItem { + public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + const definitions = await this.getDefinitions(); + return await this.createTreeItemsWithErrorHandling( + definitions, + 'invalidResource', + async definition => new ApiDefinitionTreeItem(this, this.account, this._apiCenterName, this._apiCenterApiName, this._apiCenterApiVersion.name, definition), + definition => definition.name + ); + } + public static contextValue: string = "workspaceApiCenterApiVersionDefinitions"; + public readonly contextValue: string = ApiDefinitionsTreeItem.contextValue; + private readonly _apiCenterName: string; + private readonly _apiCenterApiName: string; + private readonly _apiCenterApiVersion: ApiVersion; + private _nextLink: string | undefined; + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("list-selection"); + } + constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiService: string, apiName: string, apiVersion: ApiVersion) { + super(parent); + this._apiCenterName = apiService; + this._apiCenterApiName = apiName; + this._apiCenterApiVersion = apiVersion; + } + + public get label(): string { + return UiStrings.TreeitemLabelDefinitions; + } + private async getDefinitions(): Promise { + let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); + if (accessToken) { + let server = new fetchApiCenterServer(this.account.domain, accessToken); + let res = await server.getDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); + if (res) { + this._nextLink = res.nextLink; + return res.value; + } + } + return []; + } +} +export class ApiDefinitionTreeItem extends AzExtTreeItem { + public readonly label: string; + public contextValue: string; + public readonly apiCenterName: string; + public readonly apiName: string; + public readonly apiVersion: string; + constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiCenterName: string, apiName: string, apiVersion: string, definition: ApiDefinitions) { + super(parent); + this.label = definition.name; + this.apiCenterName = apiCenterName; + this.apiName = apiName; + this.apiVersion = apiVersion; + this.contextValue = ApiDefinitionTreeItem.contextValue; + } + public static contextValue: string = "WorkspaceAPICenter-Definition"; + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("list-selection"); + } +} + +export type ApiServer = { + name: string; +} + +export type ApiCenter = { + name: string; + title: string; + kind: string; + lifecycleStage: string; + externalDocumentation: []; + contacts: []; + customProperties: {}; +}; + +export type ApiVersion = { + name: string; + title: string; + lifecycleStage: string; +} + +export type ApiDefinitions = { + name: string; + title: string; + specification: { + name: string; + } +} + +export enum Method { + GET = "GET", + POST = "POST", +} + +export class fetchApiCenterServer { + constructor(private domain: string, private accessToken: string) { + } + private async requestApis(queryString: string, method: Method = Method.GET) { + const requestUrl = `https://${this.domain}/workspaces/default/${queryString}`; + + const headers = { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + this.accessToken, + }; + + const response = await fetch(requestUrl, { method: method, headers: headers }); + + if (response.status === 401 || response.status == 403) { + return; + } else if (!response.ok) { + return; + } + + const dataJson = await response.json(); + return dataJson; + } + public async getApis(queryString?: string): Promise<{ value: ApiCenter[]; nextLink: string }> { + return queryString ? await this.requestApis(`apis?${queryString}`) : await this.requestApis(`apis`); + } + + public async getApi(id: string): Promise<{ value: ApiCenter[]; nextLink: string }> { + return await this.requestApis(`apis/${id}`); + } + + public async getVersions(apiId: string): Promise<{ value: ApiVersion[]; nextLink: string }> { + return await this.requestApis(`apis/${apiId}/versions`); + } + + public async getDeployments(apiId: string) { + return await this.requestApis(`apis/${apiId}/deployments`); + } + + public async getDefinitions(apiId: string, version: string): Promise<{ value: ApiDefinitions[]; nextLink: string }> { + return await this.requestApis(`apis/${apiId}/versions/${version}/definitions`); + } + + public async exportDefinitionLink(apiName: string, apiVersion: string, definitionName: string) { + return await this.requestApis( + `apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification`, + Method.POST + ); + } +} diff --git a/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index e1071587..fd19e51c 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -8,7 +8,6 @@ import { showSavePromptConfigKey } from "../../../constants"; import { localize } from "../../../localize"; import { ApiVersionDefinitionTreeItem } from "../../ApiVersionDefinitionTreeItem"; import { Editor, EditorOptions } from "../Editor"; - export class OpenApiEditor extends Editor { constructor() { super(showSavePromptConfigKey); diff --git a/tsconfig.json b/tsconfig.json index b75962be..557f2775 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "module": "commonjs", "target": "ES2020", "lib": [ - "ES2020" + "ES2020", + "DOM" ], "sourceMap": true, "rootDir": "src", From 342997284489d3c92ec5271609aa7a663e5f23f6 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Tue, 16 Jul 2024 13:41:32 +0800 Subject: [PATCH 02/29] feat: support api server --- src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts | 94 +++++++++++++++++++ src/azure/ApiCenter/ApiCenterRestAPIs.ts | 3 + src/commands/workspaceApis.ts | 3 +- src/extensionVariables.ts | 7 +- src/tree/DataPlaneAccount.ts | 9 +- 5 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts diff --git a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts new file mode 100644 index 00000000..f593a9b2 --- /dev/null +++ b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as vscode from 'vscode'; +export interface DataPlaneAccount { + readonly domain: string; + readonly tenantId: string; + readonly clientId: string; +} +enum Method { + GET = "GET", + POST = "POST", +} +export class ApiCenterDataPlaneService { + private account: DataPlaneAccount; + constructor(domain: string, clientId: string, tenantId: string) { + this.account = { + domain: domain, + clientId: clientId, + tenantId: tenantId + } + }; + private async getDataPlaneToken(): Promise { + const session = await vscode.authentication.getSession('microsoft', [ + `VSCODE_CLIENT_ID:${this.account.clientId}`, // Replace by your client id + `VSCODE_TENANT:${this.account.tenantId}`, // Replace with the tenant ID or common if multi-tenant + "offline_access", // Required for the refresh token. + "https://azure-apicenter.net/user_impersonation" + ], { createIfNone: true }); + if (session?.accessToken) { + return session.accessToken; + } else { + throw new Error('sign in failed'); + } + }; + private async exitFromSession() { + const session = await vscode.authentication.getSession('microsoft', [ + `VSCODE_CLIENT_ID:${this.account.clientId}`, // Replace by your client id + `VSCODE_TENANT:${this.account.tenantId}`, // Replace with the tenant ID or common if multi-tenant + "offline_access", // Required for the refresh token. + "https://azure-apicenter.net/user_impersonation" + ], { clearSessionPreference: true }); + if (session?.accessToken) { + return session.accessToken; + } else { + vscode.window.showErrorMessage("Please login your Microsoft Account first!"); + } + }; + private async requestApis(queryString: string, method: Method = Method.GET) { + const requestUrl = `https://${this.account.domain}/workspaces/default/${queryString}`; + const accessToken = await this.getDataPlaneToken(); + const headers = { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }; + + const response = await fetch(requestUrl, { method: method, headers: headers }); + + if (response.status === 401 || response.status == 403) { + return; + } else if (!response.ok) { + return; + } + + const dataJson = await response.json(); + return dataJson; + }; + public async getApis(queryString?: string): Promise<{ value: ApiCenter[]; nextLink: string }> { + return queryString ? await this.requestApis(`apis?${queryString}`) : await this.requestApis(`apis`); + } + + public async getApi(id: string): Promise<{ value: ApiCenter[]; nextLink: string }> { + return await this.requestApis(`apis/${id}`); + } + + public async getVersions(apiId: string): Promise<{ value: ApiVersion[]; nextLink: string }> { + return await this.requestApis(`apis/${apiId}/versions`); + } + + public async getDeployments(apiId: string) { + return await this.requestApis(`apis/${apiId}/deployments`); + } + + public async getDefinitions(apiId: string, version: string): Promise<{ value: ApiDefinitions[]; nextLink: string }> { + return await this.requestApis(`apis/${apiId}/versions/${version}/definitions`); + } + + public async exportDefinitionLink(apiName: string, apiVersion: string, definitionName: string) { + return await this.requestApis( + `apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification`, + Method.POST + ); + } +} diff --git a/src/azure/ApiCenter/ApiCenterRestAPIs.ts b/src/azure/ApiCenter/ApiCenterRestAPIs.ts index 503c6fa5..507c9cd8 100644 --- a/src/azure/ApiCenter/ApiCenterRestAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterRestAPIs.ts @@ -22,3 +22,6 @@ export const APICenterRestAPIs = { ImportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/importRuleset?api-version=${restApiVersion}`, ExportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/exportRuleset?api-version=${restApiVersion}`, }; + +export const APICenterDataPlaneRestAPIs = { +} diff --git a/src/commands/workspaceApis.ts b/src/commands/workspaceApis.ts index de1b6501..717ed2dd 100644 --- a/src/commands/workspaceApis.ts +++ b/src/commands/workspaceApis.ts @@ -2,7 +2,8 @@ // Licensed under the MIT license. import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { DataPlaneAccount, ext } from "../extensionVariables"; +import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { ext } from "../extensionVariables"; export async function getDataPlaneApis(context: IActionContext): Promise { const endpointUrl = await vscode.window.showInputBox({ title: "Input Runtime URL", ignoreFocusOut: true }); const clientid = await vscode.window.showInputBox({ title: "Input Client ID", ignoreFocusOut: true }); diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 4291acfc..cbc6f152 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeDataProvider, IAzExtOutputChannel } from "@microsoft/vscode-azext-utils"; import { ExtensionContext } from "vscode"; +import { DataPlaneAccount } from "./azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiVersionDefinitionTreeItem } from "./tree/ApiVersionDefinitionTreeItem"; import { DataPlanAccountManagerTreeItem } from "./tree/DataPlaneAccount"; import { OpenApiEditor } from "./tree/Editors/openApi/OpenApiEditor"; @@ -9,12 +10,6 @@ import { OpenApiEditor } from "./tree/Editors/openApi/OpenApiEditor"; * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts */ -export interface DataPlaneAccount { - readonly domain: string; - readonly tenantId: string; - readonly clientId: string; -} - export namespace ext { export let prefix: string = 'azureAPICenter'; diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts index 08511ef4..066404bb 100644 --- a/src/tree/DataPlaneAccount.ts +++ b/src/tree/DataPlaneAccount.ts @@ -2,18 +2,13 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; +import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { SignInStatus } from "../azure/azureLogin/authTypes"; import { getSessionToken } from "../commands/workspaceApis"; -import { DataPlaneAccount, ext } from "../extensionVariables"; +import { ext } from "../extensionVariables"; import { UiStrings } from "../uiStrings"; import { treeUtils } from "../utils/treeUtils"; -// export function createDataPlaneAccountTreeItem( -// sessionProvider: AzureSessionProvider, -// ): DataPlanAccountManagerTreeItem { -// return new DataPlanAccountManagerTreeItem(sessionProvider); -// } - export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { public contextValue: string = DataPlanAccountManagerTreeItem.contextValue; private signInStatus: SignInStatus = SignInStatus.Initializing; From b187b5aa138463bf920cc6027108cc8300a1a5de Mon Sep 17 00:00:00 2001 From: wenyutang Date: Wed, 17 Jul 2024 07:56:39 +0800 Subject: [PATCH 03/29] feat: update --- package-lock.json | 6 +- package.json | 19 +- src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts | 122 +++--- src/azure/ApiCenter/ApiCenterRestAPIs.ts | 7 + src/azure/ApiCenter/ApiCenterService.ts | 37 +- src/azure/ApiCenter/contracts.ts | 34 ++ .../ResourceGraph/ResourceGraphService.ts | 2 +- src/azure/ResourceGraph/contracts.ts | 13 - src/azure/azureLogin/authTypes.ts | 9 +- src/azure/azureLogin/azureAuth.ts | 13 + .../azureLogin/dataPlaneSessionProvider.ts | 257 ------------- src/azure/azureLogin/dataSessionProvider.ts | 138 +++++++ src/commands/addDataPlaneApis.ts | 31 ++ src/commands/exportApi.ts | 35 +- src/commands/handleUri.ts | 12 + .../registerStepByStep.ts | 3 + src/commands/removeDataplaneApi.ts | 14 +- src/commands/signInToDataPlane.ts | 13 + src/commands/workspaceApis.ts | 52 --- src/extension.ts | 47 +-- src/extensionVariables.ts | 3 +- src/tree/ApiCenterTreeItem.ts | 2 +- src/tree/ApiTreeItem.ts | 18 +- src/tree/ApiVersionDefinitionTreeItem.ts | 15 +- src/tree/ApiVersionDefinitionsTreeItem.ts | 36 +- src/tree/ApiVersionTreeItem.ts | 10 +- src/tree/ApiVersionsTreeItem.ts | 30 +- src/tree/ApisTreeItem.ts | 30 +- src/tree/DataPlaneAccount.ts | 352 +++--------------- tsconfig.json | 3 +- 30 files changed, 507 insertions(+), 856 deletions(-) delete mode 100644 src/azure/ResourceGraph/contracts.ts delete mode 100644 src/azure/azureLogin/dataPlaneSessionProvider.ts create mode 100644 src/azure/azureLogin/dataSessionProvider.ts create mode 100644 src/commands/addDataPlaneApis.ts create mode 100644 src/commands/handleUri.ts create mode 100644 src/commands/signInToDataPlane.ts delete mode 100644 src/commands/workspaceApis.ts diff --git a/package-lock.json b/package-lock.json index d241d136..9adaa908 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1640,9 +1640,9 @@ } }, "node_modules/@types/vscode": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz", - "integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==", + "version": "1.91.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.91.0.tgz", + "integrity": "sha512-PgPr+bUODjG3y+ozWUCyzttqR9EHny9sPAfJagddQjDwdtf66y2sDKJMnFZRuzBA2YtBGASqJGPil8VDUPvO6A==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { diff --git a/package.json b/package.json index fb78b839..026c9f28 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ }, { "command": "azure-api-center.apiCenterWorkspace.addApis", - "title": "Add Api", + "title": "Add Data Plane Api", "icon": "$(add)", "category": "Azure API Center" }, @@ -177,12 +177,6 @@ "icon": "$(refresh)", "category": "Azure API Center" }, - { - "command": "azure-api-center.apiCenterWorkspace.logout", - "title": "Sign out", - "icon": "$(sign-out)", - "category": "Azure API Center" - }, { "command": "azure-api-center.apiCenterWorkspace.collapse", "title": "Collapse", @@ -206,7 +200,7 @@ }, { "id": "apiCenterWorkspace", - "name": "API Center Workspace", + "name": "API Center Data View", "visibility": "visible" } ] @@ -237,11 +231,6 @@ "when": "view == apiCenterWorkspace", "group": "navigation@1" }, - { - "command": "azure-api-center.apiCenterWorkspace.logout", - "when": "view == apiCenterWorkspace", - "group": "navigation@0" - }, { "command": "azure-api-center.apiCenterWorkspace.collapse", "when": "view == apiCenterWorkspace", @@ -276,7 +265,7 @@ }, { "command": "azure-api-center.exportApi", - "when": "(view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/) || (view == apiCenterWorkspace && viewItem == WorkspaceAPICenter-Definition)" + "when": "(view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/) || (view == apiCenterWorkspace && viewItem =~ /azureApiCenterApiVersionDataPlaneDefinitionTreeItem/)" }, { "command": "azure-api-center.showOpenApi", @@ -335,7 +324,7 @@ }, { "command": "azure-api-center.apiCenterWorkspace.removeApi", - "when": "view == apiCenterWorkspace && viewItem == WorkspaceAPICenter-Server", + "when": "view == apiCenterWorkspace && viewItem == azureApiCenterDataPlane", "group": "inline@0" } ], diff --git a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts index f593a9b2..6f06b0b0 100644 --- a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts @@ -1,94 +1,58 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import * as vscode from 'vscode'; +import { RequestPrepareOptions, ServiceClient } from "@azure/ms-rest-js"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { ApiCenterApiVersionDefinitionExport, DataPlaneApiCenterApi, DataPlaneApiCenterApiVersion, DataPlaneApiCenterApiVersionDefinition } from "../ApiCenter/contracts"; +import { APICenterDataPlaneRestAPIs } from "./ApiCenterRestAPIs"; export interface DataPlaneAccount { readonly domain: string; readonly tenantId: string; readonly clientId: string; } -enum Method { - GET = "GET", - POST = "POST", -} export class ApiCenterDataPlaneService { - private account: DataPlaneAccount; - constructor(domain: string, clientId: string, tenantId: string) { - this.account = { - domain: domain, - clientId: clientId, - tenantId: tenantId - } + private susbcriptionContext: ISubscriptionContext; + constructor(susbcriptionContext: ISubscriptionContext) { + this.susbcriptionContext = susbcriptionContext }; - private async getDataPlaneToken(): Promise { - const session = await vscode.authentication.getSession('microsoft', [ - `VSCODE_CLIENT_ID:${this.account.clientId}`, // Replace by your client id - `VSCODE_TENANT:${this.account.tenantId}`, // Replace with the tenant ID or common if multi-tenant - "offline_access", // Required for the refresh token. - "https://azure-apicenter.net/user_impersonation" - ], { createIfNone: true }); - if (session?.accessToken) { - return session.accessToken; - } else { - throw new Error('sign in failed'); - } + public async getApiCenterApis(): Promise<{ value: DataPlaneApiCenterApi[]; nextLink: string }> { + const client = new ServiceClient(this.susbcriptionContext.credentials); + let url = APICenterDataPlaneRestAPIs.ListApis(this.susbcriptionContext.subscriptionPath); + const options: RequestPrepareOptions = { + method: "GET", + url: url, + }; + const response = await client.sendRequest(options); + return response.parsedBody; }; - private async exitFromSession() { - const session = await vscode.authentication.getSession('microsoft', [ - `VSCODE_CLIENT_ID:${this.account.clientId}`, // Replace by your client id - `VSCODE_TENANT:${this.account.tenantId}`, // Replace with the tenant ID or common if multi-tenant - "offline_access", // Required for the refresh token. - "https://azure-apicenter.net/user_impersonation" - ], { clearSessionPreference: true }); - if (session?.accessToken) { - return session.accessToken; - } else { - vscode.window.showErrorMessage("Please login your Microsoft Account first!"); - } + public async getAPiCenterApiVersions(apiName: string): Promise<{ value: DataPlaneApiCenterApiVersion[]; nextLink: string }> { + const client = new ServiceClient(this.susbcriptionContext.credentials); + let url = APICenterDataPlaneRestAPIs.ListApiVersions(this.susbcriptionContext.subscriptionPath, apiName); + const options: RequestPrepareOptions = { + method: "GET", + url: url, + }; + const response = await client.sendRequest(options); + return response.parsedBody; }; - private async requestApis(queryString: string, method: Method = Method.GET) { - const requestUrl = `https://${this.account.domain}/workspaces/default/${queryString}`; - const accessToken = await this.getDataPlaneToken(); - const headers = { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: "Bearer " + accessToken, + public async getApiCenterApiDefinitions(apiName: string, apiVersion: string): Promise<{ value: DataPlaneApiCenterApiVersionDefinition[]; nextLink: string }> { + const client = new ServiceClient(this.susbcriptionContext.credentials); + let url = APICenterDataPlaneRestAPIs.ListApiDefinitions(this.susbcriptionContext.subscriptionPath, apiName, apiVersion); + const options: RequestPrepareOptions = { + method: "GET", + url: url, }; - - const response = await fetch(requestUrl, { method: method, headers: headers }); - - if (response.status === 401 || response.status == 403) { - return; - } else if (!response.ok) { - return; - } - - const dataJson = await response.json(); - return dataJson; + const response = await client.sendRequest(options); + return response.parsedBody; }; - public async getApis(queryString?: string): Promise<{ value: ApiCenter[]; nextLink: string }> { - return queryString ? await this.requestApis(`apis?${queryString}`) : await this.requestApis(`apis`); - } - - public async getApi(id: string): Promise<{ value: ApiCenter[]; nextLink: string }> { - return await this.requestApis(`apis/${id}`); - } - - public async getVersions(apiId: string): Promise<{ value: ApiVersion[]; nextLink: string }> { - return await this.requestApis(`apis/${apiId}/versions`); - } - - public async getDeployments(apiId: string) { - return await this.requestApis(`apis/${apiId}/deployments`); - } - - public async getDefinitions(apiId: string, version: string): Promise<{ value: ApiDefinitions[]; nextLink: string }> { - return await this.requestApis(`apis/${apiId}/versions/${version}/definitions`); - } - - public async exportDefinitionLink(apiName: string, apiVersion: string, definitionName: string) { - return await this.requestApis( - `apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification`, - Method.POST - ); + public async exportSpecification(apiName: string, + apiVersionName: string, + apiCenterApiVersionDefinitionName: string): Promise { + const client = new ServiceClient(this.susbcriptionContext.credentials); + const options: RequestPrepareOptions = { + method: "POST", + url: APICenterDataPlaneRestAPIs.ExportApiDefinitions(this.susbcriptionContext.subscriptionPath, apiName, apiVersionName, apiCenterApiVersionDefinitionName), + }; + const response = await client.sendRequest(options); + return response.parsedBody; } } diff --git a/src/azure/ApiCenter/ApiCenterRestAPIs.ts b/src/azure/ApiCenter/ApiCenterRestAPIs.ts index 507c9cd8..8fd40d1b 100644 --- a/src/azure/ApiCenter/ApiCenterRestAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterRestAPIs.ts @@ -24,4 +24,11 @@ export const APICenterRestAPIs = { }; export const APICenterDataPlaneRestAPIs = { + GetApi: (domain: string, apiName: string) => `https://${domain}/workspaces/default/apis/${apiName}`, + ListApis: (domain: string) => `https://${domain}/workspaces/default/apis`, + ListAllApis: (domain: string) => `https://${domain}/apis`, + GetApiVersion: (domain: string, apiName: string, versionName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${versionName}`, + ListApiVersions: (domain: string, apiName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions`, + ListApiDefinitions: (domain: string, apiName: string, apiVersion: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions`, + ExportApiDefinitions: (domain: string, apiName: string, apiVersion: string, definitionName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification` } diff --git a/src/azure/ApiCenter/ApiCenterService.ts b/src/azure/ApiCenter/ApiCenterService.ts index 82662ad5..7d80195f 100644 --- a/src/azure/ApiCenter/ApiCenterService.ts +++ b/src/azure/ApiCenter/ApiCenterService.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { HttpOperationResponse, RequestPrepareOptions, ServiceClient } from "@azure/ms-rest-js"; import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { getCredentialForToken } from "../../utils/credentialUtil"; import { APICenterRestAPIs } from "./ApiCenterRestAPIs"; import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport } from "./contracts"; @@ -19,8 +18,7 @@ export class ApiCenterService { } public async getApiCenter(): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIService(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -30,8 +28,7 @@ export class ApiCenterService { } public async getApiCenterApis(searchContent: string): Promise<{ value: ApiCenterApi[]; nextLink: string }> { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); let url = APICenterRestAPIs.ListAPIs(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion); if (searchContent) { url += `&$search=${searchContent}`; @@ -45,8 +42,7 @@ export class ApiCenterService { } public async getApiCenterEnvironments(): Promise<{ value: ApiCenterEnvironment[]; nextLink: string }> { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIEnvironments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -56,8 +52,7 @@ export class ApiCenterService { } public async getApiCenterApiVersions(apiName: string): Promise<{ value: ApiCenterApiVersion[]; nextLink: string }> { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIVersions(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -67,8 +62,7 @@ export class ApiCenterService { } public async getApiCenterApiDeployments(apiName: string): Promise<{ value: ApiCenterApiDeployment[]; nextLink: string }> { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDeployments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -78,8 +72,7 @@ export class ApiCenterService { } public async getApiCenterApiVersionDefinitions(apiName: string, apiVersion: string): Promise<{ value: ApiCenterApiVersionDefinition[]; nextLink: string }> { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersion, this.apiVersion) @@ -109,8 +102,7 @@ export class ApiCenterService { } public async createOrUpdateApi(apiCenterApi: ApiCenterApi): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPI(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiCenterApi.name, this.apiVersion), @@ -123,8 +115,7 @@ export class ApiCenterService { } public async createOrUpdateApiVersion(apiName: string, apiCenterApiVersion: ApiCenterApiVersion): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIVersion(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiVersion.name, this.apiVersion), @@ -138,8 +129,7 @@ export class ApiCenterService { } public async createOrUpdateApiDeployment(apiName: string, apiCenterApiDeployment: ApiCenterApiDeployment): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDeployment(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiDeployment.name, this.apiVersion), @@ -151,8 +141,7 @@ export class ApiCenterService { } public async createOrUpdateApiVersionDefinition(apiName: string, apiVersionName: string, apiCenterApiVersionDefinition: ApiCenterApiVersionDefinition): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinition.name, this.apiVersion), @@ -184,8 +173,7 @@ export class ApiCenterService { apiVersionName: string, apiCenterApiVersionDefinitionName: string, importPayload: ApiCenterApiVersionDefinitionImport): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); let options: RequestPrepareOptions = { method: "POST", @@ -216,8 +204,7 @@ export class ApiCenterService { apiName: string, apiVersionName: string, apiCenterApiVersionDefinitionName: string): Promise { - const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); - const client = new ServiceClient(creds); + const client = new ServiceClient(this.susbcriptionContext.credentials); const options: RequestPrepareOptions = { method: "POST", url: APICenterRestAPIs.ExportApiSpecification(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinitionName, this.apiVersion), diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index 0b5563dc..0af21554 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; + export type ApiCenter = { id: string; location: string; @@ -12,6 +14,11 @@ export type ApiCenter = { type: string; }; +export type DataPlaneApiCenter = { + name: string; +} + +export type GeneralApiCenterApi = ApiCenterApi | DataPlaneApiCenterApi; export type ApiCenterApi = { id: string; @@ -25,6 +32,16 @@ export type ApiCenterApi = { type: string; }; +export type DataPlaneApiCenterApi = { + name: string; + title: string; + kind: string; + lifecycleStage: string; + externalDocumentation: []; + contacts: []; + customProperties: {}; +} + export type ApiCenterEnvironment = { id: string; location: string; @@ -35,6 +52,8 @@ export type ApiCenterEnvironment = { type: string; }; +export type GeneralApiCenterApiVersion = ApiCenterApiVersion | DataPlaneApiCenterApiVersion; + export type ApiCenterApiVersion = { id: string; location: string; @@ -47,6 +66,12 @@ export type ApiCenterApiVersion = { type: string; }; +export type DataPlaneApiCenterApiVersion = { + name: string; + title: string; + lifecycleStage: string; +} + export type ApiCenterApiDeployment = { id: string; location: string; @@ -57,6 +82,8 @@ export type ApiCenterApiDeployment = { type: string; }; +export type GeneralApiCenterApiVersionDefinition = ApiCenterApiVersionDefinition | DataPlaneApiCenterApiVersionDefinition; + export type ApiCenterApiVersionDefinition = { id: string; location: string; @@ -76,6 +103,13 @@ export type ApiCenterRulesetConfig = { properties: { }; }; +export type DataPlaneApiCenterApiVersionDefinition = { + name: string; + title: string; + specification: { + name: string; + } +} export type ApiCenterApiVersionDefinitionImport = { format: string; diff --git a/src/azure/ResourceGraph/ResourceGraphService.ts b/src/azure/ResourceGraph/ResourceGraphService.ts index 323b3360..6e1392ad 100644 --- a/src/azure/ResourceGraph/ResourceGraphService.ts +++ b/src/azure/ResourceGraph/ResourceGraphService.ts @@ -4,7 +4,7 @@ import { ResourceGraphClient } from "@azure/arm-resourcegraph"; import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { getCredentialForToken } from "../../utils/credentialUtil"; -import { ApiCenter } from "./contracts"; +import { ApiCenter } from "../ApiCenter/contracts"; export class ResourceGraphService { private susbcriptionContext: ISubscriptionContext; diff --git a/src/azure/ResourceGraph/contracts.ts b/src/azure/ResourceGraph/contracts.ts deleted file mode 100644 index 8dd40a45..00000000 --- a/src/azure/ResourceGraph/contracts.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -export type ApiCenter = { - id: string; - location: string; - name: string; - resourceGroup: string; - properties: { - }; - // tslint:disable-next-line:no-reserved-keywords - type: string; -}; diff --git a/src/azure/azureLogin/authTypes.ts b/src/azure/azureLogin/authTypes.ts index 139d8a18..2d632da0 100644 --- a/src/azure/azureLogin/authTypes.ts +++ b/src/azure/azureLogin/authTypes.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { AuthenticationSession, Event } from "vscode"; import { GeneralUtils } from "../../utils/generalUtils"; +import { DataPlaneAccount } from "../ApiCenter/ApiCenterDataPlaneAPIs"; export enum SignInStatus { Initializing = 'Initializing', @@ -51,10 +52,12 @@ export type AzureSessionProvider = { }; export type AzureDataSessionProvider = { - signIn(): Promise; + signIn(_scopes: string[]): Promise; + signOut(_scopes: string[]): Promise; + signOutAll(_accounts: DataPlaneAccount[]): Promise; signInStatus: SignInStatus; signInStatusChangeEvent: Event; - getAuthSession(options?: GetAuthSessionOptions): Promise>; + getAuthSession(scopes?: string[]): Promise>; dispose(): void; } @@ -62,5 +65,3 @@ export type ReadyAzureSessionProvider = AzureSessionProvider & { signInStatus: SignInStatus.SignedIn; selectedTenant: Tenant; }; - - diff --git a/src/azure/azureLogin/azureAuth.ts b/src/azure/azureLogin/azureAuth.ts index 6af233ba..2f2f88ea 100644 --- a/src/azure/azureLogin/azureAuth.ts +++ b/src/azure/azureLogin/azureAuth.ts @@ -8,6 +8,7 @@ import { UiStrings } from "../../uiStrings"; import { GeneralUtils } from "../../utils/generalUtils"; import { AzureSessionProvider, GetAuthSessionOptions, ReadyAzureSessionProvider, SignInStatus, Tenant } from "./authTypes"; import { AzureSessionProviderHelper } from "./azureSessionProvider"; +import { AzureDataSessionProviderHelper, generateScopes } from "./dataSessionProvider"; export namespace AzureAuth { export function getEnvironment(): Environment { return getConfiguredAzureEnv(); @@ -26,6 +27,18 @@ export namespace AzureAuth { }; } + export function getDataPlaneCredential(clientId: string, tenantId: string): TokenCredential { + return { + getToken: async () => { + const session = await AzureDataSessionProviderHelper.getSessionProvider().getAuthSession(generateScopes(clientId, tenantId)); + if (GeneralUtils.failed(session)) { + throw new Error(vscode.l10n.t(UiStrings.NoMSAuthSessionFound, session.error)); + } + return { token: session.result.accessToken, expiresOnTimestamp: 0 }; + } + } + } + export function getDefaultScope(endpointUrl: string): string { // Endpoint URL is that of the audience, e.g. for ARM in the public cloud // it would be "https://management.azure.com". diff --git a/src/azure/azureLogin/dataPlaneSessionProvider.ts b/src/azure/azureLogin/dataPlaneSessionProvider.ts deleted file mode 100644 index ff6a56e0..00000000 --- a/src/azure/azureLogin/dataPlaneSessionProvider.ts +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import { - AuthenticationGetSessionOptions, - AuthenticationSession, - Event, - EventEmitter, - ExtensionContext, - Disposable as VsCodeDisposable, - authentication, - l10n -} from "vscode"; -import { UiStrings } from "../../uiStrings"; -import { GeneralUtils } from "../../utils/generalUtils"; -import { AzureAuthenticationSession, AzureDataSessionProvider, GetAuthSessionOptions, SignInStatus, Tenant } from "./authTypes"; -import { AzureAccount } from "./azureAccount"; -import { AzureAuth } from "./azureAuth"; - -enum AuthScenario { - Initialization, - SignIn, - GetSession, -} - -export namespace DataPlaneSessionProviderHelper { - - let sessionProvider: AzureDataSessionProvider; - - export function activateAzureSessionProvider(context: ExtensionContext) { - sessionProvider = new DataPlaneSessionProviderImpl(); - context.subscriptions.push(sessionProvider); - } - - export function getSessionProvider(): AzureDataSessionProvider { - return sessionProvider; - } - - class DataPlaneSessionProviderImpl extends VsCodeDisposable implements AzureDataSessionProvider { - private readonly initializePromise: Promise; - private handleSessionChanges: boolean = true; - private tenants: Tenant[] = []; - private selectedTenantValue: Tenant | null = null; - - public readonly onSignInStatusChangeEmitter = new EventEmitter(); - public signInStatusValue: SignInStatus = SignInStatus.Initializing; - - public constructor() { - const disposable = authentication.onDidChangeSessions(async (e) => { - // Ignore events for non-microsoft providers - if (e.provider.id !== AzureAuth.getConfiguredAuthProviderId()) { - return; - } - - // Ignore events that we triggered. - if (!this.handleSessionChanges) { - return; - } - - // Silently check authentication status and tenants - await this.signInAndUpdateTenants(AuthScenario.Initialization); - }); - - super(() => { - this.onSignInStatusChangeEmitter.dispose(); - disposable.dispose(); - }); - - this.initializePromise = this.initialize(); - } - - public get signInStatus(): SignInStatus { - return this.signInStatusValue; - } - - public get signInStatusChangeEvent(): Event { - return this.onSignInStatusChangeEmitter.event; - } - - private async initialize(): Promise { - await this.signInAndUpdateTenants(AuthScenario.Initialization); - } - - /** - * Sign in to Azure interactively, i.e. prompt the user to sign in even if they have an active session. - * This allows the user to choose a different account or tenant. - */ - public async signIn(): Promise { - await this.initializePromise; - - const newSignInStatus = SignInStatus.SigningIn; - if (newSignInStatus !== this.signInStatusValue) { - this.signInStatusValue = newSignInStatus; - this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); - } - - await this.signInAndUpdateTenants(AuthScenario.SignIn); - } - - private async signInAndUpdateTenants(authScenario: AuthScenario): Promise { - // Initially, try to get a session using the 'organizations' tenant/authority: - // https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority - // This allows the user to sign in to the Microsoft provider and list tenants, - // but the resulting session will not allow tenant-level operations. For that, - // we need to get a session for a specific tenant. - const orgTenantId = "organizations"; - const scopes = AzureAuth.getScopes(orgTenantId, {}); - const getSessionResult = await this.getArmSession(orgTenantId, scopes, authScenario); - - // Get the tenants - const getTenantsResult = await GeneralUtils.bindAsync(getSessionResult, (session) => AzureAccount.getTenants(session)); - const newTenants = GeneralUtils.succeeded(getTenantsResult) ? getTenantsResult.result : []; - const tenantsChanged = AzureAccount.getIdString(newTenants) !== AzureAccount.getIdString(this.tenants); - - // Determine which tenant should be selected. We can't force the user to choose at this stage, - // so this can be null, and will be set when the user tries to get a session. - const newSelectedTenant = await this.getNewSelectedTenant(newTenants, this.selectedTenantValue, authScenario); - const selectedTenantChanged = newSelectedTenant?.id !== this.selectedTenantValue?.id; - - // Get the overall sign-in status. If the user has access to any tenants they are signed in. - const newSignInStatus = newTenants.length > 0 ? SignInStatus.SignedIn : SignInStatus.SignedOut; - const signInStatusChanged = newSignInStatus !== this.signInStatusValue; - - // Update the state and fire event if anything has changed. - this.selectedTenantValue = newSelectedTenant; - this.tenants = newTenants; - this.signInStatusValue = newSignInStatus; - if (signInStatusChanged || tenantsChanged || selectedTenantChanged) { - this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); - } - } - - /** - * Get the current Azure session, silently if possible. - * @returns The current Azure session, if available. If the user is not signed in, or there are no tenants, - * an error message is returned. - */ - public async getAuthSession(options?: GetAuthSessionOptions): Promise> { - await this.initializePromise; - if (this.signInStatusValue !== SignInStatus.SignedIn) { - return { succeeded: false, error: l10n.t(UiStrings.NotSignInStatus, this.signInStatusValue) }; - } - - if (this.tenants.length === 0) { - return { succeeded: false, error: UiStrings.NoTenantFound }; - } - - if (!this.selectedTenantValue) { - if (this.tenants.length > 1) { - const selectedTenant = await AzureAuth.quickPickTenant(this.tenants); - if (!selectedTenant) { - return { succeeded: false, error: UiStrings.NoTenantSelected }; - } - - this.selectedTenantValue = selectedTenant; - } else { - this.selectedTenantValue = this.tenants[0]; - } - } - - // Get a session for a specific tenant. - const tenantId = this.selectedTenantValue.id; - const scopes = AzureAuth.getScopes(tenantId, options || {}); - return await this.getArmSession(tenantId, scopes, AuthScenario.GetSession); - } - - private async getNewSelectedTenant( - newTenants: Tenant[], - currentSelectedTenant: Tenant | null, - authScenario: AuthScenario, - ): Promise { - // For sign-in we ignore the current selected tenant because the user must be able to change it. - // For all other scenarios, we prefer to retain the current selected tenant if it is still valid. - const ignoreCurrentSelection = authScenario === AuthScenario.SignIn; - if (!ignoreCurrentSelection && currentSelectedTenant !== null) { - const isCurrentSelectedTenantValid = newTenants.some((t) => t.id === currentSelectedTenant.id); - if (isCurrentSelectedTenantValid) { - return currentSelectedTenant; - } - } - - // For sign-in, if there are multiple tenants, we should prompt the user to select one. - if (authScenario === AuthScenario.SignIn && newTenants.length > 1) { - return null; - } - - // For all other (non-interactive) scenarios, see if we can determine a default tenant to use. - const defaultTenant = await this.getDefaultTenantId(newTenants); - return defaultTenant; - } - - private async getDefaultTenantId(tenants: Tenant[]): Promise { - if (tenants.length === 1) { - return tenants[0]; - } - - // It may be the case that the user has access to multiple tenants, but only has a valid token for one of them. - // This might happen if the user has signed in to one recently, but not the others. In this case, we would want - // to default to the tenant that the user has a valid token for. - // Use the 'Initialization' scenario to ensure this is silent (no user interaction). - const getSessionPromises = tenants.map((t) => - this.getArmSession(t.id, AzureAuth.getScopes(t.id, {}), AuthScenario.Initialization), - ); - const results = await Promise.all(getSessionPromises); - const accessibleTenants = results.filter(GeneralUtils.succeeded).map((r) => r.result); - return accessibleTenants.length === 1 ? AzureAccount.findTenant(tenants, accessibleTenants[0].tenantId) : null; - } - - private async getArmSession( - tenantId: string, - scopes: string[], - authScenario: AuthScenario, - ): Promise> { - this.handleSessionChanges = false; - try { - let options: AuthenticationGetSessionOptions; - let silentFirst = false; - switch (authScenario) { - case AuthScenario.Initialization: - options = { createIfNone: false, clearSessionPreference: false, silent: true }; - break; - case AuthScenario.SignIn: - options = { createIfNone: true, clearSessionPreference: true, silent: false }; - break; - case AuthScenario.GetSession: - // the 'createIfNone' option cannot be used with 'silent', but really we want both - // flags here (i.e. create a session silently, but do create one if it doesn't exist). - // To allow this, we first try to get a session silently. - silentFirst = true; - options = { createIfNone: true, clearSessionPreference: false, silent: false }; - break; - } - - let session: AuthenticationSession | undefined; - if (silentFirst) { - // The 'silent' option is incompatible with most other options, so we completely replace the options object here. - session = await authentication.getSession("microsoft", scopes, { silent: true }); - } - - if (!session) { - session = await authentication.getSession("microsoft", scopes, options); - } - - if (!session) { - return { succeeded: false, error: UiStrings.NoAzureSessionFound }; - } - - return { succeeded: true, result: Object.assign(session, { tenantId }) }; - } catch (e) { - return { succeeded: false, error: l10n.t(UiStrings.FailedTo, GeneralUtils.getErrorMessage(e)) }; - } finally { - this.handleSessionChanges = true; - } - } - } - -} diff --git a/src/azure/azureLogin/dataSessionProvider.ts b/src/azure/azureLogin/dataSessionProvider.ts new file mode 100644 index 00000000..5fa51131 --- /dev/null +++ b/src/azure/azureLogin/dataSessionProvider.ts @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { + authentication, + AuthenticationGetSessionOptions, + AuthenticationSession, + Event, + EventEmitter, + ExtensionContext, + Disposable as VsCodeDisposable +} from "vscode"; +import { GeneralUtils } from "../../utils/generalUtils"; +import { DataPlaneAccount } from "../ApiCenter/ApiCenterDataPlaneAPIs"; +import { AzureAuthenticationSession, AzureDataSessionProvider, SignInStatus } from "./authTypes"; +import { AzureAuth } from "./azureAuth"; +enum AuthScenario { + Initialization, + SignIn, + GetSession, + SignedOut, +} + +export function generateScopes(clientId: string, tenantId: string): string[] { + return [ + `VSCODE_CLIENT_ID:${clientId}`, // Replace by your client id + `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant + "offline_access", // Required for the refresh token. + "https://azure-apicenter.net/user_impersonation" + ] +} + +export namespace AzureDataSessionProviderHelper { + let sessionProvider: AzureDataSessionProvider; + + export function activateAzureSessionProvider(context: ExtensionContext) { + sessionProvider = new AzureDataSessionProviderImpl(); + context.subscriptions.push(sessionProvider); + } + + export function getSessionProvider(): AzureDataSessionProvider { + return sessionProvider; + } + + class AzureDataSessionProviderImpl extends VsCodeDisposable implements AzureDataSessionProvider { + private handleSessionChanges: boolean = true; + public signInStatusValue: SignInStatus = SignInStatus.Initializing; + // private accountSet: Set = new Set(); + public readonly onSignInStatusChangeEmitter = new EventEmitter(); + constructor() { + const disposable = authentication.onDidChangeSessions(async (e) => { + // Ignore events for non-microsoft providers + if (e.provider.id !== AzureAuth.getConfiguredAuthProviderId()) { + return; + } + + // Ignore events that we triggered. + if (!this.handleSessionChanges) { + return; + } + + await this.updateSignInStatus([], AuthScenario.Initialization); + }); + + super(() => { + this.onSignInStatusChangeEmitter.dispose(); + disposable.dispose(); + }); + } + private async updateSignInStatus(_scopes: string[], authScenario: AuthScenario): Promise { + const orgTenantId = "organizations"; + const scopes = _scopes.length == 0 ? AzureAuth.getScopes(orgTenantId, {}) : _scopes; + await this.getArmSession(orgTenantId, scopes, authScenario); + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + public get signInStatus(): SignInStatus { + return this.signInStatusValue; + } + + public async getAuthSession(scopes?: string[]): Promise> { + return await this.getArmSession('microsoft', scopes!, AuthScenario.GetSession); + } + public async signIn(_scopes: string[]): Promise { + await this.updateSignInStatus(_scopes, AuthScenario.SignIn); + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + public get signInStatusChangeEvent(): Event { + return this.onSignInStatusChangeEmitter.event; + } + + public async signOutAll(dataPlaneAccounts: DataPlaneAccount[]): Promise { + for (let account of dataPlaneAccounts) { + await this.getArmSession("microsoft", generateScopes(account.clientId, account.tenantId), AuthScenario.SignedOut); + } + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + + public async signOut(_scopes: string[]): Promise { + await this.getArmSession("microsoft", _scopes, AuthScenario.SignedOut); + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + + private async getArmSession( + tenantId: string, + scopes: string[], + authScenario: AuthScenario, + ): Promise> { + this.handleSessionChanges = false; + try { + let options: AuthenticationGetSessionOptions; + let session: AuthenticationSession | undefined; + switch (authScenario) { + case AuthScenario.Initialization: + options = { createIfNone: false, clearSessionPreference: true, silent: true }; + session = await authentication.getSession('microsoft', scopes, options); + break; + case AuthScenario.SignIn: + options = { createIfNone: true, clearSessionPreference: true, silent: false }; + session = await authentication.getSession('microsoft', scopes, options); + break; + case AuthScenario.GetSession: + // the 'createIfNone' option cannot be used with 'silent', but really we want both + // flags here (i.e. create a session silently, but do create one if it doesn't exist). + // To allow this, we first try to get a session silently. + session = await authentication.getSession('microsoft', scopes, { silent: true }); + break; + } + if (!session) { + return { succeeded: false, error: "No Session Found" }; + } + return { succeeded: true, result: Object.assign(session, { tenantId }) }; + } catch (e) { + return { succeeded: false, error: GeneralUtils.getErrorMessage(e) }; + } finally { + this.handleSessionChanges = true; + } + } + } +} diff --git a/src/commands/addDataPlaneApis.ts b/src/commands/addDataPlaneApis.ts new file mode 100644 index 00000000..c646893a --- /dev/null +++ b/src/commands/addDataPlaneApis.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from 'vscode'; +import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { ext } from "../extensionVariables"; +export async function getDataPlaneApis(context: IActionContext): Promise { + const endpointUrl = await vscode.window.showInputBox({ title: "Input Runtime URL", ignoreFocusOut: true }); + if (!endpointUrl) { + return; + } + const clientid = await vscode.window.showInputBox({ title: "Input Client ID", ignoreFocusOut: true }); + if (!clientid) { + return; + } + const tenantid = await vscode.window.showInputBox({ title: "Input Tenant ID", ignoreFocusOut: true }); + if (!tenantid) { + return; + } + // return await getSessionToken(clientid, tenantid); + setAccountToExt(endpointUrl, clientid, tenantid); + ext.workspaceItem.refresh(context); +} +export function setAccountToExt(domain: string, clientId: string, tenantId: string) { + function pushIfNotExist(array: DataPlaneAccount[], element: DataPlaneAccount) { + if (!array.some(item => item.domain === element.domain)) { + array.push(element); + } + } + pushIfNotExist(ext.dataPlaneAccounts, { domain: domain, tenantId: tenantId, clientId: clientId }); +} diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index 9a3ff1ab..542515bb 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -7,22 +7,22 @@ import * as http from 'http'; import * as https from 'https'; import * as path from "path"; import * as vscode from "vscode"; +import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; 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 { ApiDefinitionTreeItem, fetchApiCenterServer } from "../tree/DataPlaneAccount"; import { createTemporaryFolder } from "../utils/fsUtil"; -import { getSessionToken } from "./workspaceApis"; export namespace ExportAPI { export async function exportApi( context: IActionContext, - node?: ApiVersionDefinitionTreeItem | ApiDefinitionTreeItem): Promise { + node?: ApiVersionDefinitionTreeItem): Promise { if (!node) { node = await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiVersionDefinitionTreeItem.contextValue}*`), context); } - if (node instanceof ApiVersionDefinitionTreeItem) { + + if (node.contextValue.startsWith(ApiVersionDefinitionTreeItem.contextValue)) { const apiCenterService = new ApiCenterService( node?.subscription!, getResourceGroupFromId(node?.id!), @@ -32,20 +32,18 @@ export namespace ExportAPI { node?.apiCenterApiVersionName!, node?.apiCenterApiVersionDefinition.name!); await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); - } else if (node instanceof ApiDefinitionTreeItem) { - let accessToken = await getSessionToken(node.account.clientId, node.account.tenantId); - if (accessToken) { - let server = new fetchApiCenterServer(node.account.domain, accessToken); - let results = await server.exportDefinitionLink(node.apiName, node.apiVersion, node.label); - if (results) { - const folderName = `${node.apiCenterName}-${node.apiName}`; - const folderPath = await createTemporaryFolder(folderName); - const localFilePath: string = path.join(folderPath, node.label); - await fs.ensureFile(localFilePath); - await downloadFile(results, localFilePath); - const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); - await vscode.window.showTextDocument(document); - } + } else if (node.contextValue.startsWith(ApiVersionDefinitionTreeItem.dataPlaneContextValue)) { + let server = new ApiCenterDataPlaneService(node.parent?.subscription!); + let results = await server.exportSpecification(node?.apiCenterApiName!, + node?.apiCenterApiVersionName!, node?.apiCenterApiVersionDefinition.name!); + if (results) { + const folderName = `${node.apiCenterName}-${node.apiCenterApiName}`; + const folderPath = await createTemporaryFolder(folderName); + const localFilePath: string = path.join(folderPath, node.label); + await fs.ensureFile(localFilePath); + await downloadFile(results.value, localFilePath); + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); + await vscode.window.showTextDocument(document); } } } @@ -82,7 +80,6 @@ export namespace ExportAPI { response.pipe(fileStream); fileStream.on('finish', () => { - console.log('finished!!!!!!!!'); fileStream.close(); resolve(); }); diff --git a/src/commands/handleUri.ts b/src/commands/handleUri.ts new file mode 100644 index 00000000..d6a532d4 --- /dev/null +++ b/src/commands/handleUri.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as vscode from 'vscode'; +import { setAccountToExt } from "./addDataPlaneApis"; +export async function handleUri(uri: vscode.Uri) { + const queryParams = new URLSearchParams(uri.query); + let tenantId = queryParams.get('tenantId') as string; + let clientId = queryParams.get('clientId') as string; + let runtimeUrl = queryParams.get('runtimeUrl') as string; + setAccountToExt(runtimeUrl, clientId, tenantId); + vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh') +}; diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index f62b2008..808e96f2 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -16,6 +16,9 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre const apiCenterNode = await ext.treeDataProvider.showTreeItemPicker(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } + if (!('id' in node.apiCenter)) { + return; + } const apiTitle = await vscode.window.showInputBox({ title: UiStrings.ApiTitle, ignoreFocusOut: true, validateInput: validateInputForTitle }); if (!apiTitle) { diff --git a/src/commands/removeDataplaneApi.ts b/src/commands/removeDataplaneApi.ts index 0792f5f3..76d73a08 100644 --- a/src/commands/removeDataplaneApi.ts +++ b/src/commands/removeDataplaneApi.ts @@ -2,25 +2,17 @@ // Licensed under the MIT license. import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; -import { exitFromSession } from "../commands/workspaceApis"; import { ext } from "../extensionVariables"; import { ApiServerItem } from "../tree/DataPlaneAccount"; - export async function removeDataplaneAPI(context: IActionContext, node: ApiServerItem) { let accounts = ext.dataPlaneAccounts; let indexToRemove = accounts.findIndex(account => - account.domain === node.apiAccount.domain && - account.tenantId === node.apiAccount.tenantId && - account.clientId === node.apiAccount.clientId + account.domain === node.parent?.subscription!.subscriptionPath! && + account.tenantId === node.parent?.subscription!.tenantId! && + account.clientId === node.parent?.subscription!.userId ); if (indexToRemove !== -1) { accounts.splice(indexToRemove, 1); } - let isExist = accounts.some(account => - account.tenantId === node.apiAccount.tenantId && account.clientId === node.apiAccount.clientId - ); - if (!isExist) { - await exitFromSession(node.apiAccount.clientId, node.apiAccount.tenantId); - } vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh'); } diff --git a/src/commands/signInToDataPlane.ts b/src/commands/signInToDataPlane.ts new file mode 100644 index 00000000..b9295ce6 --- /dev/null +++ b/src/commands/signInToDataPlane.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzureDataSessionProviderHelper, generateScopes } from "../azure/azureLogin/dataSessionProvider"; +import { ApisTreeItem } from "../tree/ApisTreeItem"; +import { ApiServerItem } from "../tree/DataPlaneAccount"; +export async function SignInToDataPlane(context: IActionContext, node: ApisTreeItem) { + if (!(node instanceof ApisTreeItem)) { + let parentNode = (node as ApisTreeItem).parent as ApiServerItem; + let scopes = generateScopes(parentNode.subscription!.userId!, parentNode.subscription!.tenantId!) + await AzureDataSessionProviderHelper.getSessionProvider().signIn(scopes); + } +} diff --git a/src/commands/workspaceApis.ts b/src/commands/workspaceApis.ts deleted file mode 100644 index 717ed2dd..00000000 --- a/src/commands/workspaceApis.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import { IActionContext } from "@microsoft/vscode-azext-utils"; -import * as vscode from 'vscode'; -import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { ext } from "../extensionVariables"; -export async function getDataPlaneApis(context: IActionContext): Promise { - const endpointUrl = await vscode.window.showInputBox({ title: "Input Runtime URL", ignoreFocusOut: true }); - const clientid = await vscode.window.showInputBox({ title: "Input Client ID", ignoreFocusOut: true }); - const tenantid = await vscode.window.showInputBox({ title: "Input Tenant ID", ignoreFocusOut: true }); - if (!endpointUrl || !clientid || !tenantid) { - return; - } - // return await getSessionToken(clientid, tenantid); - return setAccountToExt(endpointUrl, clientid, tenantid); -} -export function setAccountToExt(domain: string, clientId: string, tenantId: string) { - function pushIfNotExist(array: DataPlaneAccount[], element: DataPlaneAccount) { - if (!array.some(item => item.domain === element.domain)) { - array.push(element); - } - } - pushIfNotExist(ext.dataPlaneAccounts, { domain: domain, tenantId: tenantId, clientId: clientId }); -} - -export async function getSessionToken(clientId: string, tenantId: string) { - const session = await vscode.authentication.getSession('microsoft', [ - `VSCODE_CLIENT_ID:${clientId}`, // Replace by your client id - `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant - "offline_access", // Required for the refresh token. - "https://azure-apicenter.net/user_impersonation" - ], { createIfNone: true }); - if (session?.accessToken) { - return session.accessToken; - } else { - vscode.window.showErrorMessage("Please login your Microsoft Account first!"); - } -} - -export async function exitFromSession(clientId: string, tenantId: string) { - const session = await vscode.authentication.getSession('microsoft', [ - `VSCODE_CLIENT_ID:${clientId}`, // Replace by your client id - `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant - "offline_access", // Required for the refresh token. - "https://azure-apicenter.net/user_impersonation" - ], { clearSessionPreference: true }); - if (session?.accessToken) { - return session.accessToken; - } else { - vscode.window.showErrorMessage("Please login your Microsoft Account first!"); - } -} diff --git a/src/extension.ts b/src/extension.ts index 1d1a7314..af52b1b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,6 +11,8 @@ import { registerAzureUtilsExtensionVariables } from '@microsoft/vscode-azext-az import { AzExtTreeDataProvider, AzExtTreeItem, CommandCallback, IActionContext, IParsedError, createAzExtOutputChannel, isUserCancelledError, parseError, registerCommand, registerEvent } from '@microsoft/vscode-azext-utils'; import { AzureAccount } from "./azure/azureLogin/azureAccount"; import { AzureSessionProviderHelper } from "./azure/azureLogin/azureSessionProvider"; +import { AzureDataSessionProviderHelper } from "./azure/azureLogin/dataSessionProvider"; +import { getDataPlaneApis } from "./commands/addDataPlaneApis"; import { cleanupSearchResult } from './commands/cleanUpSearch'; import { detectBreakingChange } from './commands/detectBreakingChange'; import { showOpenApi } from './commands/editOpenApi'; @@ -19,11 +21,13 @@ import { GenerateApiFromCode } from './commands/generateApiFromCode'; import { generateApiLibrary } from './commands/generateApiLibrary'; import { GenerateHttpFile } from './commands/generateHttpFile'; import { generateMarkdownDocument } from './commands/generateMarkdownDocument'; +import { handleUri } from './commands/handleUri'; import { importOpenApi } from './commands/importOpenApi'; import { openAPiInSwagger } from './commands/openApiInSwagger'; import { openUrlFromTreeNode } from './commands/openUrl'; import { refreshTree } from './commands/refreshTree'; import { registerApi } from './commands/registerApi'; +import { removeDataplaneAPI } from './commands/removeDataplaneApi'; import { addCustomFunction } from './commands/rules/addCustomFunction'; import { deleteCustomFunction } from './commands/rules/deleteCustomFunction'; import { deployRules } from './commands/rules/deployRules'; @@ -33,17 +37,16 @@ import { openRule } from './commands/rules/openRule'; import { renameCustomFunction } from './commands/rules/renameCustomFunction'; import { searchApi } from './commands/searchApi'; import { setApiRuleset } from './commands/setApiRuleset'; +import { SignInToDataPlane } from "./commands/signInToDataPlane"; import { testInPostman } from './commands/testInPostman'; import { doubleClickDebounceDelay, selectedNodeKey } from './constants'; import { ext } from './extensionVariables'; import { ApiVersionDefinitionTreeItem } from './tree/ApiVersionDefinitionTreeItem'; import { createAzureAccountTreeItem } from "./tree/AzureAccountTreeItem"; +import { createAzureDataAccountTreeItem } from './tree/DataPlaneAccount'; import { OpenApiEditor } from './tree/Editors/openApi/OpenApiEditor'; // Copilot Chat -import { removeDataplaneAPI } from './commands/removeDataplaneApi'; -import { exitFromSession, getDataPlaneApis, setAccountToExt } from "./commands/workspaceApis"; import { ErrorProperties, TelemetryProperties } from './common/telemetryEvent'; -import { ApiDefinitionTreeItem, DataPlanAccountManagerTreeItem } from './tree/DataPlaneAccount'; export async function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "azure-api-center" is now active!'); @@ -74,10 +77,10 @@ export async function activate(context: vscode.ExtensionContext) { const treeView = vscode.window.createTreeView("apiCenterTreeView", { treeDataProvider }); context.subscriptions.push(treeView); - // DataPlaneSessionProviderHelper.activateAzureSessionProvider(context); - // const dataPlaneSessionProvider = AzureSessionProviderHelper.getSessionProvider(); - const dataPlanAccountManagerTreeItem = new DataPlanAccountManagerTreeItem(); - // context.subscriptions.push(dataPlanAccountManagerTreeItem); + AzureDataSessionProviderHelper.activateAzureSessionProvider(context); + const dataPlaneSessionProvider = AzureDataSessionProviderHelper.getSessionProvider(); + const dataPlanAccountManagerTreeItem = createAzureDataAccountTreeItem(dataPlaneSessionProvider); + context.subscriptions.push(dataPlanAccountManagerTreeItem); ext.workspaceItem = dataPlanAccountManagerTreeItem; const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); @@ -96,7 +99,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.exportApi', async (context: IActionContext, node?: ApiVersionDefinitionTreeItem | ApiDefinitionTreeItem) => { await ExportAPI.exportApi(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(); @@ -153,38 +156,16 @@ export async function activate(context: vscode.ExtensionContext) { registerCommandWithTelemetry('azure-api-center.signInToAzure', AzureAccount.signInToAzure); registerCommandWithTelemetry('azure-api-center.selectTenant', AzureAccount.selectTenant); registerCommandWithTelemetry('azure-api-center.selectSubscriptions', AzureAccount.selectSubscriptions); - registerCommandWithTelemetry('azure-api-center.openUrl', async (context: IActionContext, node?: AzExtTreeItem) => { - await openUrlFromTreeNode(context, node); - }); + registerCommandWithTelemetry('azure-api-center.openUrl', openUrlFromTreeNode); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.signInToDataPlane', SignInToDataPlane); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.refresh', async (context: IActionContext) => ext.workspaceItem.refresh(context)); - registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.addApis', async (context: IActionContext) => { - await getDataPlaneApis(context); - ext.workspaceItem.refresh(context); - }); - registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.logout', async (context: IActionContext) => { - if (ext.dataPlaneAccounts) { - for (let account of ext.dataPlaneAccounts) { - await exitFromSession(account.clientId, account.tenantId); - } - } - vscode.commands.executeCommand('workbench.actions.treeView.apiCenterWorkspace.collapseAll'); - ext.workspaceItem.refresh(context); - }); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.addApis', getDataPlaneApis); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.collapse', () => { vscode.commands.executeCommand('workbench.actions.treeView.apiCenterWorkspace.collapseAll'); }); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.removeApi', removeDataplaneAPI); - const handleUri = async (uri: vscode.Uri) => { - const queryParams = new URLSearchParams(uri.query); - let tenantId = queryParams.get('tenantId') as string; - let clientId = queryParams.get('clientId') as string; - let runtimeUrl = queryParams.get('runtimeUrl') as string; - setAccountToExt(runtimeUrl, clientId, tenantId); - vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh') - }; - context.subscriptions.push( vscode.window.registerUriHandler({ handleUri diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index cbc6f152..2212eae3 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -4,7 +4,6 @@ import { AzExtParentTreeItem, AzExtTreeDataProvider, IAzExtOutputChannel } from import { ExtensionContext } from "vscode"; import { DataPlaneAccount } from "./azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiVersionDefinitionTreeItem } from "./tree/ApiVersionDefinitionTreeItem"; -import { DataPlanAccountManagerTreeItem } from "./tree/DataPlaneAccount"; import { OpenApiEditor } from "./tree/Editors/openApi/OpenApiEditor"; /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -22,5 +21,5 @@ export namespace ext { export let dataPlaneAccounts: DataPlaneAccount[]; export let workspaceProvider: AzExtTreeDataProvider; - export let workspaceItem: DataPlanAccountManagerTreeItem; + export let workspaceItem: AzExtParentTreeItem & { dispose(): unknown; }; } diff --git a/src/tree/ApiCenterTreeItem.ts b/src/tree/ApiCenterTreeItem.ts index a9661a28..81e3aaa9 100644 --- a/src/tree/ApiCenterTreeItem.ts +++ b/src/tree/ApiCenterTreeItem.ts @@ -3,7 +3,7 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenter } from "../azure/ResourceGraph/contracts"; +import { ApiCenter } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { treeUtils } from "../utils/treeUtils"; import { ApisTreeItem } from "./ApisTreeItem"; diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index 31653ebc..c315b6c1 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterApi } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiDeploymentsTreeItem } from "./ApiDeploymentsTreeItem"; import { ApiVersionsTreeItem } from "./ApiVersionsTreeItem"; @@ -12,16 +12,18 @@ export class ApiTreeItem extends AzExtParentTreeItem { public static contextValue: string = "azureApiCenterApi"; public readonly contextValue: string = ApiTreeItem.contextValue; public readonly apiVersionsTreeItem: ApiVersionsTreeItem; - public readonly apiDeploymentsTreeItem: ApiDeploymentsTreeItem; - private readonly _apiCenterApi: ApiCenterApi; + public readonly apiDeploymentsTreeItem?: ApiDeploymentsTreeItem; + private readonly _apiCenterApi: GeneralApiCenterApi; private readonly _apiCenterName: string; private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: ApiCenterApi) { + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: GeneralApiCenterApi) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); - this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, apiCenterApi); + if ('id' in apiCenterApi) { + this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, apiCenterApi); + } } public get iconPath(): TreeItemIconPath { @@ -29,11 +31,11 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get id(): string { - return this._apiCenterApi.id; + return 'id' in this._apiCenterApi ? this._apiCenterApi.id : this._apiCenterApi.name; } public get label(): string { - return this._apiCenterApi.properties.title; + return 'id' in this._apiCenterApi ? this._apiCenterApi.properties.title : this._apiCenterApi.title; } public hasMoreChildrenImpl(): boolean { @@ -41,6 +43,6 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - return [this.apiVersionsTreeItem, this.apiDeploymentsTreeItem]; + return this.apiDeploymentsTreeItem ? [this.apiVersionsTreeItem, this.apiDeploymentsTreeItem] : [this.apiVersionsTreeItem]; } } diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index bcd8dde1..2ff65ac3 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,19 +2,24 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; + public static dataPlaneContextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" public readonly contextValue: string = ApiVersionDefinitionTreeItem.contextValue; constructor( parent: AzExtParentTreeItem, public apiCenterName: string, public apiCenterApiName: string, public apiCenterApiVersionName: string, - public apiCenterApiVersionDefinition: ApiCenterApiVersionDefinition) { + public apiCenterApiVersionDefinition: GeneralApiCenterApiVersionDefinition) { super(parent); - this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); + if ('id' in apiCenterApiVersionDefinition) { + this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); + } else { + this.contextValue = ApiVersionDefinitionTreeItem.dataPlaneContextValue + "-" + apiCenterApiVersionDefinition.name.toLowerCase(); + } } public get iconPath(): TreeItemIconPath { @@ -22,10 +27,10 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { } public get id(): string { - return this.apiCenterApiVersionDefinition.id; + return 'id' in this.apiCenterApiVersionDefinition ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name } public get label(): string { - return this.apiCenterApiVersionDefinition.properties.title; + return 'id' in this.apiCenterApiVersionDefinition ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; } } diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index afe880ab..d4295b66 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -3,8 +3,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; +import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionTreeItem } from "./ApiVersionDefinitionTreeItem"; @@ -14,13 +15,13 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { public readonly contextValue: string = ApiVersionDefinitionsTreeItem.contextValue; private readonly _apiCenterName: string; private readonly _apiCenterApiName: string; - private readonly _apiCenterApiVersion: ApiCenterApiVersion; + private readonly _apiCenterApiVersion: GeneralApiCenterApiVersion; private _nextLink: string | undefined; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: ApiCenterApiVersion) { + apiCenterApiVersion: GeneralApiCenterApiVersion) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this._apiCenterName = apiCenterName; @@ -36,25 +37,38 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const resourceGroupName = getResourceGroupFromId(this._apiCenterApiVersion.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); - const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); + let difinitions = await this.getDefinitions(); - this._nextLink = definitions.nextLink; return await this.createTreeItemsWithErrorHandling( - definitions.value, + difinitions, 'invalidResource', - resource => new ApiVersionDefinitionTreeItem( + difinition => new ApiVersionDefinitionTreeItem( this, this._apiCenterName, this._apiCenterApiName, this._apiCenterApiVersion.name, - resource), - resource => resource.name + difinition), + difinition => difinition.name ); } + private async getDefinitions(): Promise { + if ('id' in this._apiCenterApiVersion) { + const resourceGroupName = getResourceGroupFromId(this._apiCenterApiVersion.id); + const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); + + const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); + this._nextLink = definitions.nextLink; + return definitions.value; + } else { + const server = new ApiCenterDataPlaneService(this.parent?.subscription!); + const res = await server.getApiCenterApiDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); + this._nextLink = res.nextLink; + return res.value; + } + } + public hasMoreChildrenImpl(): boolean { return this._nextLink !== undefined; } diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index d353159c..4a59f2b5 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionsTreeItem } from "./ApiVersionDefinitionsTreeItem"; @@ -10,14 +10,14 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiVersionChildTypeLabel; public static contextValue: string = "azureApiCenterApiVersion"; public readonly contextValue: string = ApiVersionTreeItem.contextValue; - private readonly _apiCenterApiVersion: ApiCenterApiVersion; + private readonly _apiCenterApiVersion: GeneralApiCenterApiVersion; public readonly apiVersionDefinitionsTreeItem: ApiVersionDefinitionsTreeItem; private _nextLink: string | undefined; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: ApiCenterApiVersion) { + apiCenterApiVersion: GeneralApiCenterApiVersion) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this.apiVersionDefinitionsTreeItem = new ApiVersionDefinitionsTreeItem(this, apiCenterName, apiCenterApiName, apiCenterApiVersion); @@ -28,11 +28,11 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get id(): string { - return this._apiCenterApiVersion.id; + return 'id' in this._apiCenterApiVersion ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; } public get label(): string { - return this._apiCenterApiVersion.properties.title; + return 'id' in this._apiCenterApiVersion ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index b09dead8..40aed7fd 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -3,8 +3,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; +import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApi } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApi, GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -14,8 +15,8 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { public readonly contextValue: string = ApiVersionsTreeItem.contextValue; private _nextLink: string | undefined; private readonly _apiCenterName: string; - private readonly _apiCenterApi: ApiCenterApi; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: ApiCenterApi) { + private readonly _apiCenterApi: GeneralApiCenterApi; + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: GeneralApiCenterApi) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; @@ -30,19 +31,30 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const resourceGroupName = getResourceGroupFromId(this._apiCenterApi.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); - const apis = await apiCenterService.getApiCenterApiVersions(this._apiCenterApi.name); - - this._nextLink = apis.nextLink; + const apis = await this.getApiVersions(); return await this.createTreeItemsWithErrorHandling( - apis.value, + apis, 'invalidResource', resource => new ApiVersionTreeItem(this, this._apiCenterName, this._apiCenterApi.name, resource), resource => resource.name ); } + private async getApiVersions(): Promise { + if ('id' in this._apiCenterApi) { + const resourceGroupName = getResourceGroupFromId(this._apiCenterApi.id); + const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); + const apis = await apiCenterService.getApiCenterApiVersions(this._apiCenterApi.name); + this._nextLink = apis.nextLink; + return apis.value; + } else { + const server = new ApiCenterDataPlaneService(this.parent?.subscription!); + const res = await server.getAPiCenterApiVersions(this._apiCenterApi.name); + this._nextLink = res.nextLink; + return res.value; + } + } + public hasMoreChildrenImpl(): boolean { return this._nextLink !== undefined; } diff --git a/src/tree/ApisTreeItem.ts b/src/tree/ApisTreeItem.ts index d9ef7ce6..f2cbd69a 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -3,18 +3,18 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; +import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenter } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenter, GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiTreeItem } from "./ApiTreeItem"; - export class ApisTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApisTreeItemChildTypeLabel; public static contextValue: string = "azureApiCenterApis"; public searchContent: string = ""; public contextValue: string = ApisTreeItem.contextValue; private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, public apiCenter: ApiCenter) { + constructor(parent: AzExtParentTreeItem, public apiCenter: GeneralApiCenter) { super(parent); } @@ -40,19 +40,31 @@ export class ApisTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const resourceGroupName = getResourceGroupFromId(this.apiCenter.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this.apiCenter.name); - const apis = await apiCenterService.getApiCenterApis(this.searchContent); - - this._nextLink = apis.nextLink; + const apis = await this.getApis(); return await this.createTreeItemsWithErrorHandling( - apis.value, + apis, 'invalidResource', resource => new ApiTreeItem(this, this.apiCenter.name, resource), resource => resource.name ); } + private async getApis(): Promise { + if ('id' in this.apiCenter) { + const resourceGroupName = getResourceGroupFromId(this.apiCenter.id); + const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this.apiCenter.name); + const apis = await apiCenterService.getApiCenterApis(this.searchContent); + + this._nextLink = apis.nextLink; + return apis.value; + } else { + let server = new ApiCenterDataPlaneService(this.parent?.subscription!); + const res = await server.getApiCenterApis(); + this._nextLink = res.nextLink; + return res.value; + } + } + public hasMoreChildrenImpl(): boolean { return this._nextLink !== undefined; } diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts index 066404bb..11460acf 100644 --- a/src/tree/DataPlaneAccount.ts +++ b/src/tree/DataPlaneAccount.ts @@ -1,30 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; +import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, ISubscriptionContext, TreeItemIconPath, registerEvent } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { SignInStatus } from "../azure/azureLogin/authTypes"; -import { getSessionToken } from "../commands/workspaceApis"; +import { DataPlaneApiCenter } from "../azure/ApiCenter/contracts"; +import { AzureDataSessionProvider } from "../azure/azureLogin/authTypes"; +import { AzureAuth } from "../azure/azureLogin/azureAuth"; +import { AzureDataSessionProviderHelper, generateScopes } from "../azure/azureLogin/dataSessionProvider"; import { ext } from "../extensionVariables"; import { UiStrings } from "../uiStrings"; +import { GeneralUtils } from "../utils/generalUtils"; import { treeUtils } from "../utils/treeUtils"; - +import { ApisTreeItem } from "./ApisTreeItem"; +export function createAzureDataAccountTreeItem( + sessionProvider: AzureDataSessionProvider, +): AzExtParentTreeItem & { dispose(): unknown } { + return new DataPlanAccountManagerTreeItem(sessionProvider); +} export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { public contextValue: string = DataPlanAccountManagerTreeItem.contextValue; - private signInStatus: SignInStatus = SignInStatus.Initializing; - constructor() { + constructor(private readonly sessionProvider: AzureDataSessionProvider) { super(undefined); this.autoSelectInTreeItemPicker = true; - // const onStatusChange = this.sessionProvider.signInStatusChangeEvent; - // registerEvent("azureAccountDataPlaneTreeItem.onSignInStatusChange", onStatusChange, (context) => this.refresh(context)); + const onStatusChange = this.sessionProvider.signInStatusChangeEvent; + registerEvent("DataPlanAccountManagerTreeItem.onSignInStatusChange", onStatusChange, (context) => { + this.refresh(context) + }); } + public dispose(): void { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { const accounts = ext.dataPlaneAccounts; return await this.createTreeItemsWithErrorHandling( accounts, 'inValidResource', - async account => new ApiServerItem(this, account), + async account => new ApiServerItem(this, getSubscriptionContext(account)), account => account.domain.split('0')[0] ) } @@ -32,7 +42,7 @@ export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { public hasMoreChildrenImpl(): boolean { return false; } - public static contextValue: string = "APICenterWorkspaceDataPlane-AccountManager"; + public static contextValue: string = "azureApiCenterDataPlaneView"; public get iconPath(): TreeItemIconPath { return new vscode.ThemeIcon("versions"); } @@ -41,307 +51,65 @@ export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { return "dataPlaneAccount"; } - public resetStatus(): void { - this.signInStatus = SignInStatus.SignedOut; - } } export class ApiServerItem extends AzExtParentTreeItem { public label: string; - public readonly apisTreeItem: ApiTreesItem; - public readonly apiAccount: DataPlaneAccount; + public readonly subscriptionContext: ISubscriptionContext; + public readonly apisTreeItem: ApisTreeItem; public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { + let scopes = generateScopes(this.subscriptionContext.userId, this.subscriptionContext!.tenantId!); + const authSession = await AzureDataSessionProviderHelper.getSessionProvider().getAuthSession(scopes); + if (GeneralUtils.failed(authSession)) { + return [ + new GenericTreeItem(this, { + label: UiStrings.SignIntoAzure, + commandId: "azure-api-center.apiCenterWorkspace.signInToDataPlane", + contextValue: "azureCommand", + id: "azureapicenterAccountSignIn", + iconPath: new vscode.ThemeIcon("sign-in"), + includeInTreeItemPicker: true, + }) + ] + } return [this.apisTreeItem] } public hasMoreChildrenImpl(): boolean { return false; } - constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount) { + get subscription(): ISubscriptionContext { + return this.subscriptionContext; + } + constructor(parent: AzExtParentTreeItem, subContext: ISubscriptionContext) { super(parent); - this.label = account.domain.split('.')[0]; - this.apiAccount = account; - this.apisTreeItem = new ApiTreesItem(this, account); + this.label = subContext.subscriptionPath.split('.')[0]; + this.subscriptionContext = subContext; + this.apisTreeItem = new ApisTreeItem(this, { name: this.label } as DataPlaneApiCenter); } public get id(): string { return this.label; } public contextValue: string = ApiServerItem.contextValue; - public static contextValue: string = "WorkspaceAPICenter-Server"; + public static contextValue: string = "azureApiCenterDataPlane"; public get iconPath(): TreeItemIconPath { return treeUtils.getIconPath('apiCenter'); } } -export class ApiTreesItem extends AzExtParentTreeItem { - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const apis = await this.getApis(); - return await this.createTreeItemsWithErrorHandling( - apis, - 'invalidResource', - async apic => new ApiTreeItem(this, this.account, this.label, apic), - apic => apic.name - ); - } - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; - } - public static contextValue: string = "workspaceApiCenterApis"; - public contextValue: string = ApiTreesItem.contextValue; - private _nextLink: string | undefined; - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("library"); - } - public get label(): string { - return UiStrings.TreeitemLabelApis; - } - constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount) { - super(parent); - } - private async getApis(): Promise { - let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); - if (accessToken) { - let server = new fetchApiCenterServer(this.account.domain, accessToken); - const res = await server.getApis(); - if (res) { - this._nextLink = res.nextLink; - return res.value; - } - } - return []; - } -} - -export class ApiTreeItem extends AzExtParentTreeItem { - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - return [this.apiVersionsTreeItem] - } - public hasMoreChildrenImpl(): boolean { - return false; - } - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("library"); - } - public label: string; - public contextValue: string; - public readonly apiVersionsTreeItem: ApiVersionsTreeItem; - constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount, apiName: string, apiCenter: ApiCenter) { - super(parent); - this.label = apiCenter.name; - this.contextValue = ApiTreeItem.contextValue; - this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, account, apiName, apiCenter); - } - public static contextValue: string = "WorkspaceAPICenter-API"; -} - -export class ApiVersionsTreeItem extends AzExtParentTreeItem { - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const versions = await this.getVersions(); - return await this.createTreeItemsWithErrorHandling( - versions, - 'invalidResource', - async version => new ApiVersionTreeItem(this, this.account, this._apiCenterName, this._apiCenterApi.name, version), - version => version.name - ); - } - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; - } - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("versions"); - } - public static contextValue: string = "workspaceApiCenterApiVersions"; - public readonly contextValue: string = ApiVersionsTreeItem.contextValue; - public get label(): string { - return UiStrings.TreeitemLabelVersions; - } - private readonly _apiCenterName: string; - private readonly _apiCenterApi: ApiCenter; - private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiCenterName: string, apiCenterApi: ApiCenter) { - super(parent); - this._apiCenterName = apiCenterName; - this._apiCenterApi = apiCenterApi; - } - private async getVersions(): Promise { - let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); - if (accessToken) { - let server = new fetchApiCenterServer(this.account.domain, accessToken); - let res = await server.getVersions(this._apiCenterApi.name); - if (res) { - this._nextLink = res.nextLink; - return res.value; - } - } - return []; - } -} -export class ApiVersionTreeItem extends AzExtParentTreeItem { - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - return [this.apiVersionDefinitionsTreeItem]; - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - public static contextValue: string = "WorkspaceAPICenter-Version"; - public label: string; - public contextValue: string; - public readonly apiVersionDefinitionsTreeItem: ApiDefinitionsTreeItem; - constructor(parent: AzExtParentTreeItem, account: DataPlaneAccount, apiServerName: string, apiName: string, apiVersion: ApiVersion) { - super(parent); - this.apiVersionDefinitionsTreeItem = new ApiDefinitionsTreeItem(this, account, apiServerName, apiName, apiVersion); - this.label = apiVersion.name; - this.contextValue = ApiVersionTreeItem.contextValue; - } - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("versions"); - } -} -export class ApiDefinitionsTreeItem extends AzExtParentTreeItem { - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const definitions = await this.getDefinitions(); - return await this.createTreeItemsWithErrorHandling( - definitions, - 'invalidResource', - async definition => new ApiDefinitionTreeItem(this, this.account, this._apiCenterName, this._apiCenterApiName, this._apiCenterApiVersion.name, definition), - definition => definition.name - ); - } - public static contextValue: string = "workspaceApiCenterApiVersionDefinitions"; - public readonly contextValue: string = ApiDefinitionsTreeItem.contextValue; - private readonly _apiCenterName: string; - private readonly _apiCenterApiName: string; - private readonly _apiCenterApiVersion: ApiVersion; - private _nextLink: string | undefined; - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; - } - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("list-selection"); - } - constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiService: string, apiName: string, apiVersion: ApiVersion) { - super(parent); - this._apiCenterName = apiService; - this._apiCenterApiName = apiName; - this._apiCenterApiVersion = apiVersion; - } - - public get label(): string { - return UiStrings.TreeitemLabelDefinitions; - } - private async getDefinitions(): Promise { - let accessToken = await getSessionToken(this.account.clientId, this.account.tenantId); - if (accessToken) { - let server = new fetchApiCenterServer(this.account.domain, accessToken); - let res = await server.getDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); - if (res) { - this._nextLink = res.nextLink; - return res.value; - } - } - return []; - } -} -export class ApiDefinitionTreeItem extends AzExtTreeItem { - public readonly label: string; - public contextValue: string; - public readonly apiCenterName: string; - public readonly apiName: string; - public readonly apiVersion: string; - constructor(parent: AzExtParentTreeItem, public account: DataPlaneAccount, apiCenterName: string, apiName: string, apiVersion: string, definition: ApiDefinitions) { - super(parent); - this.label = definition.name; - this.apiCenterName = apiCenterName; - this.apiName = apiName; - this.apiVersion = apiVersion; - this.contextValue = ApiDefinitionTreeItem.contextValue; - } - public static contextValue: string = "WorkspaceAPICenter-Definition"; - public get iconPath(): TreeItemIconPath { - return new vscode.ThemeIcon("list-selection"); - } -} - -export type ApiServer = { - name: string; -} - -export type ApiCenter = { - name: string; - title: string; - kind: string; - lifecycleStage: string; - externalDocumentation: []; - contacts: []; - customProperties: {}; -}; - -export type ApiVersion = { - name: string; - title: string; - lifecycleStage: string; -} - -export type ApiDefinitions = { - name: string; - title: string; - specification: { - name: string; - } -} - -export enum Method { - GET = "GET", - POST = "POST", -} - -export class fetchApiCenterServer { - constructor(private domain: string, private accessToken: string) { - } - private async requestApis(queryString: string, method: Method = Method.GET) { - const requestUrl = `https://${this.domain}/workspaces/default/${queryString}`; - - const headers = { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: "Bearer " + this.accessToken, - }; - - const response = await fetch(requestUrl, { method: method, headers: headers }); - - if (response.status === 401 || response.status == 403) { - return; - } else if (!response.ok) { - return; - } - - const dataJson = await response.json(); - return dataJson; - } - public async getApis(queryString?: string): Promise<{ value: ApiCenter[]; nextLink: string }> { - return queryString ? await this.requestApis(`apis?${queryString}`) : await this.requestApis(`apis`); - } - - public async getApi(id: string): Promise<{ value: ApiCenter[]; nextLink: string }> { - return await this.requestApis(`apis/${id}`); - } - - public async getVersions(apiId: string): Promise<{ value: ApiVersion[]; nextLink: string }> { - return await this.requestApis(`apis/${apiId}/versions`); - } - - public async getDeployments(apiId: string) { - return await this.requestApis(`apis/${apiId}/deployments`); - } - - public async getDefinitions(apiId: string, version: string): Promise<{ value: ApiDefinitions[]; nextLink: string }> { - return await this.requestApis(`apis/${apiId}/versions/${version}/definitions`); - } - - public async exportDefinitionLink(apiName: string, apiVersion: string, definitionName: string) { - return await this.requestApis( - `apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification`, - Method.POST - ); - } +function getSubscriptionContext( + account: DataPlaneAccount +): ISubscriptionContext { + const credentials = AzureAuth.getDataPlaneCredential(account.clientId, account.tenantId); + const environment = AzureAuth.getEnvironment(); + + return { + credentials, + subscriptionDisplayName: "", + subscriptionId: "", + subscriptionPath: account.domain, + tenantId: account.tenantId, + userId: account.clientId, + environment, + isCustomCloud: environment.name === "AzureCustomCloud", + }; } diff --git a/tsconfig.json b/tsconfig.json index 557f2775..b75962be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,7 @@ "module": "commonjs", "target": "ES2020", "lib": [ - "ES2020", - "DOM" + "ES2020" ], "sourceMap": true, "rootDir": "src", From dfafbed19da19ffbd908597f1f0c286d07155c52 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 22 Jul 2024 10:58:55 +0800 Subject: [PATCH 04/29] feat: ready --- src/azure/ApiCenter/ApiCenterService.ts | 24 +++++++-------- src/azure/azureLogin/authTypes.ts | 3 -- src/azure/azureLogin/dataSessionProvider.ts | 34 +++++++-------------- src/commands/removeDataplaneApi.ts | 6 ++-- 4 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/azure/ApiCenter/ApiCenterService.ts b/src/azure/ApiCenter/ApiCenterService.ts index 7d80195f..d2a52716 100644 --- a/src/azure/ApiCenter/ApiCenterService.ts +++ b/src/azure/ApiCenter/ApiCenterService.ts @@ -18,7 +18,7 @@ export class ApiCenterService { } public async getApiCenter(): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIService(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -28,7 +28,7 @@ export class ApiCenterService { } public async getApiCenterApis(searchContent: string): Promise<{ value: ApiCenterApi[]; nextLink: string }> { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); let url = APICenterRestAPIs.ListAPIs(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion); if (searchContent) { url += `&$search=${searchContent}`; @@ -42,7 +42,7 @@ export class ApiCenterService { } public async getApiCenterEnvironments(): Promise<{ value: ApiCenterEnvironment[]; nextLink: string }> { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIEnvironments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -52,7 +52,7 @@ export class ApiCenterService { } public async getApiCenterApiVersions(apiName: string): Promise<{ value: ApiCenterApiVersion[]; nextLink: string }> { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIVersions(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -62,7 +62,7 @@ export class ApiCenterService { } public async getApiCenterApiDeployments(apiName: string): Promise<{ value: ApiCenterApiDeployment[]; nextLink: string }> { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDeployments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -72,7 +72,7 @@ export class ApiCenterService { } public async getApiCenterApiVersionDefinitions(apiName: string, apiVersion: string): Promise<{ value: ApiCenterApiVersionDefinition[]; nextLink: string }> { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersion, this.apiVersion) @@ -102,7 +102,7 @@ export class ApiCenterService { } public async createOrUpdateApi(apiCenterApi: ApiCenterApi): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPI(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiCenterApi.name, this.apiVersion), @@ -115,7 +115,7 @@ export class ApiCenterService { } public async createOrUpdateApiVersion(apiName: string, apiCenterApiVersion: ApiCenterApiVersion): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIVersion(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiVersion.name, this.apiVersion), @@ -129,7 +129,7 @@ export class ApiCenterService { } public async createOrUpdateApiDeployment(apiName: string, apiCenterApiDeployment: ApiCenterApiDeployment): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDeployment(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiDeployment.name, this.apiVersion), @@ -141,7 +141,7 @@ export class ApiCenterService { } public async createOrUpdateApiVersionDefinition(apiName: string, apiVersionName: string, apiCenterApiVersionDefinition: ApiCenterApiVersionDefinition): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinition.name, this.apiVersion), @@ -173,7 +173,7 @@ export class ApiCenterService { apiVersionName: string, apiCenterApiVersionDefinitionName: string, importPayload: ApiCenterApiVersionDefinitionImport): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); let options: RequestPrepareOptions = { method: "POST", @@ -204,7 +204,7 @@ export class ApiCenterService { apiName: string, apiVersionName: string, apiCenterApiVersionDefinitionName: string): Promise { - const client = new ServiceClient(this.susbcriptionContext.credentials); + const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); const options: RequestPrepareOptions = { method: "POST", url: APICenterRestAPIs.ExportApiSpecification(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinitionName, this.apiVersion), diff --git a/src/azure/azureLogin/authTypes.ts b/src/azure/azureLogin/authTypes.ts index 2d632da0..535cbd4c 100644 --- a/src/azure/azureLogin/authTypes.ts +++ b/src/azure/azureLogin/authTypes.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { AuthenticationSession, Event } from "vscode"; import { GeneralUtils } from "../../utils/generalUtils"; -import { DataPlaneAccount } from "../ApiCenter/ApiCenterDataPlaneAPIs"; export enum SignInStatus { Initializing = 'Initializing', @@ -53,8 +52,6 @@ export type AzureSessionProvider = { export type AzureDataSessionProvider = { signIn(_scopes: string[]): Promise; - signOut(_scopes: string[]): Promise; - signOutAll(_accounts: DataPlaneAccount[]): Promise; signInStatus: SignInStatus; signInStatusChangeEvent: Event; getAuthSession(scopes?: string[]): Promise>; diff --git a/src/azure/azureLogin/dataSessionProvider.ts b/src/azure/azureLogin/dataSessionProvider.ts index 5fa51131..16f17061 100644 --- a/src/azure/azureLogin/dataSessionProvider.ts +++ b/src/azure/azureLogin/dataSessionProvider.ts @@ -9,15 +9,14 @@ import { ExtensionContext, Disposable as VsCodeDisposable } from "vscode"; +import { UiStrings } from "../../uiStrings"; import { GeneralUtils } from "../../utils/generalUtils"; -import { DataPlaneAccount } from "../ApiCenter/ApiCenterDataPlaneAPIs"; import { AzureAuthenticationSession, AzureDataSessionProvider, SignInStatus } from "./authTypes"; import { AzureAuth } from "./azureAuth"; enum AuthScenario { Initialization, SignIn, - GetSession, - SignedOut, + GetSession } export function generateScopes(clientId: string, tenantId: string): string[] { @@ -42,6 +41,7 @@ export namespace AzureDataSessionProviderHelper { } class AzureDataSessionProviderImpl extends VsCodeDisposable implements AzureDataSessionProvider { + public static MicrosoftAuthProviderId: string = 'microsoft'; private handleSessionChanges: boolean = true; public signInStatusValue: SignInStatus = SignInStatus.Initializing; // private accountSet: Set = new Set(); @@ -67,9 +67,9 @@ export namespace AzureDataSessionProviderHelper { }); } private async updateSignInStatus(_scopes: string[], authScenario: AuthScenario): Promise { - const orgTenantId = "organizations"; - const scopes = _scopes.length == 0 ? AzureAuth.getScopes(orgTenantId, {}) : _scopes; - await this.getArmSession(orgTenantId, scopes, authScenario); + if (_scopes.length != 0) { + await this.getArmSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, _scopes, authScenario); + } this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); } public get signInStatus(): SignInStatus { @@ -77,7 +77,7 @@ export namespace AzureDataSessionProviderHelper { } public async getAuthSession(scopes?: string[]): Promise> { - return await this.getArmSession('microsoft', scopes!, AuthScenario.GetSession); + return await this.getArmSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes!, AuthScenario.GetSession); } public async signIn(_scopes: string[]): Promise { await this.updateSignInStatus(_scopes, AuthScenario.SignIn); @@ -87,18 +87,6 @@ export namespace AzureDataSessionProviderHelper { return this.onSignInStatusChangeEmitter.event; } - public async signOutAll(dataPlaneAccounts: DataPlaneAccount[]): Promise { - for (let account of dataPlaneAccounts) { - await this.getArmSession("microsoft", generateScopes(account.clientId, account.tenantId), AuthScenario.SignedOut); - } - this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); - } - - public async signOut(_scopes: string[]): Promise { - await this.getArmSession("microsoft", _scopes, AuthScenario.SignedOut); - this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); - } - private async getArmSession( tenantId: string, scopes: string[], @@ -111,21 +99,21 @@ export namespace AzureDataSessionProviderHelper { switch (authScenario) { case AuthScenario.Initialization: options = { createIfNone: false, clearSessionPreference: true, silent: true }; - session = await authentication.getSession('microsoft', scopes, options); + session = await authentication.getSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes, options); break; case AuthScenario.SignIn: options = { createIfNone: true, clearSessionPreference: true, silent: false }; - session = await authentication.getSession('microsoft', scopes, options); + session = await authentication.getSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes, options); break; case AuthScenario.GetSession: // the 'createIfNone' option cannot be used with 'silent', but really we want both // flags here (i.e. create a session silently, but do create one if it doesn't exist). // To allow this, we first try to get a session silently. - session = await authentication.getSession('microsoft', scopes, { silent: true }); + session = await authentication.getSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes, { silent: true }); break; } if (!session) { - return { succeeded: false, error: "No Session Found" }; + return { succeeded: false, error: UiStrings.NoAzureSessionFound }; } return { succeeded: true, result: Object.assign(session, { tenantId }) }; } catch (e) { diff --git a/src/commands/removeDataplaneApi.ts b/src/commands/removeDataplaneApi.ts index 76d73a08..c72386d0 100644 --- a/src/commands/removeDataplaneApi.ts +++ b/src/commands/removeDataplaneApi.ts @@ -7,9 +7,9 @@ import { ApiServerItem } from "../tree/DataPlaneAccount"; export async function removeDataplaneAPI(context: IActionContext, node: ApiServerItem) { let accounts = ext.dataPlaneAccounts; let indexToRemove = accounts.findIndex(account => - account.domain === node.parent?.subscription!.subscriptionPath! && - account.tenantId === node.parent?.subscription!.tenantId! && - account.clientId === node.parent?.subscription!.userId + account.domain === node.subscription.subscriptionPath! && + account.tenantId === node.subscription.tenantId! && + account.clientId === node.subscription.userId! ); if (indexToRemove !== -1) { accounts.splice(indexToRemove, 1); From c9320b68aaf3783c9a52c15b56e0c40ee030d273 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 22 Jul 2024 11:02:37 +0800 Subject: [PATCH 05/29] feat: update --- src/azure/ApiCenter/ApiCenterService.ts | 37 +++++++++++++------ src/azure/ApiCenter/contracts.ts | 20 ++++++++-- src/azure/azureLogin/azureAuth.ts | 3 +- .../registerStepByStep.ts | 4 +- src/tree/ApiTreeItem.ts | 8 ++-- src/tree/ApiVersionDefinitionTreeItem.ts | 8 ++-- src/tree/ApiVersionDefinitionsTreeItem.ts | 4 +- src/tree/ApiVersionTreeItem.ts | 6 +-- src/tree/ApiVersionsTreeItem.ts | 4 +- src/tree/ApisTreeItem.ts | 4 +- src/tree/Editors/openApi/OpenApiEditor.ts | 1 + 11 files changed, 63 insertions(+), 36 deletions(-) diff --git a/src/azure/ApiCenter/ApiCenterService.ts b/src/azure/ApiCenter/ApiCenterService.ts index d2a52716..82662ad5 100644 --- a/src/azure/ApiCenter/ApiCenterService.ts +++ b/src/azure/ApiCenter/ApiCenterService.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { HttpOperationResponse, RequestPrepareOptions, ServiceClient } from "@azure/ms-rest-js"; import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { getCredentialForToken } from "../../utils/credentialUtil"; import { APICenterRestAPIs } from "./ApiCenterRestAPIs"; import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport } from "./contracts"; @@ -18,7 +19,8 @@ export class ApiCenterService { } public async getApiCenter(): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIService(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -28,7 +30,8 @@ export class ApiCenterService { } public async getApiCenterApis(searchContent: string): Promise<{ value: ApiCenterApi[]; nextLink: string }> { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); let url = APICenterRestAPIs.ListAPIs(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion); if (searchContent) { url += `&$search=${searchContent}`; @@ -42,7 +45,8 @@ export class ApiCenterService { } public async getApiCenterEnvironments(): Promise<{ value: ApiCenterEnvironment[]; nextLink: string }> { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIEnvironments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersion) @@ -52,7 +56,8 @@ export class ApiCenterService { } public async getApiCenterApiVersions(apiName: string): Promise<{ value: ApiCenterApiVersion[]; nextLink: string }> { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIVersions(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -62,7 +67,8 @@ export class ApiCenterService { } public async getApiCenterApiDeployments(apiName: string): Promise<{ value: ApiCenterApiDeployment[]; nextLink: string }> { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDeployments(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, this.apiVersion) @@ -72,7 +78,8 @@ export class ApiCenterService { } public async getApiCenterApiVersionDefinitions(apiName: string, apiVersion: string): Promise<{ value: ApiCenterApiVersionDefinition[]; nextLink: string }> { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", url: APICenterRestAPIs.GetAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersion, this.apiVersion) @@ -102,7 +109,8 @@ export class ApiCenterService { } public async createOrUpdateApi(apiCenterApi: ApiCenterApi): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPI(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiCenterApi.name, this.apiVersion), @@ -115,7 +123,8 @@ export class ApiCenterService { } public async createOrUpdateApiVersion(apiName: string, apiCenterApiVersion: ApiCenterApiVersion): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIVersion(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiVersion.name, this.apiVersion), @@ -129,7 +138,8 @@ export class ApiCenterService { } public async createOrUpdateApiDeployment(apiName: string, apiCenterApiDeployment: ApiCenterApiDeployment): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDeployment(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiCenterApiDeployment.name, this.apiVersion), @@ -141,7 +151,8 @@ export class ApiCenterService { } public async createOrUpdateApiVersionDefinition(apiName: string, apiVersionName: string, apiCenterApiVersionDefinition: ApiCenterApiVersionDefinition): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "PUT", url: APICenterRestAPIs.CreateAPIDefinition(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinition.name, this.apiVersion), @@ -173,7 +184,8 @@ export class ApiCenterService { apiVersionName: string, apiCenterApiVersionDefinitionName: string, importPayload: ApiCenterApiVersionDefinitionImport): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); let options: RequestPrepareOptions = { method: "POST", @@ -204,7 +216,8 @@ export class ApiCenterService { apiName: string, apiVersionName: string, apiCenterApiVersionDefinitionName: string): Promise { - const client = new ServiceClient(await this.susbcriptionContext.credentials.getToken()); + const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); + const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "POST", url: APICenterRestAPIs.ExportApiSpecification(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, apiName, apiVersionName, apiCenterApiVersionDefinitionName, this.apiVersion), diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index 0af21554..b41c1142 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -2,7 +2,10 @@ // Licensed under the MIT license. export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; - +export function isApiServerManagement(obj: GeneralApiCenter): obj is ApiCenter { + const keys = Object.keys(obj) as (keyof ApiCenter)[]; + return (keys.length === Object.keys({} as ApiCenter).length) && keys.every(key => key in obj); +} export type ApiCenter = { id: string; location: string; @@ -19,7 +22,10 @@ export type DataPlaneApiCenter = { } export type GeneralApiCenterApi = ApiCenterApi | DataPlaneApiCenterApi; - +export function isApiManagement(obj: GeneralApiCenterApi): obj is ApiCenterApi { + const keys = Object.keys(obj) as (keyof ApiCenterApi)[]; + return (keys.length === Object.keys({} as ApiCenterApi).length) && keys.every(key => key in obj); +} export type ApiCenterApi = { id: string; location: string; @@ -53,7 +59,10 @@ export type ApiCenterEnvironment = { }; export type GeneralApiCenterApiVersion = ApiCenterApiVersion | DataPlaneApiCenterApiVersion; - +export function isApiVersionManagement(obj: GeneralApiCenterApiVersion): obj is ApiCenterApiVersion { + const keys = Object.keys(obj) as (keyof ApiCenterApiVersion)[]; + return (keys.length === Object.keys({} as ApiCenterApiVersion).length) && keys.every(key => key in obj); +} export type ApiCenterApiVersion = { id: string; location: string; @@ -84,6 +93,11 @@ export type ApiCenterApiDeployment = { export type GeneralApiCenterApiVersionDefinition = ApiCenterApiVersionDefinition | DataPlaneApiCenterApiVersionDefinition; +export function isDefinitionManagement(obj: GeneralApiCenterApiVersionDefinition): obj is ApiCenterApiVersionDefinition { + const keys = Object.keys(obj) as (keyof ApiCenterApiVersionDefinition)[]; + return (keys.length === Object.keys({} as ApiCenterApiVersionDefinition).length) && keys.every(key => key in obj); +} + export type ApiCenterApiVersionDefinition = { id: string; location: string; diff --git a/src/azure/azureLogin/azureAuth.ts b/src/azure/azureLogin/azureAuth.ts index 2f2f88ea..b4c5855b 100644 --- a/src/azure/azureLogin/azureAuth.ts +++ b/src/azure/azureLogin/azureAuth.ts @@ -153,8 +153,7 @@ export namespace AzureAuth { type AuthProviderId = "microsoft" | "microsoft-sovereign-cloud"; export function getConfiguredAuthProviderId(): AuthProviderId { - const type = AzureAuth.getConfiguredAzureEnv().name === Environment.AzureCloud.name ? "microsoft" : "microsoft-sovereign-cloud"; - return type; + return AzureAuth.getConfiguredAzureEnv().name === Environment.AzureCloud.name ? "microsoft" : "microsoft-sovereign-cloud"; } export function isReady(provider: AzureSessionProvider): provider is ReadyAzureSessionProvider { diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index 808e96f2..fea7200f 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -5,7 +5,7 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fse from 'fs-extra'; import * as vscode from 'vscode'; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName } from "../../azure/ApiCenter/contracts"; +import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName, isApiServerManagement } from "../../azure/ApiCenter/contracts"; import { ext } from "../../extensionVariables"; import { ApiCenterTreeItem } from "../../tree/ApiCenterTreeItem"; import { ApisTreeItem } from "../../tree/ApisTreeItem"; @@ -16,7 +16,7 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre const apiCenterNode = await ext.treeDataProvider.showTreeItemPicker(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } - if (!('id' in node.apiCenter)) { + if (!(isApiServerManagement(node.apiCenter))) { return; } diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index c315b6c1..57c0a366 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApi, isApiManagement } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiDeploymentsTreeItem } from "./ApiDeploymentsTreeItem"; import { ApiVersionsTreeItem } from "./ApiVersionsTreeItem"; @@ -21,7 +21,7 @@ export class ApiTreeItem extends AzExtParentTreeItem { this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); - if ('id' in apiCenterApi) { + if (isApiManagement(apiCenterApi)) { this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, apiCenterApi); } } @@ -31,11 +31,11 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get id(): string { - return 'id' in this._apiCenterApi ? this._apiCenterApi.id : this._apiCenterApi.name; + return isApiManagement(this._apiCenterApi) ? this._apiCenterApi.id : this._apiCenterApi.name; } public get label(): string { - return 'id' in this._apiCenterApi ? this._apiCenterApi.properties.title : this._apiCenterApi.title; + return isApiManagement(this._apiCenterApi) ? this._apiCenterApi.properties.title : this._apiCenterApi.title; } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index 2ff65ac3..8a64fce9 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersionDefinition, isDefinitionManagement } from "../azure/ApiCenter/contracts"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; @@ -15,7 +15,7 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public apiCenterApiVersionName: string, public apiCenterApiVersionDefinition: GeneralApiCenterApiVersionDefinition) { super(parent); - if ('id' in apiCenterApiVersionDefinition) { + if (isDefinitionManagement(apiCenterApiVersionDefinition)) { this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); } else { this.contextValue = ApiVersionDefinitionTreeItem.dataPlaneContextValue + "-" + apiCenterApiVersionDefinition.name.toLowerCase(); @@ -27,10 +27,10 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { } public get id(): string { - return 'id' in this.apiCenterApiVersionDefinition ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name + return isDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name } public get label(): string { - return 'id' in this.apiCenterApiVersionDefinition ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; + return isDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; } } diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index d4295b66..2e07df9c 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -5,7 +5,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition, isApiVersionManagement } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionTreeItem } from "./ApiVersionDefinitionTreeItem"; @@ -54,7 +54,7 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { } private async getDefinitions(): Promise { - if ('id' in this._apiCenterApiVersion) { + if (isApiVersionManagement(this._apiCenterApiVersion)) { const resourceGroupName = getResourceGroupFromId(this._apiCenterApiVersion.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index 4a59f2b5..458a7d2d 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersion, isApiVersionManagement } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionsTreeItem } from "./ApiVersionDefinitionsTreeItem"; @@ -28,11 +28,11 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get id(): string { - return 'id' in this._apiCenterApiVersion ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; + return isApiVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; } public get label(): string { - return 'id' in this._apiCenterApiVersion ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; + return isApiVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index 40aed7fd..6312531d 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -5,7 +5,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApi, GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApi, GeneralApiCenterApiVersion, isApiServerManagement } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -41,7 +41,7 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { } private async getApiVersions(): Promise { - if ('id' in this._apiCenterApi) { + if (isApiServerManagement(this._apiCenterApi)) { const resourceGroupName = getResourceGroupFromId(this._apiCenterApi.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); const apis = await apiCenterService.getApiCenterApiVersions(this._apiCenterApi.name); diff --git a/src/tree/ApisTreeItem.ts b/src/tree/ApisTreeItem.ts index f2cbd69a..2b33c332 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -5,7 +5,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenter, GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenter, GeneralApiCenterApi, isApiServerManagement } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiTreeItem } from "./ApiTreeItem"; export class ApisTreeItem extends AzExtParentTreeItem { @@ -50,7 +50,7 @@ export class ApisTreeItem extends AzExtParentTreeItem { } private async getApis(): Promise { - if ('id' in this.apiCenter) { + if (isApiServerManagement(this.apiCenter)) { const resourceGroupName = getResourceGroupFromId(this.apiCenter.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this.apiCenter.name); const apis = await apiCenterService.getApiCenterApis(this.searchContent); diff --git a/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index fd19e51c..e1071587 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -8,6 +8,7 @@ import { showSavePromptConfigKey } from "../../../constants"; import { localize } from "../../../localize"; import { ApiVersionDefinitionTreeItem } from "../../ApiVersionDefinitionTreeItem"; import { Editor, EditorOptions } from "../Editor"; + export class OpenApiEditor extends Editor { constructor() { super(showSavePromptConfigKey); From 6a6c8c15dce771c856e0c306a280362ac2061970 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 12:43:17 +0800 Subject: [PATCH 06/29] feat: update --- src/azure/ApiCenter/ApiCenterDistinct.ts | 19 ++++++++++++++ src/azure/ApiCenter/contracts.ts | 25 ++++++------------- .../registerStepByStep.ts | 5 ++-- src/tree/ApiTreeItem.ts | 9 ++++--- src/tree/ApiVersionDefinitionTreeItem.ts | 10 ++++---- src/tree/ApiVersionDefinitionsTreeItem.ts | 5 ++-- src/tree/ApiVersionTreeItem.ts | 7 +++--- src/tree/ApiVersionsTreeItem.ts | 5 ++-- src/tree/ApisTreeItem.ts | 5 ++-- 9 files changed, 52 insertions(+), 38 deletions(-) create mode 100644 src/azure/ApiCenter/ApiCenterDistinct.ts diff --git a/src/azure/ApiCenter/ApiCenterDistinct.ts b/src/azure/ApiCenter/ApiCenterDistinct.ts new file mode 100644 index 00000000..fbda108a --- /dev/null +++ b/src/azure/ApiCenter/ApiCenterDistinct.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { ApiCenter, ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, GeneralApiCenter, GeneralApiCenterApi, GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "./contracts"; + +export function isApiCenterServiceManagement(obj: GeneralApiCenter): obj is ApiCenter { + return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; +} + +export function isApiCenterManagement(obj: GeneralApiCenterApi): obj is ApiCenterApi { + return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; +} + +export function isApiCenterVersionManagement(obj: GeneralApiCenterApiVersion): obj is ApiCenterApiVersion { + return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; +} + +export function isApiCenterVersionDefinitionManagement(obj: GeneralApiCenterApiVersionDefinition): obj is ApiCenterApiVersionDefinition { + return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; +} diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index b41c1142..4fa8c3d6 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. - -export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; -export function isApiServerManagement(obj: GeneralApiCenter): obj is ApiCenter { - const keys = Object.keys(obj) as (keyof ApiCenter)[]; - return (keys.length === Object.keys({} as ApiCenter).length) && keys.every(key => key in obj); +export enum IApiCenterType { + management = "management", + dataplane = "dataplane" } +export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; + export type ApiCenter = { id: string; location: string; @@ -22,10 +22,7 @@ export type DataPlaneApiCenter = { } export type GeneralApiCenterApi = ApiCenterApi | DataPlaneApiCenterApi; -export function isApiManagement(obj: GeneralApiCenterApi): obj is ApiCenterApi { - const keys = Object.keys(obj) as (keyof ApiCenterApi)[]; - return (keys.length === Object.keys({} as ApiCenterApi).length) && keys.every(key => key in obj); -} + export type ApiCenterApi = { id: string; location: string; @@ -59,10 +56,7 @@ export type ApiCenterEnvironment = { }; export type GeneralApiCenterApiVersion = ApiCenterApiVersion | DataPlaneApiCenterApiVersion; -export function isApiVersionManagement(obj: GeneralApiCenterApiVersion): obj is ApiCenterApiVersion { - const keys = Object.keys(obj) as (keyof ApiCenterApiVersion)[]; - return (keys.length === Object.keys({} as ApiCenterApiVersion).length) && keys.every(key => key in obj); -} + export type ApiCenterApiVersion = { id: string; location: string; @@ -93,11 +87,6 @@ export type ApiCenterApiDeployment = { export type GeneralApiCenterApiVersionDefinition = ApiCenterApiVersionDefinition | DataPlaneApiCenterApiVersionDefinition; -export function isDefinitionManagement(obj: GeneralApiCenterApiVersionDefinition): obj is ApiCenterApiVersionDefinition { - const keys = Object.keys(obj) as (keyof ApiCenterApiVersionDefinition)[]; - return (keys.length === Object.keys({} as ApiCenterApiVersionDefinition).length) && keys.every(key => key in obj); -} - export type ApiCenterApiVersionDefinition = { id: string; location: string; diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index fea7200f..1a36abd8 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -4,8 +4,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fse from 'fs-extra'; import * as vscode from 'vscode'; +import { isApiCenterServiceManagement } from "../../azure/ApiCenter/ApiCenterDistinct"; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName, isApiServerManagement } from "../../azure/ApiCenter/contracts"; +import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName } from "../../azure/ApiCenter/contracts"; import { ext } from "../../extensionVariables"; import { ApiCenterTreeItem } from "../../tree/ApiCenterTreeItem"; import { ApisTreeItem } from "../../tree/ApisTreeItem"; @@ -16,7 +17,7 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre const apiCenterNode = await ext.treeDataProvider.showTreeItemPicker(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } - if (!(isApiServerManagement(node.apiCenter))) { + if (!(isApiCenterServiceManagement(node.apiCenter))) { return; } diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index 57c0a366..011239b8 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,7 +2,8 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApi, isApiManagement } from "../azure/ApiCenter/contracts"; +import { isApiCenterManagement } from "../azure/ApiCenter/ApiCenterDistinct"; +import { GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiDeploymentsTreeItem } from "./ApiDeploymentsTreeItem"; import { ApiVersionsTreeItem } from "./ApiVersionsTreeItem"; @@ -21,7 +22,7 @@ export class ApiTreeItem extends AzExtParentTreeItem { this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); - if (isApiManagement(apiCenterApi)) { + if (isApiCenterManagement(apiCenterApi)) { this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, apiCenterApi); } } @@ -31,11 +32,11 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get id(): string { - return isApiManagement(this._apiCenterApi) ? this._apiCenterApi.id : this._apiCenterApi.name; + return isApiCenterManagement(this._apiCenterApi) ? this._apiCenterApi.id : this._apiCenterApi.name; } public get label(): string { - return isApiManagement(this._apiCenterApi) ? this._apiCenterApi.properties.title : this._apiCenterApi.title; + return isApiCenterManagement(this._apiCenterApi) ? this._apiCenterApi.properties.title : this._apiCenterApi.title; } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index 8a64fce9..315e8ad8 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,8 +2,8 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApiVersionDefinition, isDefinitionManagement } from "../azure/ApiCenter/contracts"; - +import { isApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; +import { GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; public static dataPlaneContextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" @@ -15,7 +15,7 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public apiCenterApiVersionName: string, public apiCenterApiVersionDefinition: GeneralApiCenterApiVersionDefinition) { super(parent); - if (isDefinitionManagement(apiCenterApiVersionDefinition)) { + if (isApiCenterVersionDefinitionManagement(apiCenterApiVersionDefinition)) { this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); } else { this.contextValue = ApiVersionDefinitionTreeItem.dataPlaneContextValue + "-" + apiCenterApiVersionDefinition.name.toLowerCase(); @@ -27,10 +27,10 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { } public get id(): string { - return isDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name + return isApiCenterVersionDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name } public get label(): string { - return isDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; + return isApiCenterVersionDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; } } diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index 2e07df9c..101bfa82 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -4,8 +4,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { isApiCenterVersionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition, isApiVersionManagement } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionTreeItem } from "./ApiVersionDefinitionTreeItem"; @@ -54,7 +55,7 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { } private async getDefinitions(): Promise { - if (isApiVersionManagement(this._apiCenterApiVersion)) { + if (isApiCenterVersionManagement(this._apiCenterApiVersion)) { const resourceGroupName = getResourceGroupFromId(this._apiCenterApiVersion.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index 458a7d2d..f9e9484e 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,7 +2,8 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { GeneralApiCenterApiVersion, isApiVersionManagement } from "../azure/ApiCenter/contracts"; +import { isApiCenterVersionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; +import { GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionsTreeItem } from "./ApiVersionDefinitionsTreeItem"; @@ -28,11 +29,11 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get id(): string { - return isApiVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; + return isApiCenterVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; } public get label(): string { - return isApiVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; + return isApiCenterVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index 6312531d..d8caecd9 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -4,8 +4,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { isApiCenterManagement } from "../azure/ApiCenter/ApiCenterDistinct"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApi, GeneralApiCenterApiVersion, isApiServerManagement } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenterApi, GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -41,7 +42,7 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { } private async getApiVersions(): Promise { - if (isApiServerManagement(this._apiCenterApi)) { + if (isApiCenterManagement(this._apiCenterApi)) { const resourceGroupName = getResourceGroupFromId(this._apiCenterApi.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); const apis = await apiCenterService.getApiCenterApiVersions(this._apiCenterApi.name); diff --git a/src/tree/ApisTreeItem.ts b/src/tree/ApisTreeItem.ts index 2b33c332..8681973e 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -4,8 +4,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { isApiCenterServiceManagement } from "../azure/ApiCenter/ApiCenterDistinct"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenter, GeneralApiCenterApi, isApiServerManagement } from "../azure/ApiCenter/contracts"; +import { GeneralApiCenter, GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; import { ApiTreeItem } from "./ApiTreeItem"; export class ApisTreeItem extends AzExtParentTreeItem { @@ -50,7 +51,7 @@ export class ApisTreeItem extends AzExtParentTreeItem { } private async getApis(): Promise { - if (isApiServerManagement(this.apiCenter)) { + if (isApiCenterServiceManagement(this.apiCenter)) { const resourceGroupName = getResourceGroupFromId(this.apiCenter.id); const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this.apiCenter.name); const apis = await apiCenterService.getApiCenterApis(this.searchContent); From 25a442b44c7b0f4160755cc0dbae791f6b196a7e Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 12:56:07 +0800 Subject: [PATCH 07/29] test: update test case --- src/test/unit/commands/exportApi.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index cfbed1ba..a63fdbb0 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -43,9 +43,12 @@ describe("export API test cases", () => { "fakeApiCenterApiName", "fakeApiCenterApiVersionName", { + id: "fakeId", properties: { + title: "name", specification: { - name: "fakeName" + name: "openapi", + version: "fakeVersion", } } } as ApiCenterApiVersionDefinition From 224ece043f0b9dd40fc702090bef1161361b0dff Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 13:02:36 +0800 Subject: [PATCH 08/29] feat: add feautres --- package.json | 4 ++-- src/azure/ApiCenter/ApiCenterDistinct.ts | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 026c9f28..53bbedb0 100644 --- a/package.json +++ b/package.json @@ -245,7 +245,7 @@ }, { "command": "azure-api-center.open-api-docs", - "when": "view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi" + "when": "(view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi) || (view == apiCenterWorkspace && viewItem == azureApiCenterApiVersionDataPlaneDefinitionTreeItem-openapi)" }, { "command": "azure-api-center.open-postman", @@ -253,7 +253,7 @@ }, { "command": "azure-api-center.generate-api-client", - "when": "view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi" + "when": "(view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi) || (view == apiCenterWorkspace && viewItem == azureApiCenterApiVersionDataPlaneDefinitionTreeItem-openapi)" }, { "command": "azure-api-center.importOpenApiByFile", diff --git a/src/azure/ApiCenter/ApiCenterDistinct.ts b/src/azure/ApiCenter/ApiCenterDistinct.ts index fbda108a..8845c473 100644 --- a/src/azure/ApiCenter/ApiCenterDistinct.ts +++ b/src/azure/ApiCenter/ApiCenterDistinct.ts @@ -2,18 +2,22 @@ // Licensed under the MIT license. import { ApiCenter, ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, GeneralApiCenter, GeneralApiCenterApi, GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "./contracts"; -export function isApiCenterServiceManagement(obj: GeneralApiCenter): obj is ApiCenter { +function checkObj(obj: any): boolean { return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; } +export function isApiCenterServiceManagement(obj: GeneralApiCenter): obj is ApiCenter { + return checkObj(obj); +} + export function isApiCenterManagement(obj: GeneralApiCenterApi): obj is ApiCenterApi { - return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; + return checkObj(obj); } export function isApiCenterVersionManagement(obj: GeneralApiCenterApiVersion): obj is ApiCenterApiVersion { - return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; + return checkObj(obj); } export function isApiCenterVersionDefinitionManagement(obj: GeneralApiCenterApiVersionDefinition): obj is ApiCenterApiVersionDefinition { - return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; + return checkObj(obj); } From 48d8519105078c32bd00b9dd2214a7516425e180 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 15:48:06 +0800 Subject: [PATCH 09/29] docs: strings --- package.json | 12 ++++++------ package.nls.json | 7 ++++++- src/commands/addDataPlaneApis.ts | 7 ++++--- src/commands/exportApi.ts | 5 +++-- src/uiStrings.ts | 5 +++++ 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 53bbedb0..760da1f0 100644 --- a/package.json +++ b/package.json @@ -167,25 +167,25 @@ }, { "command": "azure-api-center.apiCenterWorkspace.addApis", - "title": "Add Data Plane Api", + "title": "%azure-api-center.commands.apiCenterWorkspace.addApis.title%", "icon": "$(add)", "category": "Azure API Center" }, { "command": "azure-api-center.apiCenterWorkspace.refresh", - "title": "Refresh", + "title": "%azure-api-center.commands.apiCenterTreeView.refresh.title%", "icon": "$(refresh)", "category": "Azure API Center" }, { "command": "azure-api-center.apiCenterWorkspace.collapse", - "title": "Collapse", + "title": "%azure-api-center.commands.apiCenterWorkspace.collapse.title%", "icon": "$(collapse-all)", "category": "Azure API Center" }, { "command": "azure-api-center.apiCenterWorkspace.removeApi", - "title": "Remove Dataplane", + "title": "%azure-api-center.commands.apiCenterWorkspace.removeApis.title%", "icon": "$(close)", "category": "Azure API Center" } @@ -194,13 +194,13 @@ "api-center-treeview": [ { "id": "apiCenterTreeView", - "name": "API Center", + "name": "%azure-api-center.views.api-center-treeview.controlplane.title%", "icon": "media/api-center-icon.svg", "contextualTitle": "Azure API Center" }, { "id": "apiCenterWorkspace", - "name": "API Center Data View", + "name": "%azure-api-center.views.api-center-treeview.dataplane.title%", "visibility": "visible" } ] diff --git a/package.nls.json b/package.nls.json index eb7c97f8..6183833c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -26,5 +26,10 @@ "azure-api-center.commands.deleteCustomFunction.title": "Delete", "azure-api-center.chatParticipants.apicenter.description": "Build, discover, and consume great APIs.", "azure-api-center.chatParticipants.apicenter.commands.list.description": "List available APIs.", - "azure-api-center.chatParticipants.apicenter.commands.find.description": "Find an API given a search query." + "azure-api-center.chatParticipants.apicenter.commands.find.description": "Find an API given a search query.", + "azure-api-center.commands.apiCenterWorkspace.collapse.title": "Collapse", + "azure-api-center.views.api-center-treeview.controlplane.title": "Azure API Center", + "azure-api-center.views.api-center-treeview.dataplane.title": "API Center Data View", + "azure-api-center.commands.apiCenterWorkspace.addApis.title": "Connect to an API Center", + "azure-api-center.commands.apiCenterWorkspace.removeApis.title": "Remove API Center", } diff --git a/src/commands/addDataPlaneApis.ts b/src/commands/addDataPlaneApis.ts index c646893a..552bfdcf 100644 --- a/src/commands/addDataPlaneApis.ts +++ b/src/commands/addDataPlaneApis.ts @@ -4,16 +4,17 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; import { ext } from "../extensionVariables"; +import { UiStrings } from "../uiStrings"; export async function getDataPlaneApis(context: IActionContext): Promise { - const endpointUrl = await vscode.window.showInputBox({ title: "Input Runtime URL", ignoreFocusOut: true }); + const endpointUrl = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneRuntimeUrl, ignoreFocusOut: true }); if (!endpointUrl) { return; } - const clientid = await vscode.window.showInputBox({ title: "Input Client ID", ignoreFocusOut: true }); + const clientid = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneClientId, ignoreFocusOut: true }); if (!clientid) { return; } - const tenantid = await vscode.window.showInputBox({ title: "Input Tenant ID", ignoreFocusOut: true }); + const tenantid = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneTenantId, ignoreFocusOut: true }); if (!tenantid) { return; } diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index 542515bb..eb84d756 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -13,6 +13,7 @@ import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; import { TelemetryClient } from '../common/telemetryClient'; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { UiStrings } from "../uiStrings"; import { createTemporaryFolder } from "../utils/fsUtil"; export namespace ExportAPI { export async function exportApi( @@ -71,7 +72,7 @@ export namespace ExportAPI { client.get(url, (response) => { if (response.statusCode !== 200) { - reject(new Error(`request failed with status code: ${response.statusCode}`)); + reject(new Error(vscode.l10n.t(UiStrings.RequestFailedWithStatusCode, response.statusCode!))); return; } @@ -85,7 +86,7 @@ export namespace ExportAPI { }); }).on('error', (err) => { - reject(new Error('download error: ' + err.message)); + reject(new Error(vscode.l10n.t(UiStrings.DownloadDefinitionFileWithErrorMsg, err.message))); }); }); } diff --git a/src/uiStrings.ts b/src/uiStrings.ts index df19fdb0..ab34bfed 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -119,4 +119,9 @@ export class UiStrings { static readonly RulesNotEnabled = vscode.l10n.t("API Analysis is not enabled. Click here to enable it."); static readonly OpenApiCenterFolder = vscode.l10n.t("Rules folder is required to be opened to enable live API linting. Click 'Yes' to open the rules folder in current window."); static readonly NoRuleFileFound = vscode.l10n.t("No rule file ('{0}') found in the rules folder '{1}'. Please add a rule file to enable API Analysis."); + static readonly AddDataPlaneRuntimeUrl = vscode.l10n.t("Input Runtime URL"); + static readonly AddDataPlaneClientId = vscode.l10n.t("Input Entra App Client ID"); + static readonly AddDataPlaneTenantId = vscode.l10n.t("Input Entra App Tenant ID"); + static readonly RequestFailedWithStatusCode = vscode.l10n.t("request failed with status code: ${0}"); + static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("download error: {0}"); } From b24677f8f5f09c988a7dcbbf2670c23c3a071b7f Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 16:04:02 +0800 Subject: [PATCH 10/29] test: update --- src/uiStrings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uiStrings.ts b/src/uiStrings.ts index ab34bfed..b9689f20 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -122,6 +122,6 @@ export class UiStrings { static readonly AddDataPlaneRuntimeUrl = vscode.l10n.t("Input Runtime URL"); static readonly AddDataPlaneClientId = vscode.l10n.t("Input Entra App Client ID"); static readonly AddDataPlaneTenantId = vscode.l10n.t("Input Entra App Tenant ID"); - static readonly RequestFailedWithStatusCode = vscode.l10n.t("request failed with status code: ${0}"); - static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("download error: {0}"); + static readonly RequestFailedWithStatusCode = vscode.l10n.t("Request failed with status code: {0}"); + static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("Download API Center Definition File error: {0}"); } From c8ab52abf31b965b11eb996f80d2534bf22576d6 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 16:17:45 +0800 Subject: [PATCH 11/29] feat: update command --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 760da1f0..7cc1d328 100644 --- a/package.json +++ b/package.json @@ -411,6 +411,10 @@ { "command": "azure-api-center.apiCenterWorkspace.removeApi", "when": "never" + }, + { + "command": "azure-api-center.apiCenterWorkspace.collapse", + "when": "never" } ] }, From 7fc420173ce9b82f27ef03a08a7a9dbf9f6e01a8 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Thu, 1 Aug 2024 20:24:04 +0800 Subject: [PATCH 12/29] feat: change to b type --- src/azure/ApiCenter/ApiCenterDefinition.ts | 80 ++++++++++++++++++++++ src/tree/ApiVersionDefinitionTreeItem.ts | 15 ++-- src/tree/ApiVersionDefinitionsTreeItem.ts | 6 +- 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 src/azure/ApiCenter/ApiCenterDefinition.ts diff --git a/src/azure/ApiCenter/ApiCenterDefinition.ts b/src/azure/ApiCenter/ApiCenterDefinition.ts new file mode 100644 index 00000000..5e99c7a2 --- /dev/null +++ b/src/azure/ApiCenter/ApiCenterDefinition.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; +import { ApiCenterApiVersion, ApiCenterApiVersionDefinition, DataPlaneApiCenterApiVersion, DataPlaneApiCenterApiVersionDefinition } from "./contracts"; +export type IVersionBase = { + getLable: () => string, + getId: () => string, + getContext: () => string, + getChild: (context: ISubscriptionContext) => Promise +} + +export class ApiCenterVersionManagement implements IVersionBase { + constructor(public data: ApiCenterApiVersion) { } + async getChild(context: ISubscriptionContext): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, this._apiCenterName); + + const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(this._apiCenterApiName, this.data.name); + this._nextLink = definitions.nextLink; + return definitions.value; + }; + getLable() { + return this.data.properties.title; + } + getId() { + return this.data.id; + } + getContext() { + return "azureApiCenterApiVersion" + } +} + +export class ApiCenterVersionDataplane implements IVersionBase { + constructor(public data: DataPlaneApiCenterApiVersion) { } + getChild: () => IDefinitionBase; + getLable() { + return this.data.name; + } + getId() { + return this.data.name; + } + getContext() { + return "azureApiCenterApiVersion" + } +} + +export type IDefinitionBase = { + getLabel: () => string, + getId: () => string, + getContext: () => string, +} + +export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { + constructor(public data: ApiCenterApiVersionDefinition) { + } + getContext() { + return "azureApiCenterApiVersionDefinitionTreeItem" + this.data.properties.specification.name.toLowerCase(); + }; + getLabel() { + return this.data.properties.title; + }; + getId() { + return this.data.id; + }; +} + +export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { + constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } + getContext() { + return "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" + this.data.name.toLowerCase(); + }; + getLabel() { + return this.data.name; + }; + getId() { + return this.data.name; + }; +} diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index 315e8ad8..174016a6 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,8 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { isApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; +import { IDefinitionBase } from "../azure/ApiCenter/ApiCenterDefinition"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; public static dataPlaneContextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" @@ -13,13 +12,9 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public apiCenterName: string, public apiCenterApiName: string, public apiCenterApiVersionName: string, - public apiCenterApiVersionDefinition: GeneralApiCenterApiVersionDefinition) { + public apiCenterApiVersionDefinition: IDefinitionBase) { super(parent); - if (isApiCenterVersionDefinitionManagement(apiCenterApiVersionDefinition)) { - this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); - } else { - this.contextValue = ApiVersionDefinitionTreeItem.dataPlaneContextValue + "-" + apiCenterApiVersionDefinition.name.toLowerCase(); - } + this.contextValue = apiCenterApiVersionDefinition.getContext(); } public get iconPath(): TreeItemIconPath { @@ -27,10 +22,10 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { } public get id(): string { - return isApiCenterVersionDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.id : this.apiCenterApiVersionDefinition.name + return this.apiCenterApiVersionDefinition.getId(); } public get label(): string { - return isApiCenterVersionDefinitionManagement(this.apiCenterApiVersionDefinition) ? this.apiCenterApiVersionDefinition.properties.title : this.apiCenterApiVersionDefinition.name; + return this.apiCenterApiVersionDefinition.getLabel(); } } diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index 101bfa82..0015a702 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -49,7 +49,7 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { this._apiCenterName, this._apiCenterApiName, this._apiCenterApiVersion.name, - difinition), + generateApiDefinition(definition)), difinition => difinition.name ); } @@ -70,6 +70,10 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { } } + // private generateApiDefinition(data: GeneralApiCenterApiVersionDefinition) { + // if (isApiCenterVersionManagement(data)) + // } + public hasMoreChildrenImpl(): boolean { return this._nextLink !== undefined; } From c86d654438f11ab5ea7a4ea4ad6c45e76a2ef45c Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 1 Aug 2024 22:43:13 +0800 Subject: [PATCH 13/29] feat: adjust treeitem view structure --- package-lock.json | 166 +++++++++++++-- package.json | 3 +- src/azure/ApiCenter/ApiCenterDefinition.ts | 200 ++++++++++++++++-- src/azure/ApiCenter/ApiCenterDistinct.ts | 23 -- src/commands/editOpenApi.ts | 3 +- src/commands/exportApi.ts | 57 ++--- src/commands/importOpenApi.ts | 4 +- .../registerStepByStep.ts | 8 +- src/test/unit/commands/exportApi.test.ts | 29 ++- src/tree/ApiCenterTreeItem.ts | 4 +- src/tree/ApiTreeItem.ts | 19 +- src/tree/ApiVersionDefinitionTreeItem.ts | 4 +- src/tree/ApiVersionDefinitionsTreeItem.ts | 48 +---- src/tree/ApiVersionTreeItem.ts | 15 +- src/tree/ApiVersionsTreeItem.ts | 32 +-- src/tree/ApisTreeItem.ts | 28 +-- src/tree/DataPlaneAccount.ts | 4 +- src/tree/Editors/openApi/OpenApiEditor.ts | 38 +++- src/utils/apiSpecificationUtils.ts | 3 +- tsconfig.json | 1 + 20 files changed, 452 insertions(+), 237 deletions(-) delete mode 100644 src/azure/ApiCenter/ApiCenterDistinct.ts diff --git a/package-lock.json b/package-lock.json index 9adaa908..78c51209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", + "node-fetch": "^3.3.2", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", @@ -509,6 +510,25 @@ "node": ">= 0.12" } }, + "node_modules/@azure/ms-rest-js/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@azure/ms-rest-js/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -3872,6 +3892,25 @@ "node-fetch": "^2.6.12" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3891,6 +3930,14 @@ "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", "dev": true }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-utils": { "version": "1.2.21", "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", @@ -4702,6 +4749,28 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4864,6 +4933,17 @@ "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7427,23 +7507,39 @@ "node": "^16 || ^18 || >= 20" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-fetch-h2": { @@ -9604,6 +9700,25 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/swagger2openapi/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/swagger2openapi/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -10215,6 +10330,14 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10582,6 +10705,25 @@ "node": ">=6" } }, + "node_modules/widdershins/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/widdershins/node_modules/oas-validator": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-4.0.8.tgz", diff --git a/package.json b/package.json index 7cc1d328..34f0ecec 100644 --- a/package.json +++ b/package.json @@ -277,7 +277,7 @@ }, { "command": "azure-api-center.generateMarkdownDocument", - "when": "view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi" + "when": "(view == apiCenterTreeView && viewItem == azureApiCenterApiVersionDefinitionTreeItem-openapi) || (view == apiCenterWorkspace && viewItem == azureApiCenterApiVersionDataPlaneDefinitionTreeItem-openapi)" }, { "command": "azure-api-center.registerApi", @@ -538,6 +538,7 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", + "node-fetch": "^3.3.2", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", diff --git a/src/azure/ApiCenter/ApiCenterDefinition.ts b/src/azure/ApiCenter/ApiCenterDefinition.ts index 5e99c7a2..e759e3c8 100644 --- a/src/azure/ApiCenter/ApiCenterDefinition.ts +++ b/src/azure/ApiCenter/ApiCenterDefinition.ts @@ -2,22 +2,171 @@ // Licensed under the MIT license. import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApiVersion, ApiCenterApiVersionDefinition, DataPlaneApiCenterApiVersion, DataPlaneApiCenterApiVersionDefinition } from "./contracts"; +import { ApiCenterDataPlaneService } from "../ApiCenter/ApiCenterDataPlaneAPIs"; +import { ApiCenterService } from "../ApiCenter/ApiCenterService"; +import { + ApiCenter, + ApiCenterApi, + ApiCenterApiVersion, + ApiCenterApiVersionDefinition, + DataPlaneApiCenter, + DataPlaneApiCenterApi, + DataPlaneApiCenterApiVersion, + DataPlaneApiCenterApiVersionDefinition, + GeneralApiCenterApi, + GeneralApiCenterApiVersion, + GeneralApiCenterApiVersionDefinition +} from "./contracts"; + +export type IApiCenterServiceBase = { + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getName: () => string, + getId: () => string, + getChild: (context: ISubscriptionContext, content: string) => Promise; + generateChild: (data: GeneralApiCenterApi) => IApiCenterBase; +} + +export class ApiCenterServiceManagement implements IApiCenterServiceBase { + constructor(public data: ApiCenter) { } + getName(): string { + return this.data.name; + } + getId(): string { + return this.data.id + } + async getChild(context: ISubscriptionContext, content: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, this.data.name); + const apis = await apiCenterService.getApiCenterApis(content); + this._nextLink = apis.nextLink; + return apis.value; + } + generateChild(data: GeneralApiCenterApi): IApiCenterBase { + return new ApiCenterApiManagement(data as ApiCenterApi); + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } +} + +export class ApiCenterServiceDataPlane implements IApiCenterServiceBase { + constructor(public data: DataPlaneApiCenter) { } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + getName(): string { + return this.data.name; + } + getId(): string { + return this.data.name; + } + async getChild(context: ISubscriptionContext, content: string): Promise { + let server = new ApiCenterDataPlaneService(context); + const res = await server.getApiCenterApis(); + this._nextLink = res.nextLink; + return res.value; + } + generateChild(data: GeneralApiCenterApi): IApiCenterBase { + return new ApiCenterApiDataPlane(data as DataPlaneApiCenterApi); + } +} + +export type IApiCenterBase = { + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getName: () => string, + getId: () => string, + getLable: () => string; + getChild: (context: ISubscriptionContext, apiName: string) => Promise; + generateChild: (data: GeneralApiCenterApiVersion) => IVersionBase; +} + +export class ApiCenterApiManagement implements IApiCenterBase { + constructor(public data: ApiCenterApi) { } + getData(): ApiCenterApi { + return this.data; + } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + getId(): string { + return this.data.id; + } + getLable(): string { + return this.data.properties.title; + } + async getChild(context: ISubscriptionContext, apiName: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); + const apis = await apiCenterService.getApiCenterApiVersions(this.data.name); + this._nextLink = apis.nextLink; + return apis.value; + } + generateChild(data: GeneralApiCenterApiVersion): IVersionBase { + return new ApiCenterVersionManagement(data as ApiCenterApiVersion); + } +} + +export class ApiCenterApiDataPlane implements IApiCenterBase { + constructor(public data: DataPlaneApiCenterApi) { } + getLable(): string { + return this.data.name; + } + getId(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + getName(): string { + return this.data.name; + } + async getChild(context: ISubscriptionContext, apiName: string): Promise { + const server = new ApiCenterDataPlaneService(context); + const res = await server.getAPiCenterApiVersions(this.data.name); + this._nextLink = res.nextLink; + return res.value; + } + generateChild(data: GeneralApiCenterApiVersion): IVersionBase { + return new ApiCenterVersionDataplane(data as DataPlaneApiCenterApiVersion); + } +} + export type IVersionBase = { + _nextLink: string | undefined; + getNextLink: () => string | undefined; getLable: () => string, getId: () => string, - getContext: () => string, - getChild: (context: ISubscriptionContext) => Promise + getName: () => string, + getChild: (context: ISubscriptionContext, apiName: string, apiServiceName: string) => Promise; + generateChild: (data: GeneralApiCenterApiVersionDefinition) => IDefinitionBase; } export class ApiCenterVersionManagement implements IVersionBase { constructor(public data: ApiCenterApiVersion) { } - async getChild(context: ISubscriptionContext): Promise { + getNextLink(): string | undefined { + return this._nextLink; + } + _nextLink: string | undefined; + getName(): string { + return this.data.name; + } + generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { + return new ApiCenterVersionDefinitionManagement(data as ApiCenterApiVersionDefinition); + } + async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { const resourceGroupName = getResourceGroupFromId(this.data.id); - const apiCenterService = new ApiCenterService(context, resourceGroupName, this._apiCenterName); + const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); - const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(this._apiCenterApiName, this.data.name); + const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(apiServiceName, this.data.name); this._nextLink = definitions.nextLink; return definitions.value; }; @@ -27,36 +176,49 @@ export class ApiCenterVersionManagement implements IVersionBase { getId() { return this.data.id; } - getContext() { - return "azureApiCenterApiVersion" - } } export class ApiCenterVersionDataplane implements IVersionBase { constructor(public data: DataPlaneApiCenterApiVersion) { } - getChild: () => IDefinitionBase; + getNextLink(): string | undefined { + return this._nextLink; + } + _nextLink: string | undefined; + getName(): string { + return this.data.name; + } + generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { + return new ApiCenterVersionDefinitionDataPlane(data as DataPlaneApiCenterApiVersionDefinition); + } + async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { + const server = new ApiCenterDataPlaneService(context); + const res = await server.getApiCenterApiDefinitions(apiServiceName, this.data.name); + this._nextLink = res.nextLink; + return res.value; + } getLable() { return this.data.name; } getId() { return this.data.name; } - getContext() { - return "azureApiCenterApiVersion" - } } export type IDefinitionBase = { getLabel: () => string, getId: () => string, getContext: () => string, + getName: () => string; } export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { - constructor(public data: ApiCenterApiVersionDefinition) { + constructor(public data: ApiCenterApiVersionDefinition) { } + static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; + getName(): string { + return this.data.name; } getContext() { - return "azureApiCenterApiVersionDefinitionTreeItem" + this.data.properties.specification.name.toLowerCase(); + return ApiCenterVersionDefinitionManagement.contextValue + "-" + this.data.properties.specification.name.toLowerCase(); }; getLabel() { return this.data.properties.title; @@ -68,8 +230,12 @@ export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } + static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; + getName(): string { + return this.data.name; + } getContext() { - return "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" + this.data.name.toLowerCase(); + return ApiCenterVersionDefinitionDataPlane.contextValue + "-" + this.data.specification.name.toLowerCase(); }; getLabel() { return this.data.name; diff --git a/src/azure/ApiCenter/ApiCenterDistinct.ts b/src/azure/ApiCenter/ApiCenterDistinct.ts deleted file mode 100644 index 8845c473..00000000 --- a/src/azure/ApiCenter/ApiCenterDistinct.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import { ApiCenter, ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, GeneralApiCenter, GeneralApiCenterApi, GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "./contracts"; - -function checkObj(obj: any): boolean { - return obj && 'id' in obj && 'properties' in obj && typeof obj.properties == 'object'; -} - -export function isApiCenterServiceManagement(obj: GeneralApiCenter): obj is ApiCenter { - return checkObj(obj); -} - -export function isApiCenterManagement(obj: GeneralApiCenterApi): obj is ApiCenterApi { - return checkObj(obj); -} - -export function isApiCenterVersionManagement(obj: GeneralApiCenterApiVersion): obj is ApiCenterApiVersion { - return checkObj(obj); -} - -export function isApiCenterVersionDefinitionManagement(obj: GeneralApiCenterApiVersionDefinition): obj is ApiCenterApiVersionDefinition { - return checkObj(obj); -} diff --git a/src/commands/editOpenApi.ts b/src/commands/editOpenApi.ts index adc2fce8..4bab1e88 100644 --- a/src/commands/editOpenApi.ts +++ b/src/commands/editOpenApi.ts @@ -2,12 +2,13 @@ // Licensed under the MIT license. import { IActionContext } from "@microsoft/vscode-azext-utils"; import { commands } from "vscode"; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; export async function showOpenApi(actionContext: IActionContext, node?: ApiVersionDefinitionTreeItem) { if (!node) { - node = await ext.treeDataProvider.showTreeItemPicker(ApiVersionDefinitionTreeItem.contextValue, actionContext); + node = await ext.treeDataProvider.showTreeItemPicker(ApiCenterVersionDefinitionManagement.contextValue, actionContext); } await ext.openApiEditor.showEditor(node); commands.executeCommand('setContext', 'isEditorEnabled', true); diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index eb84d756..ea6dc339 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -3,27 +3,25 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fs from "fs-extra"; -import * as http from 'http'; -import * as https from 'https'; +import fetch from 'node-fetch'; import * as path from "path"; import * as vscode from "vscode"; import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; +import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; 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 { UiStrings } from "../uiStrings"; 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); + node = await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); } - if (node.contextValue.startsWith(ApiVersionDefinitionTreeItem.contextValue)) { + if (node?.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionManagement) { const apiCenterService = new ApiCenterService( node?.subscription!, getResourceGroupFromId(node?.id!), @@ -31,21 +29,13 @@ export namespace ExportAPI { const exportedSpec = await apiCenterService.exportSpecification( node?.apiCenterApiName!, node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.name!); + node?.apiCenterApiVersionDefinition.getName()!); await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); - } else if (node.contextValue.startsWith(ApiVersionDefinitionTreeItem.dataPlaneContextValue)) { + } else if (node?.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionDataPlane) { let server = new ApiCenterDataPlaneService(node.parent?.subscription!); let results = await server.exportSpecification(node?.apiCenterApiName!, - node?.apiCenterApiVersionName!, node?.apiCenterApiVersionDefinition.name!); - if (results) { - const folderName = `${node.apiCenterName}-${node.apiCenterApiName}`; - const folderPath = await createTemporaryFolder(folderName); - const localFilePath: string = path.join(folderPath, node.label); - await fs.ensureFile(localFilePath); - await downloadFile(results.value, localFilePath); - const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); - await vscode.window.showTextDocument(document); - } + node?.apiCenterApiVersionName!, node?.apiCenterApiVersionDefinition.getName()); + await writeToTempFile(node!, results.format, results.value); } } @@ -54,7 +44,7 @@ export namespace ExportAPI { } function getFilename(treeItem: ApiVersionDefinitionTreeItem): string { - return `${treeItem.apiCenterApiVersionDefinition.name}`; + return `${treeItem.apiCenterApiVersionDefinition.getName()}`; } async function writeToTempFile(node: ApiVersionDefinitionTreeItem, specFormat: string, specValue: string) { @@ -62,35 +52,12 @@ export namespace ExportAPI { 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 }); + const res = await fetch(specValue); + const rawData = await res.json(); + await ExportAPI.showTempFile(node, JSON.stringify(rawData)); } } - async function downloadFile(url: string, filePath: string): Promise { - return new Promise((resolve, reject) => { - const client = url.startsWith('https') ? https : http; - - client.get(url, (response) => { - if (response.statusCode !== 200) { - reject(new Error(vscode.l10n.t(UiStrings.RequestFailedWithStatusCode, response.statusCode!))); - return; - } - - const fileStream = fs.createWriteStream(filePath); - - response.pipe(fileStream); - - fileStream.on('finish', () => { - fileStream.close(); - resolve(); - }); - - }).on('error', (err) => { - reject(new Error(vscode.l10n.t(UiStrings.DownloadDefinitionFileWithErrorMsg, err.message))); - }); - }); - } - export async function showTempFile(node: ApiVersionDefinitionTreeItem, fileContent: string) { const folderName = getFolderName(node); const folderPath = await createTemporaryFolder(folderName); diff --git a/src/commands/importOpenApi.ts b/src/commands/importOpenApi.ts index 125fd823..f9a12f0f 100644 --- a/src/commands/importOpenApi.ts +++ b/src/commands/importOpenApi.ts @@ -43,7 +43,7 @@ export async function importOpenApi( return apiCenterService.importSpecification( node?.apiCenterApiName!, node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.name!, + node?.apiCenterApiVersionDefinition.getName()!, importPayload); } ).then(async () => { @@ -75,7 +75,7 @@ export async function importOpenApi( return apiCenterService.importSpecification( node?.apiCenterApiName!, node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.name!, + node?.apiCenterApiVersionDefinition.getName()!, importPayload); } ).then(async () => { diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index 1a36abd8..7f92aa42 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -4,7 +4,7 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fse from 'fs-extra'; import * as vscode from 'vscode'; -import { isApiCenterServiceManagement } from "../../azure/ApiCenter/ApiCenterDistinct"; +import { ApiCenterVersionDefinitionManagement } from "../../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName } from "../../azure/ApiCenter/contracts"; import { ext } from "../../extensionVariables"; @@ -17,7 +17,7 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre const apiCenterNode = await ext.treeDataProvider.showTreeItemPicker(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } - if (!(isApiCenterServiceManagement(node.apiCenter))) { + if (node.apiCenter instanceof ApiCenterVersionDefinitionManagement) { return; } @@ -64,8 +64,8 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre return; } - const resourceGroupName = getResourceGroupFromId(node.apiCenter.id); - const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name); + const resourceGroupName = getResourceGroupFromId(node.apiCenter.getId()); + const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.getName()); await createApiResources(apiCenterService, apiTitle, apiKind, apiVersionTitle, apiVersionLifecycleStage, apiDefinitionTitle, specificationName, filePath); diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index a63fdbb0..0df01f1f 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; import * as sinon from "sinon"; +import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApiVersionDefinition } from "../../../azure/ApiCenter/contracts"; import { ExportAPI } from "../../../commands/exportApi"; @@ -19,12 +20,27 @@ abstract class ParentTreeItemBase extends AzExtParentTreeItem { protected abstract createChildTreeItem(index: number): AzExtTreeItem; } +const data: ApiCenterApiVersionDefinition = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + specification: { + name: "fakeName", + version: "fakeVersion", + } + }, + // tslint:disable-next-line:no-reserved-keywords + type: "fakeType", +} + 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); + return new ApiVersionDefinitionTreeItem(this, "fakeApiCenterName", "fakeApiCenterApiName", "fakeApiCenterApiVersionName", new ApiCenterVersionDefinitionManagement(data)); } } @@ -42,16 +58,7 @@ describe("export API test cases", () => { "fakeApiCenterName", "fakeApiCenterApiName", "fakeApiCenterApiVersionName", - { - id: "fakeId", - properties: { - title: "name", - specification: { - name: "openapi", - version: "fakeVersion", - } - } - } as ApiCenterApiVersionDefinition + new ApiCenterVersionDefinitionManagement(data) ); 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"); diff --git a/src/tree/ApiCenterTreeItem.ts b/src/tree/ApiCenterTreeItem.ts index 81e3aaa9..e26aebf1 100644 --- a/src/tree/ApiCenterTreeItem.ts +++ b/src/tree/ApiCenterTreeItem.ts @@ -2,6 +2,7 @@ // Licensed under the MIT license. import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; +import { ApiCenterServiceManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; import { ApiCenter } from "../azure/ApiCenter/contracts"; import { UiStrings } from "../uiStrings"; @@ -9,7 +10,6 @@ import { treeUtils } from "../utils/treeUtils"; import { ApisTreeItem } from "./ApisTreeItem"; import { EnvironmentsTreeItem } from "./EnvironmentsTreeItem"; import { RulesTreeItem } from "./rules/RulesTreeItem"; - export class ApiCenterTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiCenterTreeItemTreeItemChildTypeLabel; public static contextValue: string = "azureApiCenter"; @@ -22,7 +22,7 @@ export class ApiCenterTreeItem extends AzExtParentTreeItem { constructor(parent: AzExtParentTreeItem, apicenter: ApiCenter) { super(parent); this._apicenter = apicenter; - this.apisTreeItem = new ApisTreeItem(this, apicenter); + this.apisTreeItem = new ApisTreeItem(this, new ApiCenterServiceManagement(apicenter)); this.environmentsTreeItem = new EnvironmentsTreeItem(this, apicenter); } diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index 011239b8..b54aeed1 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,28 +2,25 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { isApiCenterManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; +import { ApiCenterApiManagement, IApiCenterBase } from "../azure/ApiCenter/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiDeploymentsTreeItem } from "./ApiDeploymentsTreeItem"; import { ApiVersionsTreeItem } from "./ApiVersionsTreeItem"; - export class ApiTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiTreeItemChildTypeLabel; public static contextValue: string = "azureApiCenterApi"; public readonly contextValue: string = ApiTreeItem.contextValue; public readonly apiVersionsTreeItem: ApiVersionsTreeItem; public readonly apiDeploymentsTreeItem?: ApiDeploymentsTreeItem; - private readonly _apiCenterApi: GeneralApiCenterApi; + private readonly _apiCenterApi: IApiCenterBase; private readonly _apiCenterName: string; - private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: GeneralApiCenterApi) { + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); - if (isApiCenterManagement(apiCenterApi)) { - this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, apiCenterApi); + if (apiCenterApi instanceof ApiCenterApiManagement) { + this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, (apiCenterApi as ApiCenterApiManagement).getData()); } } @@ -32,15 +29,15 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get id(): string { - return isApiCenterManagement(this._apiCenterApi) ? this._apiCenterApi.id : this._apiCenterApi.name; + return this._apiCenterApi.getId(); } public get label(): string { - return isApiCenterManagement(this._apiCenterApi) ? this._apiCenterApi.properties.title : this._apiCenterApi.title; + return this._apiCenterApi.getLable(); } public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return this._apiCenterApi.getNextLink() !== undefined; } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index 174016a6..8fe1ab84 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -4,9 +4,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft import * as vscode from 'vscode'; import { IDefinitionBase } from "../azure/ApiCenter/ApiCenterDefinition"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { - public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; - public static dataPlaneContextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem" - public readonly contextValue: string = ApiVersionDefinitionTreeItem.contextValue; + public readonly contextValue: string = ""; constructor( parent: AzExtParentTreeItem, public apiCenterName: string, diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index 0015a702..1ff3b27f 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -1,28 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { isApiCenterVersionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApiVersion, GeneralApiCenterApiVersionDefinition } from "../azure/ApiCenter/contracts"; +import { IVersionBase } from "../azure/ApiCenter/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionTreeItem } from "./ApiVersionDefinitionTreeItem"; - export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiVersionDefinitionsTreeItemChildTypeLabel; public static contextValue: string = "azureApiCenterApiVersionDefinitions"; public readonly contextValue: string = ApiVersionDefinitionsTreeItem.contextValue; private readonly _apiCenterName: string; private readonly _apiCenterApiName: string; - private readonly _apiCenterApiVersion: GeneralApiCenterApiVersion; - private _nextLink: string | undefined; + private readonly _apiCenterApiVersion: IVersionBase; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: GeneralApiCenterApiVersion) { + apiCenterApiVersion: IVersionBase) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this._apiCenterName = apiCenterName; @@ -38,43 +32,21 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - - let difinitions = await this.getDefinitions(); - + let definitions = await this._apiCenterApiVersion.getChild(this.parent?.subscription!, this._apiCenterName, this._apiCenterApiName); return await this.createTreeItemsWithErrorHandling( - difinitions, + definitions, 'invalidResource', - difinition => new ApiVersionDefinitionTreeItem( + definition => new ApiVersionDefinitionTreeItem( this, this._apiCenterName, this._apiCenterApiName, - this._apiCenterApiVersion.name, - generateApiDefinition(definition)), - difinition => difinition.name + this._apiCenterApiVersion.getName(), + this._apiCenterApiVersion.generateChild(definition)), + definition => definition.name ); } - private async getDefinitions(): Promise { - if (isApiCenterVersionManagement(this._apiCenterApiVersion)) { - const resourceGroupName = getResourceGroupFromId(this._apiCenterApiVersion.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); - - const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); - this._nextLink = definitions.nextLink; - return definitions.value; - } else { - const server = new ApiCenterDataPlaneService(this.parent?.subscription!); - const res = await server.getApiCenterApiDefinitions(this._apiCenterApiName, this._apiCenterApiVersion.name); - this._nextLink = res.nextLink; - return res.value; - } - } - - // private generateApiDefinition(data: GeneralApiCenterApiVersionDefinition) { - // if (isApiCenterVersionManagement(data)) - // } - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return this._apiCenterApiVersion.getNextLink() !== undefined; } } diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index f9e9484e..05287d8f 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,23 +2,20 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { isApiCenterVersionManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { IVersionBase } from "../azure/ApiCenter/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionsTreeItem } from "./ApiVersionDefinitionsTreeItem"; - export class ApiVersionTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiVersionChildTypeLabel; public static contextValue: string = "azureApiCenterApiVersion"; public readonly contextValue: string = ApiVersionTreeItem.contextValue; - private readonly _apiCenterApiVersion: GeneralApiCenterApiVersion; + private readonly _apiCenterApiVersion: IVersionBase; public readonly apiVersionDefinitionsTreeItem: ApiVersionDefinitionsTreeItem; - private _nextLink: string | undefined; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: GeneralApiCenterApiVersion) { + apiCenterApiVersion: IVersionBase) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this.apiVersionDefinitionsTreeItem = new ApiVersionDefinitionsTreeItem(this, apiCenterName, apiCenterApiName, apiCenterApiVersion); @@ -29,15 +26,15 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get id(): string { - return isApiCenterVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.id : this._apiCenterApiVersion.name; + return this._apiCenterApiVersion.getId(); } public get label(): string { - return isApiCenterVersionManagement(this._apiCenterApiVersion) ? this._apiCenterApiVersion.properties.title : this._apiCenterApiVersion.title; + return this._apiCenterApiVersion.getLable(); } public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return this._apiCenterApiVersion.getNextLink() !== undefined; } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index d8caecd9..d952bcd3 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { isApiCenterManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenterApi, GeneralApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { IApiCenterBase } from "../azure/ApiCenter/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -14,10 +10,9 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { public readonly childTypeLabel: string = UiStrings.ApiVersionsChildTypeLabel; public static contextValue: string = "azureApiCenterApiVersions"; public readonly contextValue: string = ApiVersionsTreeItem.contextValue; - private _nextLink: string | undefined; private readonly _apiCenterName: string; - private readonly _apiCenterApi: GeneralApiCenterApi; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: GeneralApiCenterApi) { + private readonly _apiCenterApi: IApiCenterBase; + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; @@ -32,31 +27,16 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const apis = await this.getApiVersions(); + const apis = await this._apiCenterApi.getChild(this.parent?.subscription!, this._apiCenterName); return await this.createTreeItemsWithErrorHandling( apis, 'invalidResource', - resource => new ApiVersionTreeItem(this, this._apiCenterName, this._apiCenterApi.name, resource), + resource => new ApiVersionTreeItem(this, this._apiCenterName, this._apiCenterApi.getName(), this._apiCenterApi.generateChild(resource)), resource => resource.name ); } - private async getApiVersions(): Promise { - if (isApiCenterManagement(this._apiCenterApi)) { - const resourceGroupName = getResourceGroupFromId(this._apiCenterApi.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this._apiCenterName); - const apis = await apiCenterService.getApiCenterApiVersions(this._apiCenterApi.name); - this._nextLink = apis.nextLink; - return apis.value; - } else { - const server = new ApiCenterDataPlaneService(this.parent?.subscription!); - const res = await server.getAPiCenterApiVersions(this._apiCenterApi.name); - this._nextLink = res.nextLink; - return res.value; - } - } - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return this._apiCenterApi.getNextLink() !== undefined; } } diff --git a/src/tree/ApisTreeItem.ts b/src/tree/ApisTreeItem.ts index 8681973e..5aa4a9d9 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { isApiCenterServiceManagement } from "../azure/ApiCenter/ApiCenterDistinct"; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { GeneralApiCenter, GeneralApiCenterApi } from "../azure/ApiCenter/contracts"; +import { IApiCenterServiceBase } from "../azure/ApiCenter/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiTreeItem } from "./ApiTreeItem"; export class ApisTreeItem extends AzExtParentTreeItem { @@ -15,7 +11,7 @@ export class ApisTreeItem extends AzExtParentTreeItem { public searchContent: string = ""; public contextValue: string = ApisTreeItem.contextValue; private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, public apiCenter: GeneralApiCenter) { + constructor(parent: AzExtParentTreeItem, public apiCenter: IApiCenterServiceBase) { super(parent); } @@ -41,31 +37,15 @@ export class ApisTreeItem extends AzExtParentTreeItem { } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const apis = await this.getApis(); + const apis = await this.apiCenter.getChild(this.parent?.subscription!, this.searchContent); return await this.createTreeItemsWithErrorHandling( apis, 'invalidResource', - resource => new ApiTreeItem(this, this.apiCenter.name, resource), + resource => new ApiTreeItem(this, this.apiCenter.getName(), this.apiCenter.generateChild(resource)), resource => resource.name ); } - private async getApis(): Promise { - if (isApiCenterServiceManagement(this.apiCenter)) { - const resourceGroupName = getResourceGroupFromId(this.apiCenter.id); - const apiCenterService = new ApiCenterService(this.parent?.subscription!, resourceGroupName, this.apiCenter.name); - const apis = await apiCenterService.getApiCenterApis(this.searchContent); - - this._nextLink = apis.nextLink; - return apis.value; - } else { - let server = new ApiCenterDataPlaneService(this.parent?.subscription!); - const res = await server.getApiCenterApis(); - this._nextLink = res.nextLink; - return res.value; - } - } - public hasMoreChildrenImpl(): boolean { return this._nextLink !== undefined; } diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts index 11460acf..005aca0f 100644 --- a/src/tree/DataPlaneAccount.ts +++ b/src/tree/DataPlaneAccount.ts @@ -3,7 +3,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, ISubscriptionContext, TreeItemIconPath, registerEvent } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { DataPlaneApiCenter } from "../azure/ApiCenter/contracts"; +import { ApiCenterServiceDataPlane } from "../azure/ApiCenter/ApiCenterDefinition"; import { AzureDataSessionProvider } from "../azure/azureLogin/authTypes"; import { AzureAuth } from "../azure/azureLogin/azureAuth"; import { AzureDataSessionProviderHelper, generateScopes } from "../azure/azureLogin/dataSessionProvider"; @@ -84,7 +84,7 @@ export class ApiServerItem extends AzExtParentTreeItem { super(parent); this.label = subContext.subscriptionPath.split('.')[0]; this.subscriptionContext = subContext; - this.apisTreeItem = new ApisTreeItem(this, { name: this.label } as DataPlaneApiCenter); + this.apisTreeItem = new ApisTreeItem(this, new ApiCenterServiceDataPlane({ name: this.label })); } public get id(): string { return this.label; diff --git a/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index e1071587..25a0d8a3 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import fetch from 'node-fetch'; import { ProgressLocation, window } from "vscode"; +import { ApiCenterDataPlaneService } from '../../../azure/ApiCenter/ApiCenterDataPlaneAPIs'; +import { ApiCenterVersionDefinitionManagement } from '../../../azure/ApiCenter/ApiCenterDefinition'; import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApiVersionDefinitionImport } from "../../../azure/ApiCenter/contracts"; import { showSavePromptConfigKey } from "../../../constants"; @@ -15,6 +18,15 @@ export class OpenApiEditor extends Editor { } public async getData(treeItem: ApiVersionDefinitionTreeItem): Promise { + if (treeItem.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionManagement) { + return this.getManagementData(treeItem); + } + else { + return this.getDataplaneData(treeItem); + } + } + + private async getManagementData(treeItem: ApiVersionDefinitionTreeItem): Promise { const apiCenterService = new ApiCenterService( treeItem?.subscription!, getResourceGroupFromId(treeItem?.id!), @@ -23,12 +35,28 @@ export class OpenApiEditor extends Editor { const exportedSpec = await apiCenterService.exportSpecification( treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!, - treeItem?.apiCenterApiVersionDefinition.name! + treeItem?.apiCenterApiVersionDefinition.getName() ); return exportedSpec.value; } + private async getDataplaneData(treeItem: ApiVersionDefinitionTreeItem): Promise { + const apiCenterService = new ApiCenterDataPlaneService(treeItem?.subscription!); + const exportedSpec = await apiCenterService.exportSpecification( + treeItem?.apiCenterApiName!, + treeItem?.apiCenterApiVersionName!, + treeItem?.apiCenterApiVersionDefinition.getName() + ); + try { + const rawData = await fetch(exportedSpec.value); + const data = await rawData.json(); + return JSON.stringify(data); + } catch (err) { + throw err; + } + } + public async updateData(treeItem: ApiVersionDefinitionTreeItem, data: string): Promise { const apiCenterService = new ApiCenterService( treeItem?.subscription!, @@ -57,7 +85,7 @@ export class OpenApiEditor extends Editor { await apiCenterService.importSpecification( treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!, - treeItem?.apiCenterApiVersionDefinition.name!, + treeItem?.apiCenterApiVersionDefinition.getName(), importPayload ); } @@ -67,15 +95,15 @@ export class OpenApiEditor extends Editor { }); } public async getFilename(treeItem: ApiVersionDefinitionTreeItem, options: EditorOptions): Promise { - return `${treeItem.apiCenterName}-${treeItem.apiCenterApiName}-${treeItem.apiCenterApiVersionName}--${treeItem.apiCenterApiVersionDefinition.name}-openapi-tempFile${options.fileType}`; + return `${treeItem.apiCenterName}-${treeItem.apiCenterApiName}-${treeItem.apiCenterApiVersionName}--${treeItem.apiCenterApiVersionDefinition.getName()}-openapi-tempFile${options.fileType}`; } public async getDiffFilename(treeItem: ApiVersionDefinitionTreeItem, options: EditorOptions): Promise { - return `${treeItem.apiCenterName}-${treeItem.apiCenterApiName}-${treeItem.apiCenterApiVersionName}--${treeItem.apiCenterApiVersionDefinition.name}-openapi.json${options.fileType}`; + return `${treeItem.apiCenterName}-${treeItem.apiCenterApiName}-${treeItem.apiCenterApiVersionName}--${treeItem.apiCenterApiVersionDefinition.getName()}-openapi.json${options.fileType}`; } public async getSaveConfirmationText(treeItem: ApiVersionDefinitionTreeItem): Promise { - return localize("", `Saving will update the API spec '${treeItem.apiCenterApiVersionDefinition.name}'.`); + return localize("", `Saving will update the API spec '${treeItem.apiCenterApiVersionDefinition.getName()}'.`); } public getSize(context: ApiVersionDefinitionTreeItem): Promise { diff --git a/src/utils/apiSpecificationUtils.ts b/src/utils/apiSpecificationUtils.ts index 6f4df8b9..a6989827 100644 --- a/src/utils/apiSpecificationUtils.ts +++ b/src/utils/apiSpecificationUtils.ts @@ -3,6 +3,7 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ApiSpecificationOptions, openapi } from "../constants"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; @@ -15,7 +16,7 @@ export async function getApiSpecification(title: string, context: IActionContext switch (apiSpecificationOption) { case ApiSpecificationOptions.apiCenter: - const node = await ext.treeDataProvider.showTreeItemPicker(`${ApiVersionDefinitionTreeItem.contextValue}-${openapi}`, context); + const node = await ext.treeDataProvider.showTreeItemPicker(`${ApiCenterVersionDefinitionManagement.contextValue}-${openapi}`, context); return node; case ApiSpecificationOptions.localFile: const fileUri = await vscode.window.showOpenDialog(); diff --git a/tsconfig.json b/tsconfig.json index b75962be..a4e57674 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "commonjs", + "experimentalDecorators": true, "target": "ES2020", "lib": [ "ES2020" From 1476178e0bd8adcdf7388098182f72ca6fc144f5 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 13:49:55 +0800 Subject: [PATCH 14/29] feat: update --- package-lock.json | 178 +++------------------ package.json | 3 +- src/azure/ApiCenter/ApiCenterDefinition.ts | 20 +++ src/commands/exportApi.ts | 39 ++--- src/test/unit/commands/exportApi.test.ts | 5 +- src/tree/Editors/openApi/OpenApiEditor.ts | 40 +---- 6 files changed, 65 insertions(+), 220 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78c51209..53f95788 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", - "node-fetch": "^3.3.2", + "node-fetch": "^2.6.0", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", @@ -46,6 +46,7 @@ "@types/js-yaml": "^4.0.8", "@types/mocha": "^10.0.2", "@types/node": "18.x", + "@types/node-fetch": "^2.6.11", "@types/sinon": "^17.0.3", "@types/swagger2openapi": "^7.0.4", "@types/unzipper": "^0.10.9", @@ -510,25 +511,6 @@ "node": ">= 0.12" } }, - "node_modules/@azure/ms-rest-js/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@azure/ms-rest-js/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -1577,6 +1559,16 @@ "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/qs": { "version": "6.9.9", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", @@ -3892,25 +3884,6 @@ "node-fetch": "^2.6.12" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3930,14 +3903,6 @@ "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==", "dev": true }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "engines": { - "node": ">= 12" - } - }, "node_modules/date-utils": { "version": "1.2.21", "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz", @@ -4749,28 +4714,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -4933,17 +4876,6 @@ "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==" }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7507,39 +7439,23 @@ "node": "^16 || ^18 || >= 20" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "4.x || >=6.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-fetch-h2": { @@ -9700,25 +9616,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/swagger2openapi/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/swagger2openapi/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -10330,14 +10227,6 @@ "node": ">=10.13.0" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10705,25 +10594,6 @@ "node": ">=6" } }, - "node_modules/widdershins/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/widdershins/node_modules/oas-validator": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-4.0.8.tgz", diff --git a/package.json b/package.json index 34f0ecec..a02d364e 100644 --- a/package.json +++ b/package.json @@ -489,6 +489,7 @@ "@types/js-yaml": "^4.0.8", "@types/mocha": "^10.0.2", "@types/node": "18.x", + "@types/node-fetch": "^2.6.11", "@types/sinon": "^17.0.3", "@types/swagger2openapi": "^7.0.4", "@types/unzipper": "^0.10.9", @@ -538,7 +539,7 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", - "node-fetch": "^3.3.2", + "node-fetch": "^2.6.0", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", diff --git a/src/azure/ApiCenter/ApiCenterDefinition.ts b/src/azure/ApiCenter/ApiCenterDefinition.ts index e759e3c8..ac914a21 100644 --- a/src/azure/ApiCenter/ApiCenterDefinition.ts +++ b/src/azure/ApiCenter/ApiCenterDefinition.ts @@ -9,6 +9,7 @@ import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, + ApiCenterApiVersionDefinitionExport, DataPlaneApiCenter, DataPlaneApiCenterApi, DataPlaneApiCenterApiVersion, @@ -209,10 +210,23 @@ export type IDefinitionBase = { getId: () => string, getContext: () => string, getName: () => string; + getDefinitions: (context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string) => Promise; } export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { constructor(public data: ApiCenterApiVersionDefinition) { } + async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService( + context, + resourceGroupName, + apiServiceName); + const exportedSpec = await apiCenterService.exportSpecification( + apiName, + apiVersionName, + this.data.name); + return exportedSpec; + } static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; getName(): string { return this.data.name; @@ -230,6 +244,12 @@ export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } + async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { + let server = new ApiCenterDataPlaneService(context); + let results = await server.exportSpecification(apiName, + apiVersionName, this.data.name); + return results; + } static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; getName(): string { return this.data.name; diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index ea6dc339..be52b772 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -1,14 +1,11 @@ // 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 fetch from 'node-fetch'; import * as path from "path"; import * as vscode from "vscode"; -import { ApiCenterDataPlaneService } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; @@ -21,22 +18,8 @@ export namespace ExportAPI { node = await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); } - if (node?.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionManagement) { - const apiCenterService = new ApiCenterService( - node?.subscription!, - getResourceGroupFromId(node?.id!), - node?.apiCenterName!); - const exportedSpec = await apiCenterService.exportSpecification( - node?.apiCenterApiName!, - node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.getName()!); - await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); - } else if (node?.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionDataPlane) { - let server = new ApiCenterDataPlaneService(node.parent?.subscription!); - let results = await server.exportSpecification(node?.apiCenterApiName!, - node?.apiCenterApiVersionName!, node?.apiCenterApiVersionDefinition.getName()); - await writeToTempFile(node!, results.format, results.value); - } + const exportedSpec = await node?.apiCenterApiVersionDefinition.getDefinitions(node?.subscription!, node?.apiCenterName!, node?.apiCenterApiName!, node?.apiCenterApiVersionName!); + await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); } function getFolderName(treeItem: ApiVersionDefinitionTreeItem): string { @@ -50,11 +33,19 @@ export namespace ExportAPI { 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. - const res = await fetch(specValue); + } else if (specFormat == ApiSpecExportResultFormat.link) { + await ExportAPI.showTempFile(node, await ExportAPI.fetchDataFromLink(specValue)); + } + } + + export async function fetchDataFromLink(link: string): Promise { + try { + const res = await fetch(link); const rawData = await res.json(); - await ExportAPI.showTempFile(node, JSON.stringify(rawData)); + return JSON.stringify(rawData); + } + catch (err) { + throw err; } } diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index 0df01f1f..e9cfc94b 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -67,10 +67,11 @@ describe("export API test cases", () => { sandbox.restore(); }); it('export API happy path with link type', async () => { - const spyShowTempFile = sandbox.spy(ExportAPI, "showTempFile"); + sandbox.stub(ExportAPI, "fetchDataFromLink").resolves('fakeTest'); + let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); sandbox.stub(ApiCenterService.prototype, "exportSpecification").resolves({ format: "link", value: "fakeValue" }); await ExportAPI.exportApi({} as IActionContext, node); - sandbox.assert.notCalled(spyShowTempFile); + sandbox.assert.calledOnce(stubShowTempFile); }); it('export API happy path with inline type', async () => { let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); diff --git a/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index 25a0d8a3..6d1d321d 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; -import fetch from 'node-fetch'; import { ProgressLocation, window } from "vscode"; -import { ApiCenterDataPlaneService } from '../../../azure/ApiCenter/ApiCenterDataPlaneAPIs'; -import { ApiCenterVersionDefinitionManagement } from '../../../azure/ApiCenter/ApiCenterDefinition'; import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApiVersionDefinitionImport } from "../../../azure/ApiCenter/contracts"; import { showSavePromptConfigKey } from "../../../constants"; @@ -18,45 +15,10 @@ export class OpenApiEditor extends Editor { } public async getData(treeItem: ApiVersionDefinitionTreeItem): Promise { - if (treeItem.apiCenterApiVersionDefinition instanceof ApiCenterVersionDefinitionManagement) { - return this.getManagementData(treeItem); - } - else { - return this.getDataplaneData(treeItem); - } - } - - private async getManagementData(treeItem: ApiVersionDefinitionTreeItem): Promise { - const apiCenterService = new ApiCenterService( - treeItem?.subscription!, - getResourceGroupFromId(treeItem?.id!), - treeItem?.apiCenterName!); - - const exportedSpec = await apiCenterService.exportSpecification( - treeItem?.apiCenterApiName!, - treeItem?.apiCenterApiVersionName!, - treeItem?.apiCenterApiVersionDefinition.getName() - ); - + const exportedSpec = await treeItem.apiCenterApiVersionDefinition.getDefinitions(treeItem?.subscription!, treeItem?.apiCenterName!, treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!); return exportedSpec.value; } - private async getDataplaneData(treeItem: ApiVersionDefinitionTreeItem): Promise { - const apiCenterService = new ApiCenterDataPlaneService(treeItem?.subscription!); - const exportedSpec = await apiCenterService.exportSpecification( - treeItem?.apiCenterApiName!, - treeItem?.apiCenterApiVersionName!, - treeItem?.apiCenterApiVersionDefinition.getName() - ); - try { - const rawData = await fetch(exportedSpec.value); - const data = await rawData.json(); - return JSON.stringify(data); - } catch (err) { - throw err; - } - } - public async updateData(treeItem: ApiVersionDefinitionTreeItem, data: string): Promise { const apiCenterService = new ApiCenterService( treeItem?.subscription!, From 0e90e97badcc0c21b6cc727fab455574a013a390 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 14:13:22 +0800 Subject: [PATCH 15/29] feat: update --- src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts | 2 +- src/azure/ApiCenter/ApiCenterDefinition.ts | 32 +++++++++---------- src/azure/ApiCenter/ApiCenterRestAPIs.ts | 4 +-- src/azure/ApiCenter/contracts.ts | 8 ++--- src/azure/azureLogin/authTypes.ts | 2 +- src/azure/azureLogin/azureAuth.ts | 2 +- src/azure/azureLogin/dataSessionProvider.ts | 4 +-- src/commands/exportApi.ts | 2 +- src/commands/handleUri.ts | 2 +- src/commands/signInToDataPlane.ts | 2 +- src/test/unit/commands/exportApi.test.ts | 9 +++--- src/tree/DataPlaneAccount.ts | 8 ++--- 12 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts index 6f06b0b0..078dc39f 100644 --- a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts @@ -12,7 +12,7 @@ export interface DataPlaneAccount { export class ApiCenterDataPlaneService { private susbcriptionContext: ISubscriptionContext; constructor(susbcriptionContext: ISubscriptionContext) { - this.susbcriptionContext = susbcriptionContext + this.susbcriptionContext = susbcriptionContext; }; public async getApiCenterApis(): Promise<{ value: DataPlaneApiCenterApi[]; nextLink: string }> { const client = new ServiceClient(this.susbcriptionContext.credentials); diff --git a/src/azure/ApiCenter/ApiCenterDefinition.ts b/src/azure/ApiCenter/ApiCenterDefinition.ts index ac914a21..8ded22ba 100644 --- a/src/azure/ApiCenter/ApiCenterDefinition.ts +++ b/src/azure/ApiCenter/ApiCenterDefinition.ts @@ -26,7 +26,7 @@ export type IApiCenterServiceBase = { getId: () => string, getChild: (context: ISubscriptionContext, content: string) => Promise; generateChild: (data: GeneralApiCenterApi) => IApiCenterBase; -} +}; export class ApiCenterServiceManagement implements IApiCenterServiceBase { constructor(public data: ApiCenter) { } @@ -34,7 +34,7 @@ export class ApiCenterServiceManagement implements IApiCenterServiceBase { return this.data.name; } getId(): string { - return this.data.id + return this.data.id; } async getChild(context: ISubscriptionContext, content: string): Promise { const resourceGroupName = getResourceGroupFromId(this.data.id); @@ -50,7 +50,7 @@ export class ApiCenterServiceManagement implements IApiCenterServiceBase { getNextLink(): string | undefined { return this._nextLink; } -} +}; export class ApiCenterServiceDataPlane implements IApiCenterServiceBase { constructor(public data: DataPlaneApiCenter) { } @@ -73,7 +73,7 @@ export class ApiCenterServiceDataPlane implements IApiCenterServiceBase { generateChild(data: GeneralApiCenterApi): IApiCenterBase { return new ApiCenterApiDataPlane(data as DataPlaneApiCenterApi); } -} +}; export type IApiCenterBase = { _nextLink: string | undefined; @@ -83,7 +83,7 @@ export type IApiCenterBase = { getLable: () => string; getChild: (context: ISubscriptionContext, apiName: string) => Promise; generateChild: (data: GeneralApiCenterApiVersion) => IVersionBase; -} +}; export class ApiCenterApiManagement implements IApiCenterBase { constructor(public data: ApiCenterApi) { } @@ -113,7 +113,7 @@ export class ApiCenterApiManagement implements IApiCenterBase { generateChild(data: GeneralApiCenterApiVersion): IVersionBase { return new ApiCenterVersionManagement(data as ApiCenterApiVersion); } -} +}; export class ApiCenterApiDataPlane implements IApiCenterBase { constructor(public data: DataPlaneApiCenterApi) { } @@ -139,7 +139,7 @@ export class ApiCenterApiDataPlane implements IApiCenterBase { generateChild(data: GeneralApiCenterApiVersion): IVersionBase { return new ApiCenterVersionDataplane(data as DataPlaneApiCenterApiVersion); } -} +}; export type IVersionBase = { _nextLink: string | undefined; @@ -149,7 +149,7 @@ export type IVersionBase = { getName: () => string, getChild: (context: ISubscriptionContext, apiName: string, apiServiceName: string) => Promise; generateChild: (data: GeneralApiCenterApiVersionDefinition) => IDefinitionBase; -} +}; export class ApiCenterVersionManagement implements IVersionBase { constructor(public data: ApiCenterApiVersion) { } @@ -177,7 +177,7 @@ export class ApiCenterVersionManagement implements IVersionBase { getId() { return this.data.id; } -} +}; export class ApiCenterVersionDataplane implements IVersionBase { constructor(public data: DataPlaneApiCenterApiVersion) { } @@ -203,7 +203,7 @@ export class ApiCenterVersionDataplane implements IVersionBase { getId() { return this.data.name; } -} +}; export type IDefinitionBase = { getLabel: () => string, @@ -211,7 +211,7 @@ export type IDefinitionBase = { getContext: () => string, getName: () => string; getDefinitions: (context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string) => Promise; -} +}; export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { constructor(public data: ApiCenterApiVersionDefinition) { } @@ -230,7 +230,7 @@ export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; getName(): string { return this.data.name; - } + }; getContext() { return ApiCenterVersionDefinitionManagement.contextValue + "-" + this.data.properties.specification.name.toLowerCase(); }; @@ -240,7 +240,7 @@ export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { getId() { return this.data.id; }; -} +}; export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } @@ -249,11 +249,11 @@ export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { let results = await server.exportSpecification(apiName, apiVersionName, this.data.name); return results; - } + }; static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; getName(): string { return this.data.name; - } + }; getContext() { return ApiCenterVersionDefinitionDataPlane.contextValue + "-" + this.data.specification.name.toLowerCase(); }; @@ -263,4 +263,4 @@ export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { getId() { return this.data.name; }; -} +}; diff --git a/src/azure/ApiCenter/ApiCenterRestAPIs.ts b/src/azure/ApiCenter/ApiCenterRestAPIs.ts index 8fd40d1b..07a5a0a3 100644 --- a/src/azure/ApiCenter/ApiCenterRestAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterRestAPIs.ts @@ -30,5 +30,5 @@ export const APICenterDataPlaneRestAPIs = { GetApiVersion: (domain: string, apiName: string, versionName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${versionName}`, ListApiVersions: (domain: string, apiName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions`, ListApiDefinitions: (domain: string, apiName: string, apiVersion: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions`, - ExportApiDefinitions: (domain: string, apiName: string, apiVersion: string, definitionName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification` -} + ExportApiDefinitions: (domain: string, apiName: string, apiVersion: string, definitionName: string) => `https://${domain}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definitionName}:exportSpecification`, +}; diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index 4fa8c3d6..c6441bfb 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -19,7 +19,7 @@ export type ApiCenter = { export type DataPlaneApiCenter = { name: string; -} +}; export type GeneralApiCenterApi = ApiCenterApi | DataPlaneApiCenterApi; @@ -43,7 +43,7 @@ export type DataPlaneApiCenterApi = { externalDocumentation: []; contacts: []; customProperties: {}; -} +}; export type ApiCenterEnvironment = { id: string; @@ -73,7 +73,7 @@ export type DataPlaneApiCenterApiVersion = { name: string; title: string; lifecycleStage: string; -} +}; export type ApiCenterApiDeployment = { id: string; @@ -112,7 +112,7 @@ export type DataPlaneApiCenterApiVersionDefinition = { specification: { name: string; } -} +}; export type ApiCenterApiVersionDefinitionImport = { format: string; diff --git a/src/azure/azureLogin/authTypes.ts b/src/azure/azureLogin/authTypes.ts index 535cbd4c..ab42b3d3 100644 --- a/src/azure/azureLogin/authTypes.ts +++ b/src/azure/azureLogin/authTypes.ts @@ -56,7 +56,7 @@ export type AzureDataSessionProvider = { signInStatusChangeEvent: Event; getAuthSession(scopes?: string[]): Promise>; dispose(): void; -} +}; export type ReadyAzureSessionProvider = AzureSessionProvider & { signInStatus: SignInStatus.SignedIn; diff --git a/src/azure/azureLogin/azureAuth.ts b/src/azure/azureLogin/azureAuth.ts index b4c5855b..c478c3e7 100644 --- a/src/azure/azureLogin/azureAuth.ts +++ b/src/azure/azureLogin/azureAuth.ts @@ -36,7 +36,7 @@ export namespace AzureAuth { } return { token: session.result.accessToken, expiresOnTimestamp: 0 }; } - } + }; } export function getDefaultScope(endpointUrl: string): string { diff --git a/src/azure/azureLogin/dataSessionProvider.ts b/src/azure/azureLogin/dataSessionProvider.ts index 16f17061..1f276a0e 100644 --- a/src/azure/azureLogin/dataSessionProvider.ts +++ b/src/azure/azureLogin/dataSessionProvider.ts @@ -25,7 +25,7 @@ export function generateScopes(clientId: string, tenantId: string): string[] { `VSCODE_TENANT:${tenantId}`, // Replace with the tenant ID or common if multi-tenant "offline_access", // Required for the refresh token. "https://azure-apicenter.net/user_impersonation" - ] + ]; } export namespace AzureDataSessionProviderHelper { @@ -67,7 +67,7 @@ export namespace AzureDataSessionProviderHelper { }); } private async updateSignInStatus(_scopes: string[], authScenario: AuthScenario): Promise { - if (_scopes.length != 0) { + if (_scopes.length !== 0) { await this.getArmSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, _scopes, authScenario); } this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index be52b772..b4f51f3b 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -33,7 +33,7 @@ export namespace ExportAPI { async function writeToTempFile(node: ApiVersionDefinitionTreeItem, specFormat: string, specValue: string) { if (specFormat === ApiSpecExportResultFormat.inline) { await ExportAPI.showTempFile(node, specValue); - } else if (specFormat == ApiSpecExportResultFormat.link) { + } else if (specFormat === ApiSpecExportResultFormat.link) { await ExportAPI.showTempFile(node, await ExportAPI.fetchDataFromLink(specValue)); } } diff --git a/src/commands/handleUri.ts b/src/commands/handleUri.ts index d6a532d4..1cb65288 100644 --- a/src/commands/handleUri.ts +++ b/src/commands/handleUri.ts @@ -8,5 +8,5 @@ export async function handleUri(uri: vscode.Uri) { let clientId = queryParams.get('clientId') as string; let runtimeUrl = queryParams.get('runtimeUrl') as string; setAccountToExt(runtimeUrl, clientId, tenantId); - vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh') + vscode.commands.executeCommand('azure-api-center.apiCenterWorkspace.refresh'); }; diff --git a/src/commands/signInToDataPlane.ts b/src/commands/signInToDataPlane.ts index b9295ce6..9b68c75c 100644 --- a/src/commands/signInToDataPlane.ts +++ b/src/commands/signInToDataPlane.ts @@ -7,7 +7,7 @@ import { ApiServerItem } from "../tree/DataPlaneAccount"; export async function SignInToDataPlane(context: IActionContext, node: ApisTreeItem) { if (!(node instanceof ApisTreeItem)) { let parentNode = (node as ApisTreeItem).parent as ApiServerItem; - let scopes = generateScopes(parentNode.subscription!.userId!, parentNode.subscription!.tenantId!) + let scopes = generateScopes(parentNode.subscription!.userId!, parentNode.subscription!.tenantId!); await AzureDataSessionProviderHelper.getSessionProvider().signIn(scopes); } } diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index e9cfc94b..ca35dde5 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -3,7 +3,6 @@ import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; import * as sinon from "sinon"; import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenter/ApiCenterDefinition"; -import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApiVersionDefinition } from "../../../azure/ApiCenter/contracts"; import { ExportAPI } from "../../../commands/exportApi"; import { TelemetryClient } from "../../../common/telemetryClient"; @@ -33,7 +32,7 @@ const data: ApiCenterApiVersionDefinition = { }, // tslint:disable-next-line:no-reserved-keywords type: "fakeType", -} +}; class RootTreeItem extends ParentTreeItemBase { public label: string = 'root'; @@ -67,15 +66,15 @@ describe("export API test cases", () => { sandbox.restore(); }); it('export API happy path with link type', async () => { - sandbox.stub(ExportAPI, "fetchDataFromLink").resolves('fakeTest'); let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); - sandbox.stub(ApiCenterService.prototype, "exportSpecification").resolves({ format: "link", value: "fakeValue" }); + sandbox.stub(node.apiCenterApiVersionDefinition, "getDefinitions").resolves({ format: "link", value: "fakeValue" }); + sandbox.stub(ExportAPI, "fetchDataFromLink").resolves(); await ExportAPI.exportApi({} as IActionContext, node); sandbox.assert.calledOnce(stubShowTempFile); }); it('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" }); + sandbox.stub(node.apiCenterApiVersionDefinition, "getDefinitions").resolves({ format: "inline", value: "fakeValue" }); await ExportAPI.exportApi({} as IActionContext, node); sandbox.assert.calledOnce(stubShowTempFile); }); diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts index 005aca0f..93233600 100644 --- a/src/tree/DataPlaneAccount.ts +++ b/src/tree/DataPlaneAccount.ts @@ -25,7 +25,7 @@ export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { const onStatusChange = this.sessionProvider.signInStatusChangeEvent; registerEvent("DataPlanAccountManagerTreeItem.onSignInStatusChange", onStatusChange, (context) => { - this.refresh(context) + this.refresh(context); }); } public dispose(): void { } @@ -36,7 +36,7 @@ export class DataPlanAccountManagerTreeItem extends AzExtParentTreeItem { 'inValidResource', async account => new ApiServerItem(this, getSubscriptionContext(account)), account => account.domain.split('0')[0] - ) + ); } public hasMoreChildrenImpl(): boolean { @@ -70,9 +70,9 @@ export class ApiServerItem extends AzExtParentTreeItem { iconPath: new vscode.ThemeIcon("sign-in"), includeInTreeItemPicker: true, }) - ] + ]; } - return [this.apisTreeItem] + return [this.apisTreeItem]; } public hasMoreChildrenImpl(): boolean { return false; From 42e823c09a51297b6de34745f306d259636c30c2 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 16:03:37 +0800 Subject: [PATCH 16/29] feat: separate defines --- src/azure/ApiCenter/ApiCenterDefinition.ts | 266 ------------------ src/azure/ApiCenterDefines/ApiCenterApi.ts | 121 ++++++++ .../ApiCenterDefines/ApiCenterDefinition.ts | 123 ++++++++ .../ApiCenterDefines/ApiCenterVersion.ts | 101 +++++++ src/commands/editOpenApi.ts | 2 +- src/commands/exportApi.ts | 2 +- .../registerStepByStep.ts | 2 +- src/test/unit/commands/exportApi.test.ts | 2 +- src/tree/ApiCenterTreeItem.ts | 4 +- src/tree/ApiTreeItem.ts | 8 +- src/tree/ApiVersionDefinitionTreeItem.ts | 2 +- src/tree/ApiVersionDefinitionsTreeItem.ts | 6 +- src/tree/ApiVersionTreeItem.ts | 6 +- src/tree/ApiVersionsTreeItem.ts | 6 +- src/tree/ApisTreeItem.ts | 4 +- src/tree/DataPlaneAccount.ts | 4 +- src/utils/apiSpecificationUtils.ts | 2 +- 17 files changed, 370 insertions(+), 291 deletions(-) delete mode 100644 src/azure/ApiCenter/ApiCenterDefinition.ts create mode 100644 src/azure/ApiCenterDefines/ApiCenterApi.ts create mode 100644 src/azure/ApiCenterDefines/ApiCenterDefinition.ts create mode 100644 src/azure/ApiCenterDefines/ApiCenterVersion.ts diff --git a/src/azure/ApiCenter/ApiCenterDefinition.ts b/src/azure/ApiCenter/ApiCenterDefinition.ts deleted file mode 100644 index 8ded22ba..00000000 --- a/src/azure/ApiCenter/ApiCenterDefinition.ts +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; -import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; -import { ApiCenterDataPlaneService } from "../ApiCenter/ApiCenterDataPlaneAPIs"; -import { ApiCenterService } from "../ApiCenter/ApiCenterService"; -import { - ApiCenter, - ApiCenterApi, - ApiCenterApiVersion, - ApiCenterApiVersionDefinition, - ApiCenterApiVersionDefinitionExport, - DataPlaneApiCenter, - DataPlaneApiCenterApi, - DataPlaneApiCenterApiVersion, - DataPlaneApiCenterApiVersionDefinition, - GeneralApiCenterApi, - GeneralApiCenterApiVersion, - GeneralApiCenterApiVersionDefinition -} from "./contracts"; - -export type IApiCenterServiceBase = { - _nextLink: string | undefined; - getNextLink: () => string | undefined; - getName: () => string, - getId: () => string, - getChild: (context: ISubscriptionContext, content: string) => Promise; - generateChild: (data: GeneralApiCenterApi) => IApiCenterBase; -}; - -export class ApiCenterServiceManagement implements IApiCenterServiceBase { - constructor(public data: ApiCenter) { } - getName(): string { - return this.data.name; - } - getId(): string { - return this.data.id; - } - async getChild(context: ISubscriptionContext, content: string): Promise { - const resourceGroupName = getResourceGroupFromId(this.data.id); - const apiCenterService = new ApiCenterService(context, resourceGroupName, this.data.name); - const apis = await apiCenterService.getApiCenterApis(content); - this._nextLink = apis.nextLink; - return apis.value; - } - generateChild(data: GeneralApiCenterApi): IApiCenterBase { - return new ApiCenterApiManagement(data as ApiCenterApi); - } - _nextLink: string | undefined; - getNextLink(): string | undefined { - return this._nextLink; - } -}; - -export class ApiCenterServiceDataPlane implements IApiCenterServiceBase { - constructor(public data: DataPlaneApiCenter) { } - _nextLink: string | undefined; - getNextLink(): string | undefined { - return this._nextLink; - } - getName(): string { - return this.data.name; - } - getId(): string { - return this.data.name; - } - async getChild(context: ISubscriptionContext, content: string): Promise { - let server = new ApiCenterDataPlaneService(context); - const res = await server.getApiCenterApis(); - this._nextLink = res.nextLink; - return res.value; - } - generateChild(data: GeneralApiCenterApi): IApiCenterBase { - return new ApiCenterApiDataPlane(data as DataPlaneApiCenterApi); - } -}; - -export type IApiCenterBase = { - _nextLink: string | undefined; - getNextLink: () => string | undefined; - getName: () => string, - getId: () => string, - getLable: () => string; - getChild: (context: ISubscriptionContext, apiName: string) => Promise; - generateChild: (data: GeneralApiCenterApiVersion) => IVersionBase; -}; - -export class ApiCenterApiManagement implements IApiCenterBase { - constructor(public data: ApiCenterApi) { } - getData(): ApiCenterApi { - return this.data; - } - getName(): string { - return this.data.name; - } - _nextLink: string | undefined; - getNextLink(): string | undefined { - return this._nextLink; - } - getId(): string { - return this.data.id; - } - getLable(): string { - return this.data.properties.title; - } - async getChild(context: ISubscriptionContext, apiName: string): Promise { - const resourceGroupName = getResourceGroupFromId(this.data.id); - const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); - const apis = await apiCenterService.getApiCenterApiVersions(this.data.name); - this._nextLink = apis.nextLink; - return apis.value; - } - generateChild(data: GeneralApiCenterApiVersion): IVersionBase { - return new ApiCenterVersionManagement(data as ApiCenterApiVersion); - } -}; - -export class ApiCenterApiDataPlane implements IApiCenterBase { - constructor(public data: DataPlaneApiCenterApi) { } - getLable(): string { - return this.data.name; - } - getId(): string { - return this.data.name; - } - _nextLink: string | undefined; - getNextLink(): string | undefined { - return this._nextLink; - } - getName(): string { - return this.data.name; - } - async getChild(context: ISubscriptionContext, apiName: string): Promise { - const server = new ApiCenterDataPlaneService(context); - const res = await server.getAPiCenterApiVersions(this.data.name); - this._nextLink = res.nextLink; - return res.value; - } - generateChild(data: GeneralApiCenterApiVersion): IVersionBase { - return new ApiCenterVersionDataplane(data as DataPlaneApiCenterApiVersion); - } -}; - -export type IVersionBase = { - _nextLink: string | undefined; - getNextLink: () => string | undefined; - getLable: () => string, - getId: () => string, - getName: () => string, - getChild: (context: ISubscriptionContext, apiName: string, apiServiceName: string) => Promise; - generateChild: (data: GeneralApiCenterApiVersionDefinition) => IDefinitionBase; -}; - -export class ApiCenterVersionManagement implements IVersionBase { - constructor(public data: ApiCenterApiVersion) { } - getNextLink(): string | undefined { - return this._nextLink; - } - _nextLink: string | undefined; - getName(): string { - return this.data.name; - } - generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { - return new ApiCenterVersionDefinitionManagement(data as ApiCenterApiVersionDefinition); - } - async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { - const resourceGroupName = getResourceGroupFromId(this.data.id); - const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); - - const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(apiServiceName, this.data.name); - this._nextLink = definitions.nextLink; - return definitions.value; - }; - getLable() { - return this.data.properties.title; - } - getId() { - return this.data.id; - } -}; - -export class ApiCenterVersionDataplane implements IVersionBase { - constructor(public data: DataPlaneApiCenterApiVersion) { } - getNextLink(): string | undefined { - return this._nextLink; - } - _nextLink: string | undefined; - getName(): string { - return this.data.name; - } - generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { - return new ApiCenterVersionDefinitionDataPlane(data as DataPlaneApiCenterApiVersionDefinition); - } - async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { - const server = new ApiCenterDataPlaneService(context); - const res = await server.getApiCenterApiDefinitions(apiServiceName, this.data.name); - this._nextLink = res.nextLink; - return res.value; - } - getLable() { - return this.data.name; - } - getId() { - return this.data.name; - } -}; - -export type IDefinitionBase = { - getLabel: () => string, - getId: () => string, - getContext: () => string, - getName: () => string; - getDefinitions: (context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string) => Promise; -}; - -export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { - constructor(public data: ApiCenterApiVersionDefinition) { } - async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { - const resourceGroupName = getResourceGroupFromId(this.data.id); - const apiCenterService = new ApiCenterService( - context, - resourceGroupName, - apiServiceName); - const exportedSpec = await apiCenterService.exportSpecification( - apiName, - apiVersionName, - this.data.name); - return exportedSpec; - } - static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; - getName(): string { - return this.data.name; - }; - getContext() { - return ApiCenterVersionDefinitionManagement.contextValue + "-" + this.data.properties.specification.name.toLowerCase(); - }; - getLabel() { - return this.data.properties.title; - }; - getId() { - return this.data.id; - }; -}; - -export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { - constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } - async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { - let server = new ApiCenterDataPlaneService(context); - let results = await server.exportSpecification(apiName, - apiVersionName, this.data.name); - return results; - }; - static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; - getName(): string { - return this.data.name; - }; - getContext() { - return ApiCenterVersionDefinitionDataPlane.contextValue + "-" + this.data.specification.name.toLowerCase(); - }; - getLabel() { - return this.data.name; - }; - getId() { - return this.data.name; - }; -}; diff --git a/src/azure/ApiCenterDefines/ApiCenterApi.ts b/src/azure/ApiCenterDefines/ApiCenterApi.ts new file mode 100644 index 00000000..51598db9 --- /dev/null +++ b/src/azure/ApiCenterDefines/ApiCenterApi.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { ApiCenterDataPlaneService } from "../ApiCenter/ApiCenterDataPlaneAPIs"; +import { ApiCenterService } from "../ApiCenter/ApiCenterService"; +import { + ApiCenter, + ApiCenterApi, + DataPlaneApiCenter, + DataPlaneApiCenterApi, + GeneralApiCenterApi +} from "../ApiCenter/contracts"; +import { ApiCenterVersionsManagement, ApiCneterVersionsDataplane, IVersionsBase } from "./ApiCenterVersion"; +export type IApiCenterApisBase = { + getName: () => string; + getId: () => string; + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getChild: (context: ISubscriptionContext, content: string) => Promise; + generateChild: (data: GeneralApiCenterApi) => IApiCenterApiBase; +} + +export class ApiCenterApisManagement implements IApiCenterApisBase { + constructor(public data: ApiCenter) { } + getId(): string { + return this.data.id; + } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + async getChild(context: ISubscriptionContext, content: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, this.data.name); + const apis = await apiCenterService.getApiCenterApis(content); + this._nextLink = apis.nextLink; + return apis.value; + } + generateChild(data: GeneralApiCenterApi): IApiCenterApiBase { + return new ApiCenterApiManagement(data as ApiCenterApi); + } +} + +export class ApiCenterApisDataplane implements IApiCenterApisBase { + constructor(private data: DataPlaneApiCenter) { } + getId(): string { + return this.data.name; + } + getName(): string { + return this.data.name + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + async getChild(context: ISubscriptionContext, content: string): Promise { + let server = new ApiCenterDataPlaneService(context); + const res = await server.getApiCenterApis(); + this._nextLink = res.nextLink; + return res.value; + } + generateChild(data: GeneralApiCenterApi): IApiCenterApiBase { + return new ApiCenterApiDataPlane(data as DataPlaneApiCenterApi); + } +} + +export type IApiCenterApiBase = { + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getName: () => string, + getId: () => string, + getLable: () => string; + generateChild: () => IVersionsBase; +}; + +export class ApiCenterApiManagement implements IApiCenterApiBase { + constructor(public data: ApiCenterApi) { } + getData(): ApiCenterApi { + return this.data; + } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + getId(): string { + return this.data.id; + } + getLable(): string { + return this.data.properties.title; + } + generateChild(): IVersionsBase { + return new ApiCenterVersionsManagement(this.data); + } +}; + +export class ApiCenterApiDataPlane implements IApiCenterApiBase { + constructor(public data: DataPlaneApiCenterApi) { } + getLable(): string { + return this.data.name; + } + getId(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + getName(): string { + return this.data.name; + } + generateChild(): IVersionsBase { + return new ApiCneterVersionsDataplane(this.data); + } +}; diff --git a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts new file mode 100644 index 00000000..6c87fa8a --- /dev/null +++ b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { ApiCenterDataPlaneService } from "../ApiCenter/ApiCenterDataPlaneAPIs"; +import { ApiCenterService } from "../ApiCenter/ApiCenterService"; +import { + ApiCenterApiVersion, + ApiCenterApiVersionDefinition, + ApiCenterApiVersionDefinitionExport, + DataPlaneApiCenterApiVersion, + DataPlaneApiCenterApiVersionDefinition, + GeneralApiCenterApiVersionDefinition +} from "../ApiCenter/contracts"; +export type IDefinitionsBase = { + getName: () => string; + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getChild: (context: ISubscriptionContext, apiName: string, apiServiceName: string) => Promise; + generateChild: (data: GeneralApiCenterApiVersionDefinition) => IDefinitionBase; +} + +export class ApiCenterVersionDefinitionsManagement implements IDefinitionsBase { + constructor(public data: ApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { + return new ApiCenterVersionDefinitionManagement(data as ApiCenterApiVersionDefinition); + } + async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); + + const definitions = await apiCenterService.getApiCenterApiVersionDefinitions(apiServiceName, this.data.name); + this._nextLink = definitions.nextLink; + return definitions.value; + }; +} + +export class ApiCenterVersionDefinitionsDataplane implements IDefinitionsBase { + constructor(public data: DataPlaneApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + async getChild(context: ISubscriptionContext, apiName: string, apiServiceName: string): Promise { + const server = new ApiCenterDataPlaneService(context); + const res = await server.getApiCenterApiDefinitions(apiServiceName, this.data.name); + this._nextLink = res.nextLink; + return res.value; + } + generateChild(data: GeneralApiCenterApiVersionDefinition): IDefinitionBase { + return new ApiCenterVersionDefinitionDataPlane(data as DataPlaneApiCenterApiVersionDefinition); + } +} + +export type IDefinitionBase = { + getLabel: () => string, + getId: () => string, + getContext: () => string, + getName: () => string; + getDefinitions: (context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string) => Promise; +}; + +export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { + constructor(public data: ApiCenterApiVersionDefinition) { } + async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService( + context, + resourceGroupName, + apiServiceName); + const exportedSpec = await apiCenterService.exportSpecification( + apiName, + apiVersionName, + this.data.name); + return exportedSpec; + } + static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; + getName(): string { + return this.data.name; + }; + getContext() { + return ApiCenterVersionDefinitionManagement.contextValue + "-" + this.data.properties.specification.name.toLowerCase(); + }; + getLabel() { + return this.data.properties.title; + }; + getId() { + return this.data.id; + }; +}; + +export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { + constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } + async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { + let server = new ApiCenterDataPlaneService(context); + let results = await server.exportSpecification(apiName, + apiVersionName, this.data.name); + return results; + }; + static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; + getName(): string { + return this.data.name; + }; + getContext() { + return ApiCenterVersionDefinitionDataPlane.contextValue + "-" + this.data.specification.name.toLowerCase(); + }; + getLabel() { + return this.data.name; + }; + getId() { + return this.data.name; + }; +}; diff --git a/src/azure/ApiCenterDefines/ApiCenterVersion.ts b/src/azure/ApiCenterDefines/ApiCenterVersion.ts new file mode 100644 index 00000000..75899dbf --- /dev/null +++ b/src/azure/ApiCenterDefines/ApiCenterVersion.ts @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { ApiCenterDataPlaneService } from "../ApiCenter/ApiCenterDataPlaneAPIs"; +import { ApiCenterService } from "../ApiCenter/ApiCenterService"; +import { + ApiCenterApi, + ApiCenterApiVersion, + DataPlaneApiCenterApi, + DataPlaneApiCenterApiVersion, + GeneralApiCenterApiVersion +} from "../ApiCenter/contracts"; +import { ApiCenterVersionDefinitionsDataplane, ApiCenterVersionDefinitionsManagement, IDefinitionsBase } from "./ApiCenterDefinition"; +export type IVersionsBase = { + getName: () => string; + _nextLink: string | undefined; + getNextLink: () => string | undefined; + getChild: (context: ISubscriptionContext, apiName: string) => Promise; + generateChild: (data: GeneralApiCenterApiVersion) => IVersionBase; +} + +export class ApiCenterVersionsManagement implements IVersionsBase { + constructor(public data: ApiCenterApi) { } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + async getChild(context: ISubscriptionContext, apiName: string): Promise { + const resourceGroupName = getResourceGroupFromId(this.data.id); + const apiCenterService = new ApiCenterService(context, resourceGroupName, apiName); + const apis = await apiCenterService.getApiCenterApiVersions(this.data.name); + this._nextLink = apis.nextLink; + return apis.value; + } + generateChild(data: GeneralApiCenterApiVersion): IVersionBase { + return new ApiCenterVersionManagement(data as ApiCenterApiVersion); + } + getName(): string { + return this.data.name; + } +} + +export class ApiCneterVersionsDataplane implements IVersionsBase { + constructor(public data: DataPlaneApiCenterApi) { } + getName(): string { + return this.data.name; + } + _nextLink: string | undefined; + getNextLink(): string | undefined { + return this._nextLink; + } + async getChild(context: ISubscriptionContext, apiName: string): Promise { + const server = new ApiCenterDataPlaneService(context); + const res = await server.getAPiCenterApiVersions(this.data.name); + this._nextLink = res.nextLink; + return res.value; + } + generateChild(data: GeneralApiCenterApiVersion): IVersionBase { + return new ApiCenterVersionDataplane(data as DataPlaneApiCenterApiVersion); + } +} + +export type IVersionBase = { + getLable: () => string, + getId: () => string, + getName: () => string, + generateChild: () => IDefinitionsBase; +}; + +export class ApiCenterVersionManagement implements IVersionBase { + constructor(public data: ApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + generateChild(): IDefinitionsBase { + return new ApiCenterVersionDefinitionsManagement(this.data); + } + getLable() { + return this.data.properties.title; + } + getId() { + return this.data.id; + } +}; + +export class ApiCenterVersionDataplane implements IVersionBase { + constructor(public data: DataPlaneApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + generateChild(): IDefinitionsBase { + return new ApiCenterVersionDefinitionsDataplane(this.data); + } + getLable() { + return this.data.name; + } + getId() { + return this.data.name; + } +}; diff --git a/src/commands/editOpenApi.ts b/src/commands/editOpenApi.ts index 4bab1e88..c112432f 100644 --- a/src/commands/editOpenApi.ts +++ b/src/commands/editOpenApi.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { IActionContext } from "@microsoft/vscode-azext-utils"; import { commands } from "vscode"; -import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index b4f51f3b..dbcfc184 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -5,8 +5,8 @@ import * as fs from "fs-extra"; import fetch from 'node-fetch'; import * as path from "path"; import * as vscode from "vscode"; -import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; import { createTemporaryFolder } from "../utils/fsUtil"; diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index 7f92aa42..109ab51d 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -4,9 +4,9 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fse from 'fs-extra'; import * as vscode from 'vscode'; -import { ApiCenterVersionDefinitionManagement } from "../../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName } from "../../azure/ApiCenter/contracts"; +import { ApiCenterVersionDefinitionManagement } from "../../azure/ApiCenterDefines/ApiCenterDefinition"; import { ext } from "../../extensionVariables"; import { ApiCenterTreeItem } from "../../tree/ApiCenterTreeItem"; import { ApisTreeItem } from "../../tree/ApisTreeItem"; diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index ca35dde5..f58b34d9 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -2,8 +2,8 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; import * as sinon from "sinon"; -import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterApiVersionDefinition } from "../../../azure/ApiCenter/contracts"; +import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenterDefines/ApiCenterDefinition"; import { ExportAPI } from "../../../commands/exportApi"; import { TelemetryClient } from "../../../common/telemetryClient"; import { ApiVersionDefinitionTreeItem } from "../../../tree/ApiVersionDefinitionTreeItem"; diff --git a/src/tree/ApiCenterTreeItem.ts b/src/tree/ApiCenterTreeItem.ts index e26aebf1..1b919721 100644 --- a/src/tree/ApiCenterTreeItem.ts +++ b/src/tree/ApiCenterTreeItem.ts @@ -2,9 +2,9 @@ // Licensed under the MIT license. import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; -import { ApiCenterServiceManagement } from "../azure/ApiCenter/ApiCenterDefinition"; import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; import { ApiCenter } from "../azure/ApiCenter/contracts"; +import { ApiCenterApisManagement } from "../azure/ApiCenterDefines/ApiCenterApi"; import { UiStrings } from "../uiStrings"; import { treeUtils } from "../utils/treeUtils"; import { ApisTreeItem } from "./ApisTreeItem"; @@ -22,7 +22,7 @@ export class ApiCenterTreeItem extends AzExtParentTreeItem { constructor(parent: AzExtParentTreeItem, apicenter: ApiCenter) { super(parent); this._apicenter = apicenter; - this.apisTreeItem = new ApisTreeItem(this, new ApiCenterServiceManagement(apicenter)); + this.apisTreeItem = new ApisTreeItem(this, new ApiCenterApisManagement(apicenter)); this.environmentsTreeItem = new EnvironmentsTreeItem(this, apicenter); } diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index b54aeed1..ba698869 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterApiManagement, IApiCenterBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { ApiCenterApiManagement, IApiCenterApiBase } from "../azure/ApiCenterDefines/ApiCenterApi"; import { UiStrings } from "../uiStrings"; import { ApiDeploymentsTreeItem } from "./ApiDeploymentsTreeItem"; import { ApiVersionsTreeItem } from "./ApiVersionsTreeItem"; @@ -12,13 +12,13 @@ export class ApiTreeItem extends AzExtParentTreeItem { public readonly contextValue: string = ApiTreeItem.contextValue; public readonly apiVersionsTreeItem: ApiVersionsTreeItem; public readonly apiDeploymentsTreeItem?: ApiDeploymentsTreeItem; - private readonly _apiCenterApi: IApiCenterBase; + private readonly _apiCenterApi: IApiCenterApiBase; private readonly _apiCenterName: string; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterBase) { + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterApiBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; - this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); + this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi.generateChild()); if (apiCenterApi instanceof ApiCenterApiManagement) { this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(this, apiCenterName, (apiCenterApi as ApiCenterApiManagement).getData()); } diff --git a/src/tree/ApiVersionDefinitionTreeItem.ts b/src/tree/ApiVersionDefinitionTreeItem.ts index 8fe1ab84..4dafd13f 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { IDefinitionBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { IDefinitionBase } from "../azure/ApiCenterDefines/ApiCenterDefinition"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { public readonly contextValue: string = ""; constructor( diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index 1ff3b27f..6a2058a4 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { IVersionBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { IDefinitionsBase } from "../azure/ApiCenterDefines/ApiCenterDefinition"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionTreeItem } from "./ApiVersionDefinitionTreeItem"; export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { @@ -11,12 +11,12 @@ export class ApiVersionDefinitionsTreeItem extends AzExtParentTreeItem { public readonly contextValue: string = ApiVersionDefinitionsTreeItem.contextValue; private readonly _apiCenterName: string; private readonly _apiCenterApiName: string; - private readonly _apiCenterApiVersion: IVersionBase; + private readonly _apiCenterApiVersion: IDefinitionsBase; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: IVersionBase) { + apiCenterApiVersion: IDefinitionsBase) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this._apiCenterName = apiCenterName; diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index 05287d8f..4c405e9f 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { IVersionBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { IVersionBase } from "../azure/ApiCenterDefines/ApiCenterVersion"; import { UiStrings } from "../uiStrings"; import { ApiVersionDefinitionsTreeItem } from "./ApiVersionDefinitionsTreeItem"; export class ApiVersionTreeItem extends AzExtParentTreeItem { @@ -18,7 +18,7 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { apiCenterApiVersion: IVersionBase) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; - this.apiVersionDefinitionsTreeItem = new ApiVersionDefinitionsTreeItem(this, apiCenterName, apiCenterApiName, apiCenterApiVersion); + this.apiVersionDefinitionsTreeItem = new ApiVersionDefinitionsTreeItem(this, apiCenterName, apiCenterApiName, apiCenterApiVersion.generateChild()); } public get iconPath(): TreeItemIconPath { @@ -34,7 +34,7 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public hasMoreChildrenImpl(): boolean { - return this._apiCenterApiVersion.getNextLink() !== undefined; + return false; } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index d952bcd3..494b9589 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { IApiCenterBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { IVersionsBase } from "../azure/ApiCenterDefines/ApiCenterVersion"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -11,8 +11,8 @@ export class ApiVersionsTreeItem extends AzExtParentTreeItem { public static contextValue: string = "azureApiCenterApiVersions"; public readonly contextValue: string = ApiVersionsTreeItem.contextValue; private readonly _apiCenterName: string; - private readonly _apiCenterApi: IApiCenterBase; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterBase) { + private readonly _apiCenterApi: IVersionsBase; + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IVersionsBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; diff --git a/src/tree/ApisTreeItem.ts b/src/tree/ApisTreeItem.ts index 5aa4a9d9..b5112efe 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext, TreeItemIconPath } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { IApiCenterServiceBase } from "../azure/ApiCenter/ApiCenterDefinition"; +import { IApiCenterApisBase } from "../azure/ApiCenterDefines/ApiCenterApi"; import { UiStrings } from "../uiStrings"; import { ApiTreeItem } from "./ApiTreeItem"; export class ApisTreeItem extends AzExtParentTreeItem { @@ -11,7 +11,7 @@ export class ApisTreeItem extends AzExtParentTreeItem { public searchContent: string = ""; public contextValue: string = ApisTreeItem.contextValue; private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, public apiCenter: IApiCenterServiceBase) { + constructor(parent: AzExtParentTreeItem, public apiCenter: IApiCenterApisBase) { super(parent); } diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts index 93233600..53331185 100644 --- a/src/tree/DataPlaneAccount.ts +++ b/src/tree/DataPlaneAccount.ts @@ -3,7 +3,7 @@ import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, ISubscriptionContext, TreeItemIconPath, registerEvent } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; import { DataPlaneAccount } from "../azure/ApiCenter/ApiCenterDataPlaneAPIs"; -import { ApiCenterServiceDataPlane } from "../azure/ApiCenter/ApiCenterDefinition"; +import { ApiCenterApisDataplane } from "../azure/ApiCenterDefines/ApiCenterApi"; import { AzureDataSessionProvider } from "../azure/azureLogin/authTypes"; import { AzureAuth } from "../azure/azureLogin/azureAuth"; import { AzureDataSessionProviderHelper, generateScopes } from "../azure/azureLogin/dataSessionProvider"; @@ -84,7 +84,7 @@ export class ApiServerItem extends AzExtParentTreeItem { super(parent); this.label = subContext.subscriptionPath.split('.')[0]; this.subscriptionContext = subContext; - this.apisTreeItem = new ApisTreeItem(this, new ApiCenterServiceDataPlane({ name: this.label })); + this.apisTreeItem = new ApisTreeItem(this, new ApiCenterApisDataplane({ name: this.label })); } public get id(): string { return this.label; diff --git a/src/utils/apiSpecificationUtils.ts b/src/utils/apiSpecificationUtils.ts index a6989827..179bbc03 100644 --- a/src/utils/apiSpecificationUtils.ts +++ b/src/utils/apiSpecificationUtils.ts @@ -3,7 +3,7 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenter/ApiCenterDefinition"; +import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; import { ApiSpecificationOptions, openapi } from "../constants"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; From d538370db200d9db61aa0e5976b2afeca60a2bfa Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 16:16:05 +0800 Subject: [PATCH 17/29] feat: update --- src/azure/ApiCenterDefines/ApiCenterApi.ts | 8 ++++---- src/azure/ApiCenterDefines/ApiCenterDefinition.ts | 2 +- src/azure/ApiCenterDefines/ApiCenterVersion.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/azure/ApiCenterDefines/ApiCenterApi.ts b/src/azure/ApiCenterDefines/ApiCenterApi.ts index 51598db9..1f2edf3a 100644 --- a/src/azure/ApiCenterDefines/ApiCenterApi.ts +++ b/src/azure/ApiCenterDefines/ApiCenterApi.ts @@ -19,7 +19,7 @@ export type IApiCenterApisBase = { getNextLink: () => string | undefined; getChild: (context: ISubscriptionContext, content: string) => Promise; generateChild: (data: GeneralApiCenterApi) => IApiCenterApiBase; -} +}; export class ApiCenterApisManagement implements IApiCenterApisBase { constructor(public data: ApiCenter) { } @@ -51,7 +51,7 @@ export class ApiCenterApisDataplane implements IApiCenterApisBase { return this.data.name; } getName(): string { - return this.data.name + return this.data.name; } _nextLink: string | undefined; getNextLink(): string | undefined { @@ -71,8 +71,8 @@ export class ApiCenterApisDataplane implements IApiCenterApisBase { export type IApiCenterApiBase = { _nextLink: string | undefined; getNextLink: () => string | undefined; - getName: () => string, - getId: () => string, + getName: () => string; + getId: () => string; getLable: () => string; generateChild: () => IVersionsBase; }; diff --git a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts index 6c87fa8a..408cb7f5 100644 --- a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts +++ b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts @@ -18,7 +18,7 @@ export type IDefinitionsBase = { getNextLink: () => string | undefined; getChild: (context: ISubscriptionContext, apiName: string, apiServiceName: string) => Promise; generateChild: (data: GeneralApiCenterApiVersionDefinition) => IDefinitionBase; -} +}; export class ApiCenterVersionDefinitionsManagement implements IDefinitionsBase { constructor(public data: ApiCenterApiVersion) { } diff --git a/src/azure/ApiCenterDefines/ApiCenterVersion.ts b/src/azure/ApiCenterDefines/ApiCenterVersion.ts index 75899dbf..dd53ce3d 100644 --- a/src/azure/ApiCenterDefines/ApiCenterVersion.ts +++ b/src/azure/ApiCenterDefines/ApiCenterVersion.ts @@ -18,7 +18,7 @@ export type IVersionsBase = { getNextLink: () => string | undefined; getChild: (context: ISubscriptionContext, apiName: string) => Promise; generateChild: (data: GeneralApiCenterApiVersion) => IVersionBase; -} +}; export class ApiCenterVersionsManagement implements IVersionsBase { constructor(public data: ApiCenterApi) { } From 06271c8504648865514ece1085a34cc6877a493c Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 16:33:31 +0800 Subject: [PATCH 18/29] feat: update --- src/azure/ApiCenter/contracts.ts | 4 ---- src/azure/ApiCenterDefines/ApiCenterApi.ts | 6 +++--- src/azure/ApiCenterDefines/ApiCenterDefinition.ts | 8 ++++---- src/azure/ApiCenterDefines/ApiCenterVersion.ts | 8 ++++---- src/commands/signInToDataPlane.ts | 8 ++------ tsconfig.json | 1 - 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index c6441bfb..da418853 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -1,9 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export enum IApiCenterType { - management = "management", - dataplane = "dataplane" -} export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; export type ApiCenter = { diff --git a/src/azure/ApiCenterDefines/ApiCenterApi.ts b/src/azure/ApiCenterDefines/ApiCenterApi.ts index 1f2edf3a..977ac6bc 100644 --- a/src/azure/ApiCenterDefines/ApiCenterApi.ts +++ b/src/azure/ApiCenterDefines/ApiCenterApi.ts @@ -22,7 +22,7 @@ export type IApiCenterApisBase = { }; export class ApiCenterApisManagement implements IApiCenterApisBase { - constructor(public data: ApiCenter) { } + constructor(private data: ApiCenter) { } getId(): string { return this.data.id; } @@ -78,7 +78,7 @@ export type IApiCenterApiBase = { }; export class ApiCenterApiManagement implements IApiCenterApiBase { - constructor(public data: ApiCenterApi) { } + constructor(private data: ApiCenterApi) { } getData(): ApiCenterApi { return this.data; } @@ -101,7 +101,7 @@ export class ApiCenterApiManagement implements IApiCenterApiBase { }; export class ApiCenterApiDataPlane implements IApiCenterApiBase { - constructor(public data: DataPlaneApiCenterApi) { } + constructor(private data: DataPlaneApiCenterApi) { } getLable(): string { return this.data.name; } diff --git a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts index 408cb7f5..ee109b9e 100644 --- a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts +++ b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts @@ -21,7 +21,7 @@ export type IDefinitionsBase = { }; export class ApiCenterVersionDefinitionsManagement implements IDefinitionsBase { - constructor(public data: ApiCenterApiVersion) { } + constructor(private data: ApiCenterApiVersion) { } getName(): string { return this.data.name; } @@ -43,7 +43,7 @@ export class ApiCenterVersionDefinitionsManagement implements IDefinitionsBase { } export class ApiCenterVersionDefinitionsDataplane implements IDefinitionsBase { - constructor(public data: DataPlaneApiCenterApiVersion) { } + constructor(private data: DataPlaneApiCenterApiVersion) { } getName(): string { return this.data.name; } @@ -71,7 +71,7 @@ export type IDefinitionBase = { }; export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { - constructor(public data: ApiCenterApiVersionDefinition) { } + constructor(private data: ApiCenterApiVersionDefinition) { } async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { const resourceGroupName = getResourceGroupFromId(this.data.id); const apiCenterService = new ApiCenterService( @@ -100,7 +100,7 @@ export class ApiCenterVersionDefinitionManagement implements IDefinitionBase { }; export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { - constructor(public data: DataPlaneApiCenterApiVersionDefinition) { } + constructor(private data: DataPlaneApiCenterApiVersionDefinition) { } async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { let server = new ApiCenterDataPlaneService(context); let results = await server.exportSpecification(apiName, diff --git a/src/azure/ApiCenterDefines/ApiCenterVersion.ts b/src/azure/ApiCenterDefines/ApiCenterVersion.ts index dd53ce3d..4fa95ce5 100644 --- a/src/azure/ApiCenterDefines/ApiCenterVersion.ts +++ b/src/azure/ApiCenterDefines/ApiCenterVersion.ts @@ -21,7 +21,7 @@ export type IVersionsBase = { }; export class ApiCenterVersionsManagement implements IVersionsBase { - constructor(public data: ApiCenterApi) { } + constructor(private data: ApiCenterApi) { } _nextLink: string | undefined; getNextLink(): string | undefined { return this._nextLink; @@ -42,7 +42,7 @@ export class ApiCenterVersionsManagement implements IVersionsBase { } export class ApiCneterVersionsDataplane implements IVersionsBase { - constructor(public data: DataPlaneApiCenterApi) { } + constructor(private data: DataPlaneApiCenterApi) { } getName(): string { return this.data.name; } @@ -69,7 +69,7 @@ export type IVersionBase = { }; export class ApiCenterVersionManagement implements IVersionBase { - constructor(public data: ApiCenterApiVersion) { } + constructor(private data: ApiCenterApiVersion) { } getName(): string { return this.data.name; } @@ -85,7 +85,7 @@ export class ApiCenterVersionManagement implements IVersionBase { }; export class ApiCenterVersionDataplane implements IVersionBase { - constructor(public data: DataPlaneApiCenterApiVersion) { } + constructor(private data: DataPlaneApiCenterApiVersion) { } getName(): string { return this.data.name; } diff --git a/src/commands/signInToDataPlane.ts b/src/commands/signInToDataPlane.ts index 9b68c75c..0b1ca3f7 100644 --- a/src/commands/signInToDataPlane.ts +++ b/src/commands/signInToDataPlane.ts @@ -3,11 +3,7 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import { AzureDataSessionProviderHelper, generateScopes } from "../azure/azureLogin/dataSessionProvider"; import { ApisTreeItem } from "../tree/ApisTreeItem"; -import { ApiServerItem } from "../tree/DataPlaneAccount"; export async function SignInToDataPlane(context: IActionContext, node: ApisTreeItem) { - if (!(node instanceof ApisTreeItem)) { - let parentNode = (node as ApisTreeItem).parent as ApiServerItem; - let scopes = generateScopes(parentNode.subscription!.userId!, parentNode.subscription!.tenantId!); - await AzureDataSessionProviderHelper.getSessionProvider().signIn(scopes); - } + const scopes = generateScopes(node.parent?.subscription!.userId!, node.parent?.subscription!.tenantId!); + await AzureDataSessionProviderHelper.getSessionProvider().signIn(scopes); } diff --git a/tsconfig.json b/tsconfig.json index a4e57674..b75962be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "module": "commonjs", - "experimentalDecorators": true, "target": "ES2020", "lib": [ "ES2020" From d3bf5db2ad73c0312614619e1c8ecf6be31386df Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 18:11:07 +0800 Subject: [PATCH 19/29] test: add test cases --- src/azure/ApiCenterDefines/ApiCenterApi.ts | 6 +- .../ApiCenterDefines/ApiCenterDefinition.ts | 3 +- .../ApiCenterDefines/ApiCenterVersion.ts | 6 +- src/commands/exportApi.ts | 16 +- .../ApiCenterDefines/ApiCenterApi.test.ts | 134 +++++++++++++++++ .../ApiCenterDefinition.test.ts | 138 ++++++++++++++++++ .../ApiCenterDefines/ApiCenterVersion.test.ts | 110 ++++++++++++++ src/test/unit/commands/exportApi.test.ts | 3 +- src/tree/ApiTreeItem.ts | 2 +- src/tree/ApiVersionTreeItem.ts | 2 +- src/tree/Editors/openApi/OpenApiEditor.ts | 11 +- src/utils/generalUtils.ts | 13 +- 12 files changed, 416 insertions(+), 28 deletions(-) create mode 100644 src/test/unit/azure/ApiCenterDefines/ApiCenterApi.test.ts create mode 100644 src/test/unit/azure/ApiCenterDefines/ApiCenterDefinition.test.ts create mode 100644 src/test/unit/azure/ApiCenterDefines/ApiCenterVersion.test.ts diff --git a/src/azure/ApiCenterDefines/ApiCenterApi.ts b/src/azure/ApiCenterDefines/ApiCenterApi.ts index 977ac6bc..8e64d7ff 100644 --- a/src/azure/ApiCenterDefines/ApiCenterApi.ts +++ b/src/azure/ApiCenterDefines/ApiCenterApi.ts @@ -73,7 +73,7 @@ export type IApiCenterApiBase = { getNextLink: () => string | undefined; getName: () => string; getId: () => string; - getLable: () => string; + getLabel: () => string; generateChild: () => IVersionsBase; }; @@ -92,7 +92,7 @@ export class ApiCenterApiManagement implements IApiCenterApiBase { getId(): string { return this.data.id; } - getLable(): string { + getLabel(): string { return this.data.properties.title; } generateChild(): IVersionsBase { @@ -102,7 +102,7 @@ export class ApiCenterApiManagement implements IApiCenterApiBase { export class ApiCenterApiDataPlane implements IApiCenterApiBase { constructor(private data: DataPlaneApiCenterApi) { } - getLable(): string { + getLabel(): string { return this.data.name; } getId(): string { diff --git a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts index ee109b9e..807d43a1 100644 --- a/src/azure/ApiCenterDefines/ApiCenterDefinition.ts +++ b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts @@ -103,8 +103,7 @@ export class ApiCenterVersionDefinitionDataPlane implements IDefinitionBase { constructor(private data: DataPlaneApiCenterApiVersionDefinition) { } async getDefinitions(context: ISubscriptionContext, apiServiceName: string, apiName: string, apiVersionName: string): Promise { let server = new ApiCenterDataPlaneService(context); - let results = await server.exportSpecification(apiName, - apiVersionName, this.data.name); + let results = await server.exportSpecification(apiName, apiVersionName, this.data.name); return results; }; static contextValue: string = "azureApiCenterApiVersionDataPlaneDefinitionTreeItem"; diff --git a/src/azure/ApiCenterDefines/ApiCenterVersion.ts b/src/azure/ApiCenterDefines/ApiCenterVersion.ts index 4fa95ce5..4967b9de 100644 --- a/src/azure/ApiCenterDefines/ApiCenterVersion.ts +++ b/src/azure/ApiCenterDefines/ApiCenterVersion.ts @@ -62,7 +62,7 @@ export class ApiCneterVersionsDataplane implements IVersionsBase { } export type IVersionBase = { - getLable: () => string, + getLabel: () => string, getId: () => string, getName: () => string, generateChild: () => IDefinitionsBase; @@ -76,7 +76,7 @@ export class ApiCenterVersionManagement implements IVersionBase { generateChild(): IDefinitionsBase { return new ApiCenterVersionDefinitionsManagement(this.data); } - getLable() { + getLabel() { return this.data.properties.title; } getId() { @@ -92,7 +92,7 @@ export class ApiCenterVersionDataplane implements IVersionBase { generateChild(): IDefinitionsBase { return new ApiCenterVersionDefinitionsDataplane(this.data); } - getLable() { + getLabel() { return this.data.name; } getId() { diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index dbcfc184..4ee3d28e 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fs from "fs-extra"; -import fetch from 'node-fetch'; + import * as path from "path"; import * as vscode from "vscode"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; @@ -10,6 +10,7 @@ import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; import { createTemporaryFolder } from "../utils/fsUtil"; +import { GeneralUtils } from "../utils/generalUtils"; export namespace ExportAPI { export async function exportApi( context: IActionContext, @@ -34,18 +35,7 @@ export namespace ExportAPI { if (specFormat === ApiSpecExportResultFormat.inline) { await ExportAPI.showTempFile(node, specValue); } else if (specFormat === ApiSpecExportResultFormat.link) { - await ExportAPI.showTempFile(node, await ExportAPI.fetchDataFromLink(specValue)); - } - } - - export async function fetchDataFromLink(link: string): Promise { - try { - const res = await fetch(link); - const rawData = await res.json(); - return JSON.stringify(rawData); - } - catch (err) { - throw err; + await ExportAPI.showTempFile(node, await GeneralUtils.fetchDataFromLink(specValue)); } } diff --git a/src/test/unit/azure/ApiCenterDefines/ApiCenterApi.test.ts b/src/test/unit/azure/ApiCenterDefines/ApiCenterApi.test.ts new file mode 100644 index 00000000..4ec47769 --- /dev/null +++ b/src/test/unit/azure/ApiCenterDefines/ApiCenterApi.test.ts @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as assert from "assert"; +import * as sinon from "sinon"; +import { ApiCenter, ApiCenterApi, DataPlaneApiCenter, DataPlaneApiCenterApi } from "../../../../azure/ApiCenter/contracts"; +import { ApiCenterApiDataPlane, ApiCenterApiManagement, ApiCenterApisDataplane, ApiCenterApisManagement } from "../../../../azure/ApiCenterDefines/ApiCenterApi"; +describe('Azure ApiCenter Defines ApiCenterApisManagement', () => { + let sandbox = null as any; + let data: ApiCenter; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + resourceGroup: "fakeRG", + properties: {}, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterApisManagement class getId", () => { + const api: ApiCenterApisManagement = new ApiCenterApisManagement(data); + const res = api.getId(); + assert.strictEqual("fakeId", res); + }); + it("ApiCenterApisManagement class getName", () => { + const api: ApiCenterApisManagement = new ApiCenterApisManagement(data); + const res = api.getName(); + assert.strictEqual("fakeName", res); + }); +}); +describe('Azure ApiCenter Defines ApiCenterApisDataplane', () => { + let sandbox = null as any; + let data: DataPlaneApiCenter; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterApisDataplane class getId", () => { + const api: ApiCenterApisDataplane = new ApiCenterApisDataplane(data); + const res = api.getId(); + assert.strictEqual("fakeName", res); + }); + it("ApiCenterApisDataplane class getName", () => { + const api: ApiCenterApisDataplane = new ApiCenterApisDataplane(data); + const res = api.getName(); + assert.strictEqual("fakeName", res); + }); +}); +describe('Azure ApiCenter Defines ApiCenterApiManagement', () => { + let sandbox = null as any; + let data: ApiCenterApi; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + kind: "fakeKind" + }, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterApiManagement class getId", () => { + const api: ApiCenterApiManagement = new ApiCenterApiManagement(data); + const res = api.getId(); + assert.strictEqual("fakeId", res); + }); + it('ApiCenterApiManagement class getName', () => { + const api: ApiCenterApiManagement = new ApiCenterApiManagement(data); + const res = api.getName(); + assert.strictEqual("fakeName", res); + }); + it('ApiCenterApiManagement class getData', () => { + const api: ApiCenterApiManagement = new ApiCenterApiManagement(data); + const res = api.getData(); + assert.equal("fakeName", res.name); + assert.equal("fakeId", res.id); + assert.equal("fakeLocation", res.location); + assert.equal("fakeTitle", res.properties.title); + }); + it('ApiCenterApiManagement class getLabel', () => { + const api: ApiCenterApiManagement = new ApiCenterApiManagement(data); + const res = api.getLabel(); + assert.strictEqual(res, 'fakeTitle'); + }); +}); +describe('Azure ApiCenter Defines ApiCenterApiDataPlane', () => { + let sandbox = null as any; + let data: DataPlaneApiCenterApi; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName", + title: "fakeTitle", + kind: "fakeKind", + lifecycleStage: "fakeStage", + externalDocumentation: [], + contacts: [], + customProperties: {} + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it('ApiCenterApiDataPlane getId', () => { + let obj: ApiCenterApiDataPlane = new ApiCenterApiDataPlane(data); + let res = obj.getId(); + assert.equal('fakeName', res); + }); + it('ApiCenterApiDataPlane getLabel', () => { + let obj: ApiCenterApiDataPlane = new ApiCenterApiDataPlane(data); + let res = obj.getLabel(); + assert.equal('fakeName', res); + }); + it('ApiCenterApiDataPlane getname', () => { + let obj: ApiCenterApiDataPlane = new ApiCenterApiDataPlane(data); + let res = obj.getName(); + assert.equal('fakeName', res); + }); +}); diff --git a/src/test/unit/azure/ApiCenterDefines/ApiCenterDefinition.test.ts b/src/test/unit/azure/ApiCenterDefines/ApiCenterDefinition.test.ts new file mode 100644 index 00000000..ebd326d1 --- /dev/null +++ b/src/test/unit/azure/ApiCenterDefines/ApiCenterDefinition.test.ts @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as assert from "assert"; +import * as sinon from "sinon"; +import { ApiCenterApiVersion, ApiCenterApiVersionDefinition, DataPlaneApiCenterApiVersion, DataPlaneApiCenterApiVersionDefinition } from "../../../../azure/ApiCenter/contracts"; +import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement, ApiCenterVersionDefinitionsDataplane, ApiCenterVersionDefinitionsManagement } from "../../../../azure/ApiCenterDefines/ApiCenterDefinition"; + +describe('ApiCenterVersionDefinitionsManagement class test', () => { + let sandbox = null as any; + let data: ApiCenterApiVersion; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + lifecycleStage: "fakeStage" + }, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterVersionDefinitionsManagement getName", () => { + let obj: ApiCenterVersionDefinitionsManagement = new ApiCenterVersionDefinitionsManagement(data); + let res = obj.getName(); + assert.equal(res, "fakeName"); + }); +}); +describe('ApiCenterVersionDefinitionsDataplane class test', () => { + let sandbox = null as any; + let data: DataPlaneApiCenterApiVersion; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName", + title: "fakeTitle", + lifecycleStage: "fakeStage" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterVersionDefinitionsDataplane getName", () => { + let obj: ApiCenterVersionDefinitionsDataplane = new ApiCenterVersionDefinitionsDataplane(data); + let res = obj.getName(); + assert.equal(res, "fakeName"); + }); +}); +describe('ApiCenterVersionDefinitionManagement class test', () => { + let sandbox = null as any; + let data: ApiCenterApiVersionDefinition; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + specification: { + name: "fakeSpecName", + version: "fakeVersion" + } + }, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterVersionDefinitionManagement getLabel", () => { + let obj: ApiCenterVersionDefinitionManagement = new ApiCenterVersionDefinitionManagement(data); + let res = obj.getLabel(); + assert.equal(res, "fakeTitle"); + }); + it("ApiCenterVersionDefinitionManagement getId", () => { + let obj: ApiCenterVersionDefinitionManagement = new ApiCenterVersionDefinitionManagement(data); + let res = obj.getId(); + assert.equal(res, "fakeId"); + }); + it("ApiCenterVersionDefinitionManagement getContext", () => { + let obj: ApiCenterVersionDefinitionManagement = new ApiCenterVersionDefinitionManagement(data); + let res = obj.getContext(); + assert.equal(res, "azureApiCenterApiVersionDefinitionTreeItem-fakespecname"); + }); + it("ApiCenterVersionDefinitionManagement getName", () => { + let obj: ApiCenterVersionDefinitionManagement = new ApiCenterVersionDefinitionManagement(data); + let res = obj.getName(); + assert.equal(res, "fakeName"); + }); +}); +describe('ApiCenterVersionDefinitionDataPlane class test', () => { + let sandbox = null as any; + let data: DataPlaneApiCenterApiVersionDefinition = { + name: "", + title: "", + specification: { + name: "" + } + }; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName", + title: "fakeTitle", + specification: { + name: "fakeSpecName" + } + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it("ApiCenterVersionDefinitionDataPlane getLabel", () => { + let obj: ApiCenterVersionDefinitionDataPlane = new ApiCenterVersionDefinitionDataPlane(data); + let res = obj.getLabel(); + assert.equal(res, "fakeName"); + }); + it("ApiCenterVersionDefinitionDataPlane getId", () => { + let obj: ApiCenterVersionDefinitionDataPlane = new ApiCenterVersionDefinitionDataPlane(data); + let res = obj.getId(); + assert.equal(res, "fakeName"); + }); + it("ApiCenterVersionDefinitionDataPlane getContext", () => { + let obj: ApiCenterVersionDefinitionDataPlane = new ApiCenterVersionDefinitionDataPlane(data); + let res = obj.getContext(); + assert.equal(res, "azureApiCenterApiVersionDataPlaneDefinitionTreeItem-fakespecname"); + }); + it("ApiCenterVersionDefinitionDataPlane getName", () => { + let obj: ApiCenterVersionDefinitionDataPlane = new ApiCenterVersionDefinitionDataPlane(data); + let res = obj.getName(); + assert.equal(res, "fakeName"); + }); +}); diff --git a/src/test/unit/azure/ApiCenterDefines/ApiCenterVersion.test.ts b/src/test/unit/azure/ApiCenterDefines/ApiCenterVersion.test.ts new file mode 100644 index 00000000..7337be01 --- /dev/null +++ b/src/test/unit/azure/ApiCenterDefines/ApiCenterVersion.test.ts @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as assert from "assert"; +import * as sinon from "sinon"; +import { ApiCenterApi, ApiCenterApiVersion, DataPlaneApiCenterApi, DataPlaneApiCenterApiVersion } from "../../../../azure/ApiCenter/contracts"; +import { ApiCenterVersionDataplane, ApiCenterVersionManagement, ApiCenterVersionsManagement, ApiCneterVersionsDataplane } from "../../../../azure/ApiCenterDefines/ApiCenterVersion"; +describe('ApiCenterVersionsManagement class test', () => { + let sandbox = null as any; + let data: ApiCenterApi; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + kind: "fakeKind" + }, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it('ApiCenterVersionsManagement getName', () => { + let obj: ApiCenterVersionsManagement = new ApiCenterVersionsManagement(data); + let res = obj.getName(); + assert.equal(res, 'fakeName'); + }); +}); +describe('ApiCneterVersionsDataplane class test', () => { + let sandbox = null as any; + let data: DataPlaneApiCenterApi; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName", + title: "fakeTitle", + kind: "fakeKind", + lifecycleStage: "fakeStage", + externalDocumentation: [], + contacts: [], + customProperties: {} + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it('ApiCneterVersionsDataplane getName', () => { + let obj: ApiCneterVersionsDataplane = new ApiCneterVersionsDataplane(data); + let res = obj.getName(); + assert.equal(res, 'fakeName'); + }); +}); +describe('ApiCenterVersionManagement class test', () => { + let sandbox = null as any; + let data: ApiCenterApiVersion; + before(() => { + sandbox = sinon.createSandbox(); + data = { + id: "fakeId", + location: "fakeLocation", + name: "fakeName", + properties: { + title: "fakeTitle", + lifecycleStage: "fakeStage" + }, + type: "fakeType" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it('ApiCenterVersionManagement getName', () => { + let obj: ApiCenterVersionManagement = new ApiCenterVersionManagement(data); + let res = obj.getName(); + assert.equal(res, 'fakeName'); + }); + it('ApiCenterVersionManagement getLabel', () => { + let obj: ApiCenterVersionManagement = new ApiCenterVersionManagement(data); + let res = obj.getLabel(); + assert.equal(res, 'fakeTitle'); + }); +}); +describe('ApiCenterVersionDataplane class test', () => { + let sandbox = null as any; + let data: DataPlaneApiCenterApiVersion; + before(() => { + sandbox = sinon.createSandbox(); + data = { + name: "fakeName", + title: "fakeTitle", + lifecycleStage: "fakeStage" + }; + }); + afterEach(() => { + sandbox.restore(); + }); + it('ApiCenterVersionDataplane getName', () => { + let obj: ApiCenterVersionDataplane = new ApiCenterVersionDataplane(data); + let res = obj.getName(); + assert.equal(res, 'fakeName'); + }); + it('ApiCenterVersionDataplane getLabel', () => { + let obj: ApiCenterVersionDataplane = new ApiCenterVersionDataplane(data); + let res = obj.getLabel(); + assert.equal(res, 'fakeName'); + }); +}); diff --git a/src/test/unit/commands/exportApi.test.ts b/src/test/unit/commands/exportApi.test.ts index f58b34d9..036b2f8f 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -7,6 +7,7 @@ import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenterDe import { ExportAPI } from "../../../commands/exportApi"; import { TelemetryClient } from "../../../common/telemetryClient"; import { ApiVersionDefinitionTreeItem } from "../../../tree/ApiVersionDefinitionTreeItem"; +import { GeneralUtils } from "../../../utils/generalUtils"; abstract class ParentTreeItemBase extends AzExtParentTreeItem { private _childIndex: number = 0; public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { @@ -68,7 +69,7 @@ describe("export API test cases", () => { it('export API happy path with link type', async () => { let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); sandbox.stub(node.apiCenterApiVersionDefinition, "getDefinitions").resolves({ format: "link", value: "fakeValue" }); - sandbox.stub(ExportAPI, "fetchDataFromLink").resolves(); + sandbox.stub(GeneralUtils, "fetchDataFromLink").resolves(); await ExportAPI.exportApi({} as IActionContext, node); sandbox.assert.calledOnce(stubShowTempFile); }); diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index ba698869..12eb32e8 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -33,7 +33,7 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get label(): string { - return this._apiCenterApi.getLable(); + return this._apiCenterApi.getLabel(); } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/ApiVersionTreeItem.ts b/src/tree/ApiVersionTreeItem.ts index 4c405e9f..1add6fc2 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -30,7 +30,7 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get label(): string { - return this._apiCenterApiVersion.getLable(); + return this._apiCenterApiVersion.getLabel(); } public hasMoreChildrenImpl(): boolean { diff --git a/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index 6d1d321d..e18e2caa 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -3,12 +3,12 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { ProgressLocation, window } from "vscode"; import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApiVersionDefinitionImport } from "../../../azure/ApiCenter/contracts"; +import { ApiCenterApiVersionDefinitionImport, ApiSpecExportResultFormat } from "../../../azure/ApiCenter/contracts"; import { showSavePromptConfigKey } from "../../../constants"; import { localize } from "../../../localize"; +import { GeneralUtils } from "../../../utils/generalUtils"; import { ApiVersionDefinitionTreeItem } from "../../ApiVersionDefinitionTreeItem"; import { Editor, EditorOptions } from "../Editor"; - export class OpenApiEditor extends Editor { constructor() { super(showSavePromptConfigKey); @@ -16,7 +16,12 @@ export class OpenApiEditor extends Editor { public async getData(treeItem: ApiVersionDefinitionTreeItem): Promise { const exportedSpec = await treeItem.apiCenterApiVersionDefinition.getDefinitions(treeItem?.subscription!, treeItem?.apiCenterName!, treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!); - return exportedSpec.value; + if (exportedSpec.format === ApiSpecExportResultFormat.inline) { + return exportedSpec.value; + } else { + let rawData = GeneralUtils.fetchDataFromLink(exportedSpec.value); + return rawData; + } } public async updateData(treeItem: ApiVersionDefinitionTreeItem, data: string): Promise { diff --git a/src/utils/generalUtils.ts b/src/utils/generalUtils.ts index e8f9af28..bd1d5a51 100644 --- a/src/utils/generalUtils.ts +++ b/src/utils/generalUtils.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import fetch from 'node-fetch'; import * as os from "os"; import * as path from "path"; - export namespace GeneralUtils { const apiCenterFolder = ".api-center"; @@ -54,4 +54,15 @@ export namespace GeneralUtils { export function getApiCenterWorkspacePath(): string { return path.join(os.homedir(), apiCenterFolder); } + + export async function fetchDataFromLink(link: string): Promise { + try { + const res = await fetch(link); + const rawData = await res.json(); + return JSON.stringify(rawData); + } + catch (err) { + throw err; + } + } } From 9816f7c07a09347b455fa2229a9347f26f5f1ec7 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 2 Aug 2024 20:31:54 +0800 Subject: [PATCH 20/29] test: add test cases --- src/test/unit/commands/handleUri.test.ts | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/test/unit/commands/handleUri.test.ts diff --git a/src/test/unit/commands/handleUri.test.ts b/src/test/unit/commands/handleUri.test.ts new file mode 100644 index 00000000..3be60dd6 --- /dev/null +++ b/src/test/unit/commands/handleUri.test.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import * as assert from 'assert'; +import * as sinon from "sinon"; +import * as vscode from "vscode"; +import * as addDataPlaneApis from '../../../commands/addDataPlaneApis'; +import { handleUri } from "../../../commands/handleUri"; +describe('handleUri test happy path', () => { + let sandbox = null as any; + before(() => { + sandbox = sinon.createSandbox(); + }); + afterEach(() => { + sandbox.restore(); + }); + it('handleUri happy path', async () => { + const fakeUrl = 'vscode-insiders://apidev.azure-api-center?clientId=fakeClientId&tenantId=fakeTenantId&runtimeUrl=fakeRuntimeUrl'; + const url = vscode.Uri.parse(fakeUrl); + const stubSetAccountToExt = sandbox.stub(addDataPlaneApis, "setAccountToExt").returns(); + sandbox.stub(vscode.commands, 'executeCommand').resolves(); + await handleUri(url); + sandbox.assert.calledOnce(stubSetAccountToExt); + assert.equal(stubSetAccountToExt.getCall(0).args[0], 'fakeRuntimeUrl'); + assert.equal(stubSetAccountToExt.getCall(0).args[1], 'fakeClientId'); + assert.equal(stubSetAccountToExt.getCall(0).args[2], 'fakeTenantId'); + }); +}); From 405785a4acda888e0e956eeffe510156e3cc48a0 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Tue, 13 Aug 2024 08:48:07 +0800 Subject: [PATCH 21/29] fix: update the code according to comments --- src/commands/addDataPlaneApis.ts | 4 +- .../registerStepByStep.ts | 4 +- src/extension.ts | 68 +++++++++---------- src/uiStrings.ts | 1 + 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/commands/addDataPlaneApis.ts b/src/commands/addDataPlaneApis.ts index 552bfdcf..dac77398 100644 --- a/src/commands/addDataPlaneApis.ts +++ b/src/commands/addDataPlaneApis.ts @@ -18,7 +18,7 @@ export async function getDataPlaneApis(context: IActionContext): Promise item.domain === element.domain)) { array.push(element); + } else { + vscode.window.showInformationMessage(UiStrings.DatplaneAlreadyAdded); } } pushIfNotExist(ext.dataPlaneAccounts, { domain: domain, tenantId: tenantId, clientId: clientId }); diff --git a/src/commands/registerApiSubCommands/registerStepByStep.ts b/src/commands/registerApiSubCommands/registerStepByStep.ts index 109ab51d..ac54aaff 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -6,7 +6,7 @@ import * as fse from 'fs-extra'; import * as vscode from 'vscode'; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApi, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionImport, ApiKind, ApiVersionLifecycleStage, SpecificationName } from "../../azure/ApiCenter/contracts"; -import { ApiCenterVersionDefinitionManagement } from "../../azure/ApiCenterDefines/ApiCenterDefinition"; +import { ApiCenterVersionDefinitionDataPlane } from "../../azure/ApiCenterDefines/ApiCenterDefinition"; import { ext } from "../../extensionVariables"; import { ApiCenterTreeItem } from "../../tree/ApiCenterTreeItem"; import { ApisTreeItem } from "../../tree/ApisTreeItem"; @@ -17,7 +17,7 @@ export async function registerStepByStep(context: IActionContext, node?: ApisTre const apiCenterNode = await ext.treeDataProvider.showTreeItemPicker(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } - if (node.apiCenter instanceof ApiCenterVersionDefinitionManagement) { + if (node.apiCenter instanceof ApiCenterVersionDefinitionDataPlane) { return; } diff --git a/src/extension.ts b/src/extension.ts index af52b1b0..2fc917fd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -39,14 +39,13 @@ import { searchApi } from './commands/searchApi'; import { setApiRuleset } from './commands/setApiRuleset'; import { SignInToDataPlane } from "./commands/signInToDataPlane"; import { testInPostman } from './commands/testInPostman'; +import { ErrorProperties, TelemetryProperties } from './common/telemetryEvent'; import { doubleClickDebounceDelay, selectedNodeKey } from './constants'; import { ext } from './extensionVariables'; import { ApiVersionDefinitionTreeItem } from './tree/ApiVersionDefinitionTreeItem'; import { createAzureAccountTreeItem } from "./tree/AzureAccountTreeItem"; import { createAzureDataAccountTreeItem } from './tree/DataPlaneAccount'; import { OpenApiEditor } from './tree/Editors/openApi/OpenApiEditor'; -// Copilot Chat -import { ErrorProperties, TelemetryProperties } from './common/telemetryEvent'; export async function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "azure-api-center" is now active!'); @@ -60,39 +59,8 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(ext.outputChannel); registerAzureUtilsExtensionVariables(ext); - AzureSessionProviderHelper.activateAzureSessionProvider(context); - const sessionProvider = AzureSessionProviderHelper.getSessionProvider(); - - const azureAccountTreeItem = createAzureAccountTreeItem(sessionProvider); - context.subscriptions.push(azureAccountTreeItem); - ext.treeItem = azureAccountTreeItem; - // var a = ext.treeItem.subscription; - ext.dataPlaneAccounts = []; - const treeDataProvider = new AzExtTreeDataProvider(azureAccountTreeItem, "appService.loadMore"); - - ext.treeItem = azureAccountTreeItem; - - ext.treeDataProvider = treeDataProvider; - - const treeView = vscode.window.createTreeView("apiCenterTreeView", { treeDataProvider }); - context.subscriptions.push(treeView); - - AzureDataSessionProviderHelper.activateAzureSessionProvider(context); - const dataPlaneSessionProvider = AzureDataSessionProviderHelper.getSessionProvider(); - const dataPlanAccountManagerTreeItem = createAzureDataAccountTreeItem(dataPlaneSessionProvider); - context.subscriptions.push(dataPlanAccountManagerTreeItem); - ext.workspaceItem = dataPlanAccountManagerTreeItem; - - const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); - ext.workspaceProvider = workspaceTreeDataProvider; - - vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); - - treeView.onDidChangeSelection((e: vscode.TreeViewSelectionChangeEvent) => { - const selectedNode = e.selection[0]; - ext.outputChannel.appendLine(selectedNode.id!); - ext.context.globalState.update(selectedNodeKey, selectedNode.id); - }); + setupControlView(context); + setupDataTreeView(context); // Register API Center extension commands @@ -203,4 +171,34 @@ async function registerCommandWithTelemetry(commandId: string, callback: Command }, debounce); } +function setupControlView(context: vscode.ExtensionContext) { + AzureSessionProviderHelper.activateAzureSessionProvider(context); + const sessionProvider = AzureSessionProviderHelper.getSessionProvider(); + const azureAccountTreeItem = createAzureAccountTreeItem(sessionProvider); + context.subscriptions.push(azureAccountTreeItem); + ext.treeItem = azureAccountTreeItem; + const treeDataProvider = new AzExtTreeDataProvider(azureAccountTreeItem, "appService.loadMore"); + ext.treeItem = azureAccountTreeItem; + ext.treeDataProvider = treeDataProvider; + const treeView = vscode.window.createTreeView("apiCenterTreeView", { treeDataProvider }); + context.subscriptions.push(treeView); + treeView.onDidChangeSelection((e: vscode.TreeViewSelectionChangeEvent) => { + const selectedNode = e.selection[0]; + ext.outputChannel.appendLine(selectedNode.id!); + ext.context.globalState.update(selectedNodeKey, selectedNode.id); + }); +} + +function setupDataTreeView(context: vscode.ExtensionContext) { + ext.dataPlaneAccounts = []; + AzureDataSessionProviderHelper.activateAzureSessionProvider(context); + const dataPlaneSessionProvider = AzureDataSessionProviderHelper.getSessionProvider(); + const dataPlanAccountManagerTreeItem = createAzureDataAccountTreeItem(dataPlaneSessionProvider); + context.subscriptions.push(dataPlanAccountManagerTreeItem); + ext.workspaceItem = dataPlanAccountManagerTreeItem; + const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); + ext.workspaceProvider = workspaceTreeDataProvider; + vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); +} + export function deactivate() { } diff --git a/src/uiStrings.ts b/src/uiStrings.ts index b9689f20..c8dac0ac 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -124,4 +124,5 @@ export class UiStrings { static readonly AddDataPlaneTenantId = vscode.l10n.t("Input Entra App Tenant ID"); static readonly RequestFailedWithStatusCode = vscode.l10n.t("Request failed with status code: {0}"); static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("Download API Center Definition File error: {0}"); + static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); } From a04a3c4dea8f698d3e4e50bd4630c19cba18776b Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 14 Aug 2024 15:58:18 +0800 Subject: [PATCH 22/29] fix: update code according to comments --- package-lock.json | 12 ------------ package.json | 2 -- src/commands/addDataPlaneApis.ts | 2 +- .../registerApiSubCommands/registerStepByStep.ts | 4 ---- src/extension.ts | 6 +++--- src/extensionVariables.ts | 4 ++-- src/utils/generalUtils.ts | 7 +++---- 7 files changed, 9 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53f95788..9adaa908 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,6 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", - "node-fetch": "^2.6.0", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", @@ -46,7 +45,6 @@ "@types/js-yaml": "^4.0.8", "@types/mocha": "^10.0.2", "@types/node": "18.x", - "@types/node-fetch": "^2.6.11", "@types/sinon": "^17.0.3", "@types/swagger2openapi": "^7.0.4", "@types/unzipper": "^0.10.9", @@ -1559,16 +1557,6 @@ "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==", "dev": true }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@types/qs": { "version": "6.9.9", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", diff --git a/package.json b/package.json index a02d364e..cdde7736 100644 --- a/package.json +++ b/package.json @@ -489,7 +489,6 @@ "@types/js-yaml": "^4.0.8", "@types/mocha": "^10.0.2", "@types/node": "18.x", - "@types/node-fetch": "^2.6.11", "@types/sinon": "^17.0.3", "@types/swagger2openapi": "^7.0.4", "@types/unzipper": "^0.10.9", @@ -539,7 +538,6 @@ "fs-extra": "^11.1.1", "get-port": "^7.0.0", "json-schema-faker": "^0.5.6", - "node-fetch": "^2.6.0", "openapi-types": "^12.1.3", "swagger2openapi": "^7.0.8", "unzipper": "^0.12.1", diff --git a/src/commands/addDataPlaneApis.ts b/src/commands/addDataPlaneApis.ts index dac77398..edbf663c 100644 --- a/src/commands/addDataPlaneApis.ts +++ b/src/commands/addDataPlaneApis.ts @@ -20,7 +20,7 @@ export async function getDataPlaneApis(context: IActionContext): Promise(ApiCenterTreeItem.contextValue, context); node = apiCenterNode.apisTreeItem; } - if (node.apiCenter instanceof ApiCenterVersionDefinitionDataPlane) { - return; - } const apiTitle = await vscode.window.showInputBox({ title: UiStrings.ApiTitle, ignoreFocusOut: true, validateInput: validateInputForTitle }); if (!apiTitle) { diff --git a/src/extension.ts b/src/extension.ts index 2fc917fd..60405f5d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -126,7 +126,7 @@ export async function activate(context: vscode.ExtensionContext) { registerCommandWithTelemetry('azure-api-center.selectSubscriptions', AzureAccount.selectSubscriptions); registerCommandWithTelemetry('azure-api-center.openUrl', openUrlFromTreeNode); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.signInToDataPlane', SignInToDataPlane); - registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.refresh', async (context: IActionContext) => ext.workspaceItem.refresh(context)); + registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.refresh', async (context: IActionContext) => ext.dataPlaneTreeItem.refresh(context)); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.addApis', getDataPlaneApis); registerCommandWithTelemetry('azure-api-center.apiCenterWorkspace.collapse', () => { vscode.commands.executeCommand('workbench.actions.treeView.apiCenterWorkspace.collapseAll'); @@ -195,9 +195,9 @@ function setupDataTreeView(context: vscode.ExtensionContext) { const dataPlaneSessionProvider = AzureDataSessionProviderHelper.getSessionProvider(); const dataPlanAccountManagerTreeItem = createAzureDataAccountTreeItem(dataPlaneSessionProvider); context.subscriptions.push(dataPlanAccountManagerTreeItem); - ext.workspaceItem = dataPlanAccountManagerTreeItem; + ext.dataPlaneTreeItem = dataPlanAccountManagerTreeItem; const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); - ext.workspaceProvider = workspaceTreeDataProvider; + ext.dataPlaneTreeDataProvier = workspaceTreeDataProvider; vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); } diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 2212eae3..7cea47ee 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -20,6 +20,6 @@ export namespace ext { export let selectedApiVersionDefinitionTreeItem: ApiVersionDefinitionTreeItem; export let dataPlaneAccounts: DataPlaneAccount[]; - export let workspaceProvider: AzExtTreeDataProvider; - export let workspaceItem: AzExtParentTreeItem & { dispose(): unknown; }; + export let dataPlaneTreeDataProvier: AzExtTreeDataProvider; + export let dataPlaneTreeItem: AzExtParentTreeItem & { dispose(): unknown; }; } diff --git a/src/utils/generalUtils.ts b/src/utils/generalUtils.ts index bd1d5a51..ecb071c0 100644 --- a/src/utils/generalUtils.ts +++ b/src/utils/generalUtils.ts @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import fetch from 'node-fetch'; +import axios from 'axios'; import * as os from "os"; import * as path from "path"; export namespace GeneralUtils { @@ -57,9 +57,8 @@ export namespace GeneralUtils { export async function fetchDataFromLink(link: string): Promise { try { - const res = await fetch(link); - const rawData = await res.json(); - return JSON.stringify(rawData); + const res = await axios.get(link); + return JSON.stringify(res.data); } catch (err) { throw err; From 9c5a58f65beddbd6b25e20b5272c3a6e826ef91c Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 15 Aug 2024 14:01:24 +0800 Subject: [PATCH 23/29] feat: update code --- package.nls.json | 2 +- src/commands/exportApi.ts | 41 ++++++++++++++++++++++++++++++-- src/constants.ts | 5 ++++ src/tree/SubscriptionTreeItem.ts | 2 +- src/uiStrings.ts | 2 ++ src/utils/generalUtils.ts | 9 ++----- 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/package.nls.json b/package.nls.json index 6183833c..501a9245 100644 --- a/package.nls.json +++ b/package.nls.json @@ -31,5 +31,5 @@ "azure-api-center.views.api-center-treeview.controlplane.title": "Azure API Center", "azure-api-center.views.api-center-treeview.dataplane.title": "API Center Data View", "azure-api-center.commands.apiCenterWorkspace.addApis.title": "Connect to an API Center", - "azure-api-center.commands.apiCenterWorkspace.removeApis.title": "Remove API Center", + "azure-api-center.commands.apiCenterWorkspace.removeApis.title": "Remove API Center" } diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index 4ee3d28e..f022992e 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -6,9 +6,13 @@ import * as fs from "fs-extra"; import * as path from "path"; import * as vscode from "vscode"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; -import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; +import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; +import { TreeViewType } from "../constants"; import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { ApiServerItem } from "../tree/DataPlaneAccount"; +import { SubscriptionTreeItem } from "../tree/SubscriptionTreeItem"; +import { UiStrings } from "../uiStrings"; import { createTemporaryFolder } from "../utils/fsUtil"; import { GeneralUtils } from "../utils/generalUtils"; export namespace ExportAPI { @@ -16,7 +20,11 @@ export namespace ExportAPI { context: IActionContext, node?: ApiVersionDefinitionTreeItem): Promise { if (!node) { - node = await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); + let res = await getDefinitionTreeNode(context); + if (!res) { + return; + } + node = res; } const exportedSpec = await node?.apiCenterApiVersionDefinition.getDefinitions(node?.subscription!, node?.apiCenterName!, node?.apiCenterApiName!, node?.apiCenterApiVersionName!); @@ -49,4 +57,33 @@ export namespace ExportAPI { await vscode.workspace.fs.writeFile(vscode.Uri.file(localFilePath), Buffer.from(fileContent)); await vscode.window.showTextDocument(document); } + + async function getDefinitionTreeNode(context: IActionContext): Promise { + const controlViewItem = await ext.dataPlaneTreeDataProvier.getChildren(ext.treeItem) + const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); + const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); + const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); + if (!isControlPlaneExist && !isDataPlaneExist) { + vscode.window.showInformationMessage(UiStrings.GetTreeView); + return null; + } + if (!isDataPlaneExist) { + return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); + } + if (!isControlPlaneExist) { + return await ext.dataPlaneTreeDataProvier.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + } + const viewType = await vscode.window.showQuickPick(Object.values(TreeViewType), { title: UiStrings.SelectItemFromTreeView, ignoreFocusOut: true }); + if (!viewType) { + return null; + } + switch (viewType) { + case TreeViewType.controlPlaneView: + return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); + case TreeViewType.dataPlaneView: + return await ext.dataPlaneTreeDataProvier.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + default: + return null; + } + } } diff --git a/src/constants.ts b/src/constants.ts index bb6bd206..12de744e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -34,6 +34,11 @@ export const RegisterApiOptions = { cicd: UiStrings.RegisterApiOptionCicd, }; +export const TreeViewType = { + controlPlaneView: "API Center Management View", + dataPlaneView: "API Center Data Plane View", +} + export const ApiRulesetOptions = { default: UiStrings.ApiRulesetOptionDefault, azureApiGuideline: UiStrings.ApiRulesetOptionAzureApiGuideline, diff --git a/src/tree/SubscriptionTreeItem.ts b/src/tree/SubscriptionTreeItem.ts index 3163689b..70ec4449 100644 --- a/src/tree/SubscriptionTreeItem.ts +++ b/src/tree/SubscriptionTreeItem.ts @@ -14,7 +14,7 @@ export function createSubscriptionTreeItem( return new SubscriptionTreeItem(parent, subscription); } -class SubscriptionTreeItem extends AzExtParentTreeItem { +export class SubscriptionTreeItem extends AzExtParentTreeItem { public readonly subscriptionContext: ISubscriptionContext; public readonly subscriptionId: string; public static contextValue: string = "azureApiCenterAzureSubscription"; diff --git a/src/uiStrings.ts b/src/uiStrings.ts index c8dac0ac..2419955e 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -125,4 +125,6 @@ export class UiStrings { static readonly RequestFailedWithStatusCode = vscode.l10n.t("Request failed with status code: {0}"); static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("Download API Center Definition File error: {0}"); static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); + static readonly SelectItemFromTreeView = vscode.l10n.t("Select from which tree view"); + static readonly GetTreeView = vscode.l10n.t("Please Sign In to Azure to get management view or connect to data plane APIs"); } diff --git a/src/utils/generalUtils.ts b/src/utils/generalUtils.ts index ecb071c0..66c3b6ec 100644 --- a/src/utils/generalUtils.ts +++ b/src/utils/generalUtils.ts @@ -56,12 +56,7 @@ export namespace GeneralUtils { } export async function fetchDataFromLink(link: string): Promise { - try { - const res = await axios.get(link); - return JSON.stringify(res.data); - } - catch (err) { - throw err; - } + const res = await axios.get(link); + return JSON.stringify(res.data); } } From 5c8f46641d037fa301e46159ea1a211eba518632 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 15 Aug 2024 14:04:03 +0800 Subject: [PATCH 24/29] feat: update --- src/commands/exportApi.ts | 2 +- src/constants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index f022992e..a47a871b 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -59,7 +59,7 @@ export namespace ExportAPI { } async function getDefinitionTreeNode(context: IActionContext): Promise { - const controlViewItem = await ext.dataPlaneTreeDataProvier.getChildren(ext.treeItem) + const controlViewItem = await ext.dataPlaneTreeDataProvier.getChildren(ext.treeItem); const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); diff --git a/src/constants.ts b/src/constants.ts index 12de744e..1154302c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -37,7 +37,7 @@ export const RegisterApiOptions = { export const TreeViewType = { controlPlaneView: "API Center Management View", dataPlaneView: "API Center Data Plane View", -} +}; export const ApiRulesetOptions = { default: UiStrings.ApiRulesetOptionDefault, From 8246f70f90f70e646f8ea314a4f8933911a43ef9 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 16 Aug 2024 09:16:32 +0800 Subject: [PATCH 25/29] feat: update --- package.json | 1 + src/commands/exportApi.ts | 6 +++--- src/extension.ts | 2 +- src/extensionVariables.ts | 2 +- src/uiStrings.ts | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cdde7736..6b1e0f1f 100644 --- a/package.json +++ b/package.json @@ -441,6 +441,7 @@ ], "configuration": [ { + "title": "Azure-Api-Center", "properties": { "azure-api-center.selectedSubscriptions": { "type": "array", diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index a47a871b..8e65cc2d 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -59,7 +59,7 @@ export namespace ExportAPI { } async function getDefinitionTreeNode(context: IActionContext): Promise { - const controlViewItem = await ext.dataPlaneTreeDataProvier.getChildren(ext.treeItem); + const controlViewItem = await ext.dataPlaneTreeDataProvider.getChildren(ext.treeItem); const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); @@ -71,7 +71,7 @@ export namespace ExportAPI { return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); } if (!isControlPlaneExist) { - return await ext.dataPlaneTreeDataProvier.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); } const viewType = await vscode.window.showQuickPick(Object.values(TreeViewType), { title: UiStrings.SelectItemFromTreeView, ignoreFocusOut: true }); if (!viewType) { @@ -81,7 +81,7 @@ export namespace ExportAPI { case TreeViewType.controlPlaneView: return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); case TreeViewType.dataPlaneView: - return await ext.dataPlaneTreeDataProvier.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); default: return null; } diff --git a/src/extension.ts b/src/extension.ts index 60405f5d..d771f4dd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -197,7 +197,7 @@ function setupDataTreeView(context: vscode.ExtensionContext) { context.subscriptions.push(dataPlanAccountManagerTreeItem); ext.dataPlaneTreeItem = dataPlanAccountManagerTreeItem; const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); - ext.dataPlaneTreeDataProvier = workspaceTreeDataProvider; + ext.dataPlaneTreeDataProvider = workspaceTreeDataProvider; vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); } diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 7cea47ee..357bd6cd 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -20,6 +20,6 @@ export namespace ext { export let selectedApiVersionDefinitionTreeItem: ApiVersionDefinitionTreeItem; export let dataPlaneAccounts: DataPlaneAccount[]; - export let dataPlaneTreeDataProvier: AzExtTreeDataProvider; + export let dataPlaneTreeDataProvider: AzExtTreeDataProvider; export let dataPlaneTreeItem: AzExtParentTreeItem & { dispose(): unknown; }; } diff --git a/src/uiStrings.ts b/src/uiStrings.ts index 2419955e..6f98dda8 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -126,5 +126,5 @@ export class UiStrings { static readonly DownloadDefinitionFileWithErrorMsg = vscode.l10n.t("Download API Center Definition File error: {0}"); static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); static readonly SelectItemFromTreeView = vscode.l10n.t("Select from which tree view"); - static readonly GetTreeView = vscode.l10n.t("Please Sign In to Azure to get management view or connect to data plane APIs"); + static readonly GetTreeView = vscode.l10n.t("Please connect to Azure API Center Service first."); } From 8ff5a41e2cf61a6f540b6a3feb61b576b112779f Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 16 Aug 2024 13:40:02 +0800 Subject: [PATCH 26/29] fix: update --- package.json | 2 +- src/commands/exportApi.ts | 38 ++--------------------------- src/constants.ts | 4 +-- src/uiStrings.ts | 2 ++ src/utils/apiSpecificationUtils.ts | 9 +++---- src/utils/treeUtils.ts | 39 ++++++++++++++++++++++++++++-- 6 files changed, 48 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 6b1e0f1f..1e693845 100644 --- a/package.json +++ b/package.json @@ -441,7 +441,7 @@ ], "configuration": [ { - "title": "Azure-Api-Center", + "title": "Azure Api Center", "properties": { "azure-api-center.selectedSubscriptions": { "type": "array", diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index 8e65cc2d..06009ac9 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -6,21 +6,16 @@ import * as fs from "fs-extra"; import * as path from "path"; import * as vscode from "vscode"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; -import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; -import { TreeViewType } from "../constants"; -import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; -import { ApiServerItem } from "../tree/DataPlaneAccount"; -import { SubscriptionTreeItem } from "../tree/SubscriptionTreeItem"; -import { UiStrings } from "../uiStrings"; import { createTemporaryFolder } from "../utils/fsUtil"; import { GeneralUtils } from "../utils/generalUtils"; +import { treeUtils } from "../utils/treeUtils"; export namespace ExportAPI { export async function exportApi( context: IActionContext, node?: ApiVersionDefinitionTreeItem): Promise { if (!node) { - let res = await getDefinitionTreeNode(context); + let res = await treeUtils.getDefinitionTreeNode(context); if (!res) { return; } @@ -57,33 +52,4 @@ export namespace ExportAPI { await vscode.workspace.fs.writeFile(vscode.Uri.file(localFilePath), Buffer.from(fileContent)); await vscode.window.showTextDocument(document); } - - async function getDefinitionTreeNode(context: IActionContext): Promise { - const controlViewItem = await ext.dataPlaneTreeDataProvider.getChildren(ext.treeItem); - const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); - const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); - const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); - if (!isControlPlaneExist && !isDataPlaneExist) { - vscode.window.showInformationMessage(UiStrings.GetTreeView); - return null; - } - if (!isDataPlaneExist) { - return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); - } - if (!isControlPlaneExist) { - return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); - } - const viewType = await vscode.window.showQuickPick(Object.values(TreeViewType), { title: UiStrings.SelectItemFromTreeView, ignoreFocusOut: true }); - if (!viewType) { - return null; - } - switch (viewType) { - case TreeViewType.controlPlaneView: - return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); - case TreeViewType.dataPlaneView: - return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); - default: - return null; - } - } } diff --git a/src/constants.ts b/src/constants.ts index 1154302c..18ec1b9e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -35,8 +35,8 @@ export const RegisterApiOptions = { }; export const TreeViewType = { - controlPlaneView: "API Center Management View", - dataPlaneView: "API Center Data Plane View", + controlPlaneView: UiStrings.APIControlPlaneView, + dataPlaneView: UiStrings.APIDataPlaneView, }; export const ApiRulesetOptions = { diff --git a/src/uiStrings.ts b/src/uiStrings.ts index 6f98dda8..b60d7689 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -127,4 +127,6 @@ export class UiStrings { static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); static readonly SelectItemFromTreeView = vscode.l10n.t("Select from which tree view"); static readonly GetTreeView = vscode.l10n.t("Please connect to Azure API Center Service first."); + static readonly APIControlPlaneView = vscode.l10n.t("API Center Management View"); + static readonly APIDataPlaneView = vscode.l10n.t("API Center Data Plane View"); } diff --git a/src/utils/apiSpecificationUtils.ts b/src/utils/apiSpecificationUtils.ts index 179bbc03..d9dc7d08 100644 --- a/src/utils/apiSpecificationUtils.ts +++ b/src/utils/apiSpecificationUtils.ts @@ -3,10 +3,9 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -import { ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; -import { ApiSpecificationOptions, openapi } from "../constants"; -import { ext } from "../extensionVariables"; +import { ApiSpecificationOptions } from "../constants"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { treeUtils } from "../utils/treeUtils"; export async function getApiSpecification(title: string, context: IActionContext): Promise { const apiSpecificationOption = await vscode.window.showQuickPick(Object.values(ApiSpecificationOptions), { title, ignoreFocusOut: true }); @@ -16,8 +15,8 @@ export async function getApiSpecification(title: string, context: IActionContext switch (apiSpecificationOption) { case ApiSpecificationOptions.apiCenter: - const node = await ext.treeDataProvider.showTreeItemPicker(`${ApiCenterVersionDefinitionManagement.contextValue}-${openapi}`, context); - return node; + const node = await treeUtils.getDefinitionTreeNode(context); + return node ? node : undefined; case ApiSpecificationOptions.localFile: const fileUri = await vscode.window.showOpenDialog(); return fileUri?.[0]; diff --git a/src/utils/treeUtils.ts b/src/utils/treeUtils.ts index 0c533621..daa68141 100644 --- a/src/utils/treeUtils.ts +++ b/src/utils/treeUtils.ts @@ -1,9 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { TreeItemIconPath } from '@microsoft/vscode-azext-utils'; +import { IActionContext, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; +import { window } from "vscode"; +import { ApiCenterVersionDefinitionDataPlane, ApiCenterVersionDefinitionManagement } from "../azure/ApiCenterDefines/ApiCenterDefinition"; +import { TreeViewType } from "../constants"; import { ext } from '../extensionVariables'; - +import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; +import { ApiServerItem } from "../tree/DataPlaneAccount"; +import { SubscriptionTreeItem } from "../tree/SubscriptionTreeItem"; +import { UiStrings } from "../uiStrings"; export namespace treeUtils { export function getIconPath(iconName: string): TreeItemIconPath { return path.join(getResourcesPath(), `${iconName}.svg`); @@ -19,4 +25,33 @@ export namespace treeUtils { function getResourcesPath(): string { return ext.context.asAbsolutePath('resources'); } + + export async function getDefinitionTreeNode(context: IActionContext): Promise { + const controlViewItem = await ext.dataPlaneTreeDataProvider.getChildren(ext.treeItem); + const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); + const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); + const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); + if (!isControlPlaneExist && !isDataPlaneExist) { + window.showInformationMessage(UiStrings.GetTreeView); + return null; + } + if (!isDataPlaneExist) { + return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); + } + if (!isControlPlaneExist) { + return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + } + const viewType = await window.showQuickPick(Object.values(TreeViewType), { title: UiStrings.SelectItemFromTreeView, ignoreFocusOut: true }); + if (!viewType) { + return null; + } + switch (viewType) { + case TreeViewType.controlPlaneView: + return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); + case TreeViewType.dataPlaneView: + return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); + default: + return null; + } + } } From 70dee3eabd2d30d11dac53adfca0790c05ca7ff1 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 22 Aug 2024 10:09:40 +0800 Subject: [PATCH 27/29] test: fix comments and test cases --- src/commands/exportApi.ts | 10 ++++------ src/test/e2e/tests/validateAzureTreeView.test.ts | 2 +- src/uiStrings.ts | 4 ++-- src/utils/treeUtils.ts | 8 ++++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/commands/exportApi.ts b/src/commands/exportApi.ts index 06009ac9..25c3f422 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -15,13 +15,11 @@ export namespace ExportAPI { context: IActionContext, node?: ApiVersionDefinitionTreeItem): Promise { if (!node) { - let res = await treeUtils.getDefinitionTreeNode(context); - if (!res) { - return; - } - node = res; + node = await treeUtils.getDefinitionTreeNode(context); + } + if (!node) { + return; } - const exportedSpec = await node?.apiCenterApiVersionDefinition.getDefinitions(node?.subscription!, node?.apiCenterName!, node?.apiCenterApiName!, node?.apiCenterApiVersionName!); await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); } diff --git a/src/test/e2e/tests/validateAzureTreeView.test.ts b/src/test/e2e/tests/validateAzureTreeView.test.ts index e17138c3..3e4c8143 100644 --- a/src/test/e2e/tests/validateAzureTreeView.test.ts +++ b/src/test/e2e/tests/validateAzureTreeView.test.ts @@ -34,7 +34,7 @@ test('validate azure tree view', async ({ workbox }) => { expect(await VscodeOperator.isTreeItemExist(workbox, "openapi")).toBeTruthy(); //Refresh tree and verify - await VscodeOperator.clickToolbarItem(workbox, "API Center actions"); + await VscodeOperator.clickToolbarItem(workbox, "Azure API Center actions"); await workbox.waitForTimeout(Timeout.PREPARE_EXT); expect(await VscodeOperator.isTreeItemExist(workbox, "Teams Cloud - E2E Testing with TTL = 1 Days")).toBeTruthy(); expect(await VscodeOperator.isTreeItemExist(workbox, "openapi")).toBeTruthy(); diff --git a/src/uiStrings.ts b/src/uiStrings.ts index d48432d8..da5a5c1c 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -129,6 +129,6 @@ export class UiStrings { static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); static readonly SelectItemFromTreeView = vscode.l10n.t("Select from which tree view"); static readonly GetTreeView = vscode.l10n.t("Please connect to Azure API Center Service first."); - static readonly APIControlPlaneView = vscode.l10n.t("API Center Management View"); - static readonly APIDataPlaneView = vscode.l10n.t("API Center Data Plane View"); + static readonly APIControlPlaneView = vscode.l10n.t("API Center Management"); + static readonly APIDataPlaneView = vscode.l10n.t("API Center Data Plane"); } diff --git a/src/utils/treeUtils.ts b/src/utils/treeUtils.ts index daa68141..6b29613e 100644 --- a/src/utils/treeUtils.ts +++ b/src/utils/treeUtils.ts @@ -26,14 +26,14 @@ export namespace treeUtils { return ext.context.asAbsolutePath('resources'); } - export async function getDefinitionTreeNode(context: IActionContext): Promise { + export async function getDefinitionTreeNode(context: IActionContext): Promise { const controlViewItem = await ext.dataPlaneTreeDataProvider.getChildren(ext.treeItem); const isControlPlaneExist = controlViewItem.some(item => item.contextValue === SubscriptionTreeItem.contextValue); const dataViewItem = await ext.treeDataProvider.getChildren(ext.dataPlaneTreeItem); const isDataPlaneExist = dataViewItem.some(item => item.contextValue === ApiServerItem.contextValue); if (!isControlPlaneExist && !isDataPlaneExist) { window.showInformationMessage(UiStrings.GetTreeView); - return null; + return undefined; } if (!isDataPlaneExist) { return await ext.treeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionManagement.contextValue}*`), context); @@ -43,7 +43,7 @@ export namespace treeUtils { } const viewType = await window.showQuickPick(Object.values(TreeViewType), { title: UiStrings.SelectItemFromTreeView, ignoreFocusOut: true }); if (!viewType) { - return null; + return undefined; } switch (viewType) { case TreeViewType.controlPlaneView: @@ -51,7 +51,7 @@ export namespace treeUtils { case TreeViewType.dataPlaneView: return await ext.dataPlaneTreeDataProvider.showTreeItemPicker(new RegExp(`${ApiCenterVersionDefinitionDataPlane.contextValue}*`), context); default: - return null; + return undefined; } } } From be0ea57a6449caf158d2c3516611d68afba3907f Mon Sep 17 00:00:00 2001 From: wenyutang Date: Thu, 22 Aug 2024 10:11:31 +0800 Subject: [PATCH 28/29] test: update --- src/uiStrings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uiStrings.ts b/src/uiStrings.ts index da5a5c1c..0997bcc5 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -129,6 +129,6 @@ export class UiStrings { static readonly DatplaneAlreadyAdded = vscode.l10n.t("This Data Plane Runtime URL already added to Data View."); static readonly SelectItemFromTreeView = vscode.l10n.t("Select from which tree view"); static readonly GetTreeView = vscode.l10n.t("Please connect to Azure API Center Service first."); - static readonly APIControlPlaneView = vscode.l10n.t("API Center Management"); + static readonly APIControlPlaneView = vscode.l10n.t("API Center Management Plane"); static readonly APIDataPlaneView = vscode.l10n.t("API Center Data Plane"); } From b715df500575a615909d84c0d461f7b7ff1e91a2 Mon Sep 17 00:00:00 2001 From: wenyutang Date: Fri, 23 Aug 2024 09:05:40 +0800 Subject: [PATCH 29/29] fix: update the uistring --- package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.json b/package.nls.json index 501a9245..ec30e417 100644 --- a/package.nls.json +++ b/package.nls.json @@ -31,5 +31,5 @@ "azure-api-center.views.api-center-treeview.controlplane.title": "Azure API Center", "azure-api-center.views.api-center-treeview.dataplane.title": "API Center Data View", "azure-api-center.commands.apiCenterWorkspace.addApis.title": "Connect to an API Center", - "azure-api-center.commands.apiCenterWorkspace.removeApis.title": "Remove API Center" + "azure-api-center.commands.apiCenterWorkspace.removeApis.title": "Disconnect from API Center" }