diff --git a/package-lock.json b/package-lock.json index ac5ce0d2..1a7c5c07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1641,9 +1641,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 ad34d65c..bb07aeda 100644 --- a/package.json +++ b/package.json @@ -164,15 +164,44 @@ "command": "azure-api-center.selectTenant", "title": "%azure-api-center.commands.selectTenant.title%", "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.addApis", + "title": "%azure-api-center.commands.apiCenterWorkspace.addApis.title%", + "icon": "$(add)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.refresh", + "title": "%azure-api-center.commands.apiCenterTreeView.refresh.title%", + "icon": "$(refresh)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.collapse", + "title": "%azure-api-center.commands.apiCenterWorkspace.collapse.title%", + "icon": "$(collapse-all)", + "category": "Azure API Center" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "title": "%azure-api-center.commands.apiCenterWorkspace.removeApis.title%", + "icon": "$(close)", + "category": "Azure API Center" } ], "views": { "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": "%azure-api-center.views.api-center-treeview.dataplane.title%", + "visibility": "visible" } ] }, @@ -191,6 +220,21 @@ "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.collapse", + "when": "view == apiCenterWorkspace", + "group": "navigation@3" } ], "view/item/context": [ @@ -201,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", @@ -209,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", @@ -221,7 +265,7 @@ }, { "command": "azure-api-center.exportApi", - "when": "view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/" + "when": "(view == apiCenterTreeView && viewItem =~ /azureApiCenterApiVersionDefinitionTreeItem/) || (view == apiCenterWorkspace && viewItem =~ /azureApiCenterApiVersionDataPlaneDefinitionTreeItem/)" }, { "command": "azure-api-center.showOpenApi", @@ -233,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", @@ -277,6 +321,11 @@ "command": "azure-api-center.deleteCustomFunction", "when": "view == apiCenterTreeView && viewItem == azureApiCenterFunction", "group": "function@1" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "when": "view == apiCenterWorkspace && viewItem == azureApiCenterDataPlane", + "group": "inline@0" } ], "copilot": [ @@ -358,6 +407,14 @@ { "command": "azure-api-center.deleteCustomFunction", "when": "never" + }, + { + "command": "azure-api-center.apiCenterWorkspace.removeApi", + "when": "never" + }, + { + "command": "azure-api-center.apiCenterWorkspace.collapse", + "when": "never" } ] }, @@ -384,6 +441,7 @@ ], "configuration": [ { + "title": "Azure Api Center", "properties": { "azure-api-center.selectedSubscriptions": { "type": "array", diff --git a/package.nls.json b/package.nls.json index eb7c97f8..ec30e417 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": "Disconnect from API Center" } diff --git a/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts new file mode 100644 index 00000000..078dc39f --- /dev/null +++ b/src/azure/ApiCenter/ApiCenterDataPlaneAPIs.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +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; +} +export class ApiCenterDataPlaneService { + private susbcriptionContext: ISubscriptionContext; + constructor(susbcriptionContext: ISubscriptionContext) { + this.susbcriptionContext = susbcriptionContext; + }; + 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; + }; + 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; + }; + 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 client.sendRequest(options); + return response.parsedBody; + }; + 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 1bebb8e4..d8850664 100644 --- a/src/azure/ApiCenter/ApiCenterRestAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterRestAPIs.ts @@ -22,3 +22,13 @@ 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/analyzerConfigs/spectral-openapi/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/analyzerConfigs/spectral-openapi/exportRuleset?api-version=${restApiVersion}`, }; + +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/contracts.ts b/src/azure/ApiCenter/contracts.ts index 28120d79..be0c61e5 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +export type GeneralApiCenter = ApiCenter | DataPlaneApiCenter; export type ApiCenter = { id: string; @@ -12,6 +13,11 @@ export type ApiCenter = { type: string; }; +export type DataPlaneApiCenter = { + name: string; +}; + +export type GeneralApiCenterApi = ApiCenterApi | DataPlaneApiCenterApi; export type ApiCenterApi = { id: string; @@ -25,6 +31,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 +51,8 @@ export type ApiCenterEnvironment = { type: string; }; +export type GeneralApiCenterApiVersion = ApiCenterApiVersion | DataPlaneApiCenterApiVersion; + export type ApiCenterApiVersion = { id: string; location: string; @@ -47,6 +65,12 @@ export type ApiCenterApiVersion = { type: string; }; +export type DataPlaneApiCenterApiVersion = { + name: string; + title: string; + lifecycleStage: string; +}; + export type ApiCenterApiDeployment = { id: string; location: string; @@ -57,6 +81,8 @@ export type ApiCenterApiDeployment = { type: string; }; +export type GeneralApiCenterApiVersionDefinition = ApiCenterApiVersionDefinition | DataPlaneApiCenterApiVersionDefinition; + export type ApiCenterApiVersionDefinition = { id: string; location: string; @@ -76,6 +102,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/ApiCenterDefines/ApiCenterApi.ts b/src/azure/ApiCenterDefines/ApiCenterApi.ts new file mode 100644 index 00000000..8e64d7ff --- /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(private 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; + getLabel: () => string; + generateChild: () => IVersionsBase; +}; + +export class ApiCenterApiManagement implements IApiCenterApiBase { + constructor(private 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; + } + getLabel(): string { + return this.data.properties.title; + } + generateChild(): IVersionsBase { + return new ApiCenterVersionsManagement(this.data); + } +}; + +export class ApiCenterApiDataPlane implements IApiCenterApiBase { + constructor(private data: DataPlaneApiCenterApi) { } + getLabel(): 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..807d43a1 --- /dev/null +++ b/src/azure/ApiCenterDefines/ApiCenterDefinition.ts @@ -0,0 +1,122 @@ +// 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(private 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(private 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(private 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(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); + 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..4967b9de --- /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(private 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(private 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 = { + getLabel: () => string, + getId: () => string, + getName: () => string, + generateChild: () => IDefinitionsBase; +}; + +export class ApiCenterVersionManagement implements IVersionBase { + constructor(private data: ApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + generateChild(): IDefinitionsBase { + return new ApiCenterVersionDefinitionsManagement(this.data); + } + getLabel() { + return this.data.properties.title; + } + getId() { + return this.data.id; + } +}; + +export class ApiCenterVersionDataplane implements IVersionBase { + constructor(private data: DataPlaneApiCenterApiVersion) { } + getName(): string { + return this.data.name; + } + generateChild(): IDefinitionsBase { + return new ApiCenterVersionDefinitionsDataplane(this.data); + } + getLabel() { + return this.data.name; + } + getId() { + return this.data.name; + } +}; diff --git a/src/azure/ResourceGraph/ResourceGraphService.ts b/src/azure/ResourceGraph/ResourceGraphService.ts index 5f518389..7ebbfb2c 100644 --- a/src/azure/ResourceGraph/ResourceGraphService.ts +++ b/src/azure/ResourceGraph/ResourceGraphService.ts @@ -5,7 +5,7 @@ import { ResourceGraphClient } from "@azure/arm-resourcegraph"; import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { clientOptions } from "../../common/clientOptions"; 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 ed55b191..ab42b3d3 100644 --- a/src/azure/azureLogin/authTypes.ts +++ b/src/azure/azureLogin/authTypes.ts @@ -50,9 +50,15 @@ export type AzureSessionProvider = { dispose(): void; }; +export type AzureDataSessionProvider = { + signIn(_scopes: string[]): Promise; + signInStatus: SignInStatus; + signInStatusChangeEvent: Event; + getAuthSession(scopes?: string[]): 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..c478c3e7 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/dataSessionProvider.ts b/src/azure/azureLogin/dataSessionProvider.ts new file mode 100644 index 00000000..1f276a0e --- /dev/null +++ b/src/azure/azureLogin/dataSessionProvider.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { + authentication, + AuthenticationGetSessionOptions, + AuthenticationSession, + Event, + EventEmitter, + ExtensionContext, + Disposable as VsCodeDisposable +} from "vscode"; +import { UiStrings } from "../../uiStrings"; +import { GeneralUtils } from "../../utils/generalUtils"; +import { AzureAuthenticationSession, AzureDataSessionProvider, SignInStatus } from "./authTypes"; +import { AzureAuth } from "./azureAuth"; +enum AuthScenario { + Initialization, + SignIn, + GetSession +} + +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 { + public static MicrosoftAuthProviderId: string = 'microsoft'; + 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 { + if (_scopes.length !== 0) { + await this.getArmSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, _scopes, authScenario); + } + this.onSignInStatusChangeEmitter.fire(this.signInStatusValue); + } + public get signInStatus(): SignInStatus { + return this.signInStatusValue; + } + + public async getAuthSession(scopes?: string[]): Promise> { + return await this.getArmSession(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, 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; + } + + 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(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes, options); + break; + case AuthScenario.SignIn: + options = { createIfNone: true, clearSessionPreference: true, silent: false }; + 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(AzureDataSessionProviderImpl.MicrosoftAuthProviderId, scopes, { silent: true }); + break; + } + if (!session) { + return { succeeded: false, error: UiStrings.NoAzureSessionFound }; + } + 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..edbf663c --- /dev/null +++ b/src/commands/addDataPlaneApis.ts @@ -0,0 +1,34 @@ +// 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"; +import { UiStrings } from "../uiStrings"; +export async function getDataPlaneApis(context: IActionContext): Promise { + const endpointUrl = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneRuntimeUrl, ignoreFocusOut: true }); + if (!endpointUrl) { + return; + } + const clientid = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneClientId, ignoreFocusOut: true }); + if (!clientid) { + return; + } + const tenantid = await vscode.window.showInputBox({ title: UiStrings.AddDataPlaneTenantId, ignoreFocusOut: true }); + if (!tenantid) { + return; + } + + setAccountToExt(endpointUrl, clientid, tenantid); + ext.dataPlaneTreeItem.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); + } else { + vscode.window.showInformationMessage(UiStrings.DatplaneAlreadyAdded); + } + } + pushIfNotExist(ext.dataPlaneAccounts, { domain: domain, tenantId: tenantId, clientId: clientId }); +} diff --git a/src/commands/editOpenApi.ts b/src/commands/editOpenApi.ts index adc2fce8..c112432f 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/ApiCenterDefines/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 caf3fedb..25c3f422 100644 --- a/src/commands/exportApi.ts +++ b/src/commands/exportApi.ts @@ -1,32 +1,26 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as fs from "fs-extra"; + import * as path from "path"; import * as vscode from "vscode"; -import { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; import { ApiSpecExportResultFormat } from "../azure/ApiCenter/contracts"; -import { TelemetryClient } from '../common/telemetryClient'; -import { ext } from "../extensionVariables"; import { ApiVersionDefinitionTreeItem } from "../tree/ApiVersionDefinitionTreeItem"; import { createTemporaryFolder } from "../utils/fsUtil"; +import { GeneralUtils } from "../utils/generalUtils"; +import { treeUtils } from "../utils/treeUtils"; 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 treeUtils.getDefinitionTreeNode(context); } - - const apiCenterService = new ApiCenterService( - node?.subscription!, - getResourceGroupFromId(node?.id!), - node?.apiCenterName!); - const exportedSpec = await apiCenterService.exportSpecification( - node?.apiCenterApiName!, - node?.apiCenterApiVersionName!, - node?.apiCenterApiVersionDefinition.name!); + if (!node) { + return; + } + const exportedSpec = await node?.apiCenterApiVersionDefinition.getDefinitions(node?.subscription!, node?.apiCenterName!, node?.apiCenterApiName!, node?.apiCenterApiVersionName!); await writeToTempFile(node!, exportedSpec.format, exportedSpec.value); } @@ -35,15 +29,14 @@ 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) { if (specFormat === ApiSpecExportResultFormat.inline) { await ExportAPI.showTempFile(node, specValue); - } else { - // Currently at server side did not exist link, so just monitor this event. - TelemetryClient.sendEvent("azure-api-center.exportApi", { format: specFormat }); + } else if (specFormat === ApiSpecExportResultFormat.link) { + await ExportAPI.showTempFile(node, await GeneralUtils.fetchDataFromLink(specValue)); } } diff --git a/src/commands/handleUri.ts b/src/commands/handleUri.ts new file mode 100644 index 00000000..1cb65288 --- /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/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 f62b2008..7f93f7e8 100644 --- a/src/commands/registerApiSubCommands/registerStepByStep.ts +++ b/src/commands/registerApiSubCommands/registerStepByStep.ts @@ -60,8 +60,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/commands/removeDataplaneApi.ts b/src/commands/removeDataplaneApi.ts new file mode 100644 index 00000000..c72386d0 --- /dev/null +++ b/src/commands/removeDataplaneApi.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from "vscode"; +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.subscription.subscriptionPath! && + account.tenantId === node.subscription.tenantId! && + account.clientId === node.subscription.userId! + ); + if (indexToRemove !== -1) { + accounts.splice(indexToRemove, 1); + } + 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..0b1ca3f7 --- /dev/null +++ b/src/commands/signInToDataPlane.ts @@ -0,0 +1,9 @@ +// 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"; +export async function SignInToDataPlane(context: IActionContext, node: ApisTreeItem) { + const scopes = generateScopes(node.parent?.subscription!.userId!, node.parent?.subscription!.tenantId!); + await AzureDataSessionProviderHelper.getSessionProvider().signIn(scopes); +} diff --git a/src/constants.ts b/src/constants.ts index bb6bd206..18ec1b9e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -34,6 +34,11 @@ export const RegisterApiOptions = { cicd: UiStrings.RegisterApiOptionCicd, }; +export const TreeViewType = { + controlPlaneView: UiStrings.APIControlPlaneView, + dataPlaneView: UiStrings.APIDataPlaneView, +}; + export const ApiRulesetOptions = { default: UiStrings.ApiRulesetOptionDefault, azureApiGuideline: UiStrings.ApiRulesetOptionAzureApiGuideline, diff --git a/src/extension.ts b/src/extension.ts index db65b80c..72ea2264 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,15 +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 { 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'; import { TelemetryUtils } from './utils/telemetryUtils'; - export async function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "azure-api-center" is now active!'); @@ -55,25 +60,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); - 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); - }); + setupControlView(context); + setupDataTreeView(context); // Register API Center extension commands @@ -137,9 +125,21 @@ 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.dataPlaneTreeItem.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); + + context.subscriptions.push( + vscode.window.registerUriHandler({ + handleUri + }) + ); } async function registerCommandWithTelemetry(commandId: string, callback: CommandCallback, debounce?: number): Promise { @@ -170,4 +170,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.dataPlaneTreeItem = dataPlanAccountManagerTreeItem; + const workspaceTreeDataProvider = new AzExtTreeDataProvider(dataPlanAccountManagerTreeItem, "appService.loadMore"); + ext.dataPlaneTreeDataProvider = workspaceTreeDataProvider; + vscode.window.registerTreeDataProvider('apiCenterWorkspace', workspaceTreeDataProvider); +} + export function deactivate() { } diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 08bc6959..357bd6cd 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -2,12 +2,13 @@ // 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 { 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 namespace ext { export let prefix: string = 'azureAPICenter'; @@ -17,4 +18,8 @@ export namespace ext { export let outputChannel: IAzExtOutputChannel; export let openApiEditor: OpenApiEditor; export let selectedApiVersionDefinitionTreeItem: ApiVersionDefinitionTreeItem; + + export let dataPlaneAccounts: DataPlaneAccount[]; + export let dataPlaneTreeDataProvider: AzExtTreeDataProvider; + export let dataPlaneTreeItem: AzExtParentTreeItem & { dispose(): unknown; }; } 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/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 cfbed1ba..036b2f8f 100644 --- a/src/test/unit/commands/exportApi.test.ts +++ b/src/test/unit/commands/exportApi.test.ts @@ -2,11 +2,12 @@ // Licensed under the MIT license. import { AzExtParentTreeItem, AzExtTreeItem, IActionContext } from "@microsoft/vscode-azext-utils"; import * as sinon from "sinon"; -import { ApiCenterService } from "../../../azure/ApiCenter/ApiCenterService"; import { ApiCenterApiVersionDefinition } from "../../../azure/ApiCenter/contracts"; +import { ApiCenterVersionDefinitionManagement } from "../../../azure/ApiCenterDefines/ApiCenterDefinition"; 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 { @@ -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,13 +58,7 @@ describe("export API test cases", () => { "fakeApiCenterName", "fakeApiCenterApiName", "fakeApiCenterApiVersionName", - { - properties: { - specification: { - name: "fakeName" - } - } - } 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"); @@ -57,14 +67,15 @@ describe("export API test cases", () => { sandbox.restore(); }); it('export API happy path with link type', async () => { - const spyShowTempFile = sandbox.spy(ExportAPI, "showTempFile"); - sandbox.stub(ApiCenterService.prototype, "exportSpecification").resolves({ format: "link", value: "fakeValue" }); + let stubShowTempFile = sandbox.stub(ExportAPI, "showTempFile").resolves(); + sandbox.stub(node.apiCenterApiVersionDefinition, "getDefinitions").resolves({ format: "link", value: "fakeValue" }); + sandbox.stub(GeneralUtils, "fetchDataFromLink").resolves(); 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(); - 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/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'); + }); +}); diff --git a/src/tree/ApiCenterTreeItem.ts b/src/tree/ApiCenterTreeItem.ts index a9661a28..1b919721 100644 --- a/src/tree/ApiCenterTreeItem.ts +++ b/src/tree/ApiCenterTreeItem.ts @@ -3,13 +3,13 @@ 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 { ApiCenterApisManagement } from "../azure/ApiCenterDefines/ApiCenterApi"; import { UiStrings } from "../uiStrings"; 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 ApiCenterApisManagement(apicenter)); this.environmentsTreeItem = new EnvironmentsTreeItem(this, apicenter); } diff --git a/src/tree/ApiTreeItem.ts b/src/tree/ApiTreeItem.ts index 31653ebc..12eb32e8 100644 --- a/src/tree/ApiTreeItem.ts +++ b/src/tree/ApiTreeItem.ts @@ -2,26 +2,26 @@ // 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 { ApiCenterApiManagement, IApiCenterApiBase } from "../azure/ApiCenterDefines/ApiCenterApi"; 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: ApiCenterApi; + public readonly apiDeploymentsTreeItem?: ApiDeploymentsTreeItem; + private readonly _apiCenterApi: IApiCenterApiBase; private readonly _apiCenterName: string; - private _nextLink: string | undefined; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: ApiCenterApi) { + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IApiCenterApiBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; - this.apiVersionsTreeItem = new ApiVersionsTreeItem(this, apiCenterName, apiCenterApi); - this.apiDeploymentsTreeItem = new ApiDeploymentsTreeItem(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()); + } } public get iconPath(): TreeItemIconPath { @@ -29,18 +29,18 @@ export class ApiTreeItem extends AzExtParentTreeItem { } public get id(): string { - return this._apiCenterApi.id; + return this._apiCenterApi.getId(); } public get label(): string { - return this._apiCenterApi.properties.title; + return this._apiCenterApi.getLabel(); } public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return this._apiCenterApi.getNextLink() !== undefined; } 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..4dafd13f 100644 --- a/src/tree/ApiVersionDefinitionTreeItem.ts +++ b/src/tree/ApiVersionDefinitionTreeItem.ts @@ -2,19 +2,17 @@ // 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 { IDefinitionBase } from "../azure/ApiCenterDefines/ApiCenterDefinition"; export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { - public static contextValue: string = "azureApiCenterApiVersionDefinitionTreeItem"; - public readonly contextValue: string = ApiVersionDefinitionTreeItem.contextValue; + public readonly contextValue: string = ""; constructor( parent: AzExtParentTreeItem, public apiCenterName: string, public apiCenterApiName: string, public apiCenterApiVersionName: string, - public apiCenterApiVersionDefinition: ApiCenterApiVersionDefinition) { + public apiCenterApiVersionDefinition: IDefinitionBase) { super(parent); - this.contextValue += "-" + apiCenterApiVersionDefinition.properties.specification.name.toLowerCase(); + this.contextValue = apiCenterApiVersionDefinition.getContext(); } public get iconPath(): TreeItemIconPath { @@ -22,10 +20,10 @@ export class ApiVersionDefinitionTreeItem extends AzExtTreeItem { } public get id(): string { - return this.apiCenterApiVersionDefinition.id; + return this.apiCenterApiVersionDefinition.getId(); } public get label(): string { - return this.apiCenterApiVersionDefinition.properties.title; + return this.apiCenterApiVersionDefinition.getLabel(); } } diff --git a/src/tree/ApiVersionDefinitionsTreeItem.ts b/src/tree/ApiVersionDefinitionsTreeItem.ts index afe880ab..6a2058a4 100644 --- a/src/tree/ApiVersionDefinitionsTreeItem.ts +++ b/src/tree/ApiVersionDefinitionsTreeItem.ts @@ -1,26 +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 { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApiVersion } from "../azure/ApiCenter/contracts"; +import { IDefinitionsBase } from "../azure/ApiCenterDefines/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: ApiCenterApiVersion; - private _nextLink: string | undefined; + private readonly _apiCenterApiVersion: IDefinitionsBase; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: ApiCenterApiVersion) { + apiCenterApiVersion: IDefinitionsBase) { super(parent); this._apiCenterApiVersion = apiCenterApiVersion; this._apiCenterName = apiCenterName; @@ -36,26 +32,21 @@ 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); - - this._nextLink = definitions.nextLink; + let definitions = await this._apiCenterApiVersion.getChild(this.parent?.subscription!, this._apiCenterName, this._apiCenterApiName); return await this.createTreeItemsWithErrorHandling( - definitions.value, + definitions, 'invalidResource', - resource => new ApiVersionDefinitionTreeItem( + definition => new ApiVersionDefinitionTreeItem( this, this._apiCenterName, this._apiCenterApiName, - this._apiCenterApiVersion.name, - resource), - resource => resource.name + this._apiCenterApiVersion.getName(), + this._apiCenterApiVersion.generateChild(definition)), + definition => definition.name ); } 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 d353159c..1add6fc2 100644 --- a/src/tree/ApiVersionTreeItem.ts +++ b/src/tree/ApiVersionTreeItem.ts @@ -2,25 +2,23 @@ // 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 { IVersionBase } from "../azure/ApiCenterDefines/ApiCenterVersion"; 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: ApiCenterApiVersion; + private readonly _apiCenterApiVersion: IVersionBase; public readonly apiVersionDefinitionsTreeItem: ApiVersionDefinitionsTreeItem; - private _nextLink: string | undefined; constructor( parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApiName: string, - apiCenterApiVersion: ApiCenterApiVersion) { + 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 { @@ -28,15 +26,15 @@ export class ApiVersionTreeItem extends AzExtParentTreeItem { } public get id(): string { - return this._apiCenterApiVersion.id; + return this._apiCenterApiVersion.getId(); } public get label(): string { - return this._apiCenterApiVersion.properties.title; + return this._apiCenterApiVersion.getLabel(); } public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; + return false; } public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { diff --git a/src/tree/ApiVersionsTreeItem.ts b/src/tree/ApiVersionsTreeItem.ts index b09dead8..494b9589 100644 --- a/src/tree/ApiVersionsTreeItem.ts +++ b/src/tree/ApiVersionsTreeItem.ts @@ -1,10 +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 { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenterApi } from "../azure/ApiCenter/contracts"; +import { IVersionsBase } from "../azure/ApiCenterDefines/ApiCenterVersion"; import { UiStrings } from "../uiStrings"; import { ApiVersionTreeItem } from "./ApiVersionTreeItem"; @@ -12,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: ApiCenterApi; - constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: ApiCenterApi) { + private readonly _apiCenterApi: IVersionsBase; + constructor(parent: AzExtParentTreeItem, apiCenterName: string, apiCenterApi: IVersionsBase) { super(parent); this._apiCenterName = apiCenterName; this._apiCenterApi = apiCenterApi; @@ -30,20 +27,16 @@ 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._apiCenterApi.getChild(this.parent?.subscription!, this._apiCenterName); return await this.createTreeItemsWithErrorHandling( - apis.value, + 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 ); } 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 d9ef7ce6..b5112efe 100644 --- a/src/tree/ApisTreeItem.ts +++ b/src/tree/ApisTreeItem.ts @@ -1,20 +1,17 @@ // 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 { ApiCenterService } from "../azure/ApiCenter/ApiCenterService"; -import { ApiCenter } from "../azure/ApiCenter/contracts"; +import { IApiCenterApisBase } from "../azure/ApiCenterDefines/ApiCenterApi"; 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: IApiCenterApisBase) { super(parent); } @@ -40,15 +37,11 @@ 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.apiCenter.getChild(this.parent?.subscription!, this.searchContent); return await this.createTreeItemsWithErrorHandling( - apis.value, + apis, 'invalidResource', - resource => new ApiTreeItem(this, this.apiCenter.name, resource), + resource => new ApiTreeItem(this, this.apiCenter.getName(), this.apiCenter.generateChild(resource)), resource => resource.name ); } diff --git a/src/tree/DataPlaneAccount.ts b/src/tree/DataPlaneAccount.ts new file mode 100644 index 00000000..53331185 --- /dev/null +++ b/src/tree/DataPlaneAccount.ts @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +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 { ApiCenterApisDataplane } from "../azure/ApiCenterDefines/ApiCenterApi"; +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; + constructor(private readonly sessionProvider: AzureDataSessionProvider) { + super(undefined); + this.autoSelectInTreeItemPicker = true; + + 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, getSubscriptionContext(account)), + account => account.domain.split('0')[0] + ); + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + public static contextValue: string = "azureApiCenterDataPlaneView"; + public get iconPath(): TreeItemIconPath { + return new vscode.ThemeIcon("versions"); + } + + public get label(): string { + return "dataPlaneAccount"; + } + +} + +export class ApiServerItem extends AzExtParentTreeItem { + public label: string; + 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; + } + get subscription(): ISubscriptionContext { + return this.subscriptionContext; + } + constructor(parent: AzExtParentTreeItem, subContext: ISubscriptionContext) { + super(parent); + this.label = subContext.subscriptionPath.split('.')[0]; + this.subscriptionContext = subContext; + this.apisTreeItem = new ApisTreeItem(this, new ApiCenterApisDataplane({ name: this.label })); + } + public get id(): string { + return this.label; + } + public contextValue: string = ApiServerItem.contextValue; + public static contextValue: string = "azureApiCenterDataPlane"; + public get iconPath(): TreeItemIconPath { + return treeUtils.getIconPath('apiCenter'); + } +} + +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/src/tree/Editors/openApi/OpenApiEditor.ts b/src/tree/Editors/openApi/OpenApiEditor.ts index e1071587..e18e2caa 100644 --- a/src/tree/Editors/openApi/OpenApiEditor.ts +++ b/src/tree/Editors/openApi/OpenApiEditor.ts @@ -3,30 +3,25 @@ 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); } public async getData(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.name! - ); - - return exportedSpec.value; + const exportedSpec = await treeItem.apiCenterApiVersionDefinition.getDefinitions(treeItem?.subscription!, treeItem?.apiCenterName!, treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!); + 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 { @@ -57,7 +52,7 @@ export class OpenApiEditor extends Editor { await apiCenterService.importSpecification( treeItem?.apiCenterApiName!, treeItem?.apiCenterApiVersionName!, - treeItem?.apiCenterApiVersionDefinition.name!, + treeItem?.apiCenterApiVersionDefinition.getName(), importPayload ); } @@ -67,15 +62,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/tree/SubscriptionTreeItem.ts b/src/tree/SubscriptionTreeItem.ts index 020883f1..481605ba 100644 --- a/src/tree/SubscriptionTreeItem.ts +++ b/src/tree/SubscriptionTreeItem.ts @@ -15,7 +15,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 db18ecf5..0997bcc5 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -121,4 +121,14 @@ 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 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 connect to Azure API Center Service first."); + static readonly APIControlPlaneView = vscode.l10n.t("API Center Management Plane"); + static readonly APIDataPlaneView = vscode.l10n.t("API Center Data Plane"); } diff --git a/src/utils/apiSpecificationUtils.ts b/src/utils/apiSpecificationUtils.ts index 6f4df8b9..d9dc7d08 100644 --- a/src/utils/apiSpecificationUtils.ts +++ b/src/utils/apiSpecificationUtils.ts @@ -3,9 +3,9 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; -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 }); @@ -15,8 +15,8 @@ export async function getApiSpecification(title: string, context: IActionContext switch (apiSpecificationOption) { case ApiSpecificationOptions.apiCenter: - const node = await ext.treeDataProvider.showTreeItemPicker(`${ApiVersionDefinitionTreeItem.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/generalUtils.ts b/src/utils/generalUtils.ts index e8f9af28..66c3b6ec 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 axios from 'axios'; import * as os from "os"; import * as path from "path"; - export namespace GeneralUtils { const apiCenterFolder = ".api-center"; @@ -54,4 +54,9 @@ export namespace GeneralUtils { export function getApiCenterWorkspacePath(): string { return path.join(os.homedir(), apiCenterFolder); } + + export async function fetchDataFromLink(link: string): Promise { + const res = await axios.get(link); + return JSON.stringify(res.data); + } } diff --git a/src/utils/treeUtils.ts b/src/utils/treeUtils.ts index 0c533621..6b29613e 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 undefined; + } + 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 undefined; + } + 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 undefined; + } + } }