From 3f5a22baea3dd8dd18136544e040805560596fd6 Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Thu, 13 May 2021 22:10:52 -0700 Subject: [PATCH 01/11] temp commit --- src/commands/createGraphqlApi.ts | 43 ++++++++++++++++++++++++++++++++ src/explorer/ApisTreeItem.ts | 15 +++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/commands/createGraphqlApi.ts diff --git a/src/commands/createGraphqlApi.ts b/src/commands/createGraphqlApi.ts new file mode 100644 index 0000000..ccdb9f4 --- /dev/null +++ b/src/commands/createGraphqlApi.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//import { ServiceClient } from "@azure/ms-rest-js"; +import { createGenericClient, IActionContext } from "../../extension.bundle"; +import { ApisTreeItem, IApiTreeItemContext } from "../explorer/ApisTreeItem"; +import { ServiceTreeItem } from "../explorer/ServiceTreeItem"; +import { ext } from "../extensionVariables"; +import { localize } from "../localize"; + +// tslint:disable-next-line: export-name +export async function createGraphqlApi(context: IActionContext & Partial, node?: ApisTreeItem): Promise { + if (!node) { + const serviceNode = await ext.tree.showTreeItemPicker(ServiceTreeItem.contextValue, context); + node = serviceNode.apisTreeItem; + } + + + await askLink(); + await createGenericClient(node.root.credentials); + + +} + +async function askLink() : Promise { + const promptStr: string = localize('apiLinkPrompt', 'Specify a Graphql OpenAPI link.'); + return (await ext.ui.showInputBox({ + prompt: promptStr, + placeHolder: 'https://', + validateInput: async (value: string): Promise => { + value = value ? value.trim() : ''; + const regexp = /http(s?):\/\/[\d\w][\d\w]*(\.[\d\w][\d\w-]*)*(:\d+)?(\/[\d\w-\.\?,'/\\\+&=:%\$#_]*)?/; + const isUrlValid = regexp.test(value); + if (!isUrlValid) { + return localize("invalidOpenApiLink", "Provide a valid link. example - https://petstore.swagger.io/v2/swagger.json"); + } else { + return undefined; + } + } + })).trim(); +} diff --git a/src/explorer/ApisTreeItem.ts b/src/explorer/ApisTreeItem.ts index 7a36794..5a2856a 100644 --- a/src/explorer/ApisTreeItem.ts +++ b/src/explorer/ApisTreeItem.ts @@ -5,8 +5,8 @@ import { ApiManagementModels } from "@azure/arm-apimanagement"; import { ApiContract, ApiCreateOrUpdateParameter } from "@azure/arm-apimanagement/src/models"; -import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui"; -import { topItemCount } from "../constants"; +import { ServiceClient } from "@azure/ms-rest-js"; +import { AzExtTreeItem, AzureParentTreeItem, createGenericClient, ICreateChildImplContext } from "vscode-azureextensionui"; import { localize } from "../localize"; import { IOpenApiImportObject } from "../openApi/OpenApiImportObject"; import { apiUtil } from "../utils/apiUtil"; @@ -46,12 +46,23 @@ export class ApisTreeItem extends AzureParentTreeItem { let apisToLoad : ApiContract[] = this.selectedApis; if (this.selectedApis.length === 0) { + /* const apiCollection: ApiManagementModels.ApiCollection = this._nextLink === undefined ? await this.root.client.api.listByService(this.root.resourceGroupName, this.root.serviceName, { expandApiVersionSet: true, top: topItemCount }) : await this.root.client.api.listByServiceNext(this._nextLink); this._nextLink = apiCollection.nextLink; + apisToLoad = apiCollection.map((s) => s).filter(s => apiUtil.isNotApiRevision(s));*/ + const client: ServiceClient = await createGenericClient(this.root.credentials); + // tslint:disable-next-line: no-unsafe-any + const apiCollection: ApiManagementModels.ApiCollection = (await client.sendRequest({ + method: "GET", + url: `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`, + headers: { + Authorization: "SharedAccessSignature integration&202106122332&mkR/KwuXqDeXgsCEN6h8H/bTu5Btks+UnvTAl+rez9cv7Iq3BfRY/z0Wm2HHt8/SUc1kHDrvTNCafH7VkcVVeg==" + } + })).parsedBody; apisToLoad = apiCollection.map((s) => s).filter(s => apiUtil.isNotApiRevision(s)); } From 935cf51250aeafb64e49a7ae87eb2e05d603d80c Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Wed, 19 May 2021 17:14:23 -0700 Subject: [PATCH 02/11] Add support for graphql API --- package.json | 17 ++++ package.nls.json | 2 + src/azure/apim/TempApiContract.ts | 25 ++++++ src/commands/createGraphqlApi.ts | 46 +++++++++- src/constants.ts | 2 + src/explorer/ApiTreeItem.ts | 14 +-- src/explorer/ApisTreeItem.ts | 88 ++++++++++++++++--- src/explorer/GraphqlApiTreeItem.ts | 71 +++++++++++++++ src/explorer/IApiTreeRoot.ts | 1 + src/explorer/editors/arm/ApiResourceEditor.ts | 34 ++++++- .../editors/policy/ApiPolicyEditor.ts | 39 +++++++- src/extension.ts | 2 + src/utils/apiUtil.ts | 7 ++ 13 files changed, 323 insertions(+), 25 deletions(-) create mode 100644 src/azure/apim/TempApiContract.ts create mode 100644 src/explorer/GraphqlApiTreeItem.ts diff --git a/package.json b/package.json index 6ee1a00..3ba9016 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,12 @@ "onCommand:azureApiManagement.deleteOperation", "onCommand:azureApiManagement.importOpenApiByFile", "onCommand:azureApiManagement.importOpenApiByLink", + "onCommand:azureApiManagement.importGraphqlAPIByLink", "onCommand:azureApiManagement.testOperation", "onCommand:azureApiManagement.openInPortal", "onCommand:azureApiManagement.showApi", "onCommand:azureApiManagement.showArmApi", + "onCommand:azureApiManagement.showArmGraphqlApi", "onCommand:azureApiManagement.showArmApiOperation", "onCommand:azureApiManagement.showArmProduct", "onCommand:azureApiManagement.showServicePolicy", @@ -169,6 +171,11 @@ "title": "%azureApiManagement.importOpenApiByLink%", "category": "Azure API Management" }, + { + "command": "azureApiManagement.importGraphqlAPIByLink", + "title": "%azureApiManagement.importGraphqlAPIByLink%", + "category": "Azure API Management" + }, { "command": "azureApiManagement.testOperation", "title": "%azureApiManagement.testOperation%", @@ -215,6 +222,11 @@ "title": "%azureApiManagement.showArmApi%", "category": "Azure API Management" }, + { + "command": "azureApiManagement.showArmGraphqlApi", + "title": "%azureApiManagement.showArmGraphqlApi%", + "category": "Azure API Management" + }, { "command": "azureApiManagement.showArmApiOperation", "title": "%azureApiManagement.showArmApiOperation%", @@ -491,6 +503,11 @@ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementApis", "group": "1@2" }, + { + "command": "azureApiManagement.importGraphqlAPIByLink", + "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementApis", + "group": "1@3" + }, { "command": "azureApiManagement.importFunctionApp", "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementApis", diff --git a/package.nls.json b/package.nls.json index a623e6a..b579b05 100644 --- a/package.nls.json +++ b/package.nls.json @@ -2,6 +2,7 @@ "azureApiManagement.openInPortal": "Open in Portal", "azureApiManagement.importOpenApiByFile": "Import from OpenAPI file", "azureApiManagement.importOpenApiByLink": "Import from OpenAPI link", + "azureApiManagement.importGraphqlAPIByLink": "Import Graphql API from OpenAPI link", "azureApiManagement.showExplorer": "Show or hide the Azure API Management Explorer.", "azureApiManagement.showSavePrompt": "Show warning dialog on remote file uploading.", "azureApiManagement.createService": "Create API Management in Azure", @@ -9,6 +10,7 @@ "azureApiManagement.copySubscriptionKey" : "Copy Subscription Key", "azureApiManagement.showApi": "Edit OpenAPI", "azureApiManagement.showArmApi": "Edit API", + "azureApiManagement.showArmGraphqlApi": "Edit Graphql API", "azureApiManagement.showArmApiOperation": "Edit Operation", "azureApiManagement.showServicePolicy": "Edit Global policy", "azureApiManagement.showApiPolicy": "Edit API policy", diff --git a/src/azure/apim/TempApiContract.ts b/src/azure/apim/TempApiContract.ts new file mode 100644 index 0000000..35fa51c --- /dev/null +++ b/src/azure/apim/TempApiContract.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AuthenticationSettingsContract, Protocol, SubscriptionKeyParameterNamesContract } from "@azure/arm-apimanagement/src/models"; + +export interface IApiContract { + id: string; + name: string; + properties: { + displayName: string; + apiRevision: string; + authenticationSettings?: AuthenticationSettingsContract; + description: string; + isCurrent: boolean; + path: string; + protocols?: Protocol[]; + serviceUrl: string; + subscriptionKeyParameterNames?: SubscriptionKeyParameterNamesContract; + subscriptionRequired?: boolean; + // tslint:disable-next-line: no-reserved-keywords + type: string; + }; +} diff --git a/src/commands/createGraphqlApi.ts b/src/commands/createGraphqlApi.ts index ccdb9f4..0d04789 100644 --- a/src/commands/createGraphqlApi.ts +++ b/src/commands/createGraphqlApi.ts @@ -4,11 +4,15 @@ *--------------------------------------------------------------------------------------------*/ //import { ServiceClient } from "@azure/ms-rest-js"; -import { createGenericClient, IActionContext } from "../../extension.bundle"; +import requestPromise from "request-promise"; +import { ProgressLocation, window } from "vscode"; +import { IActionContext } from "../../extension.bundle"; +import { SharedAccessToken } from "../constants"; import { ApisTreeItem, IApiTreeItemContext } from "../explorer/ApisTreeItem"; import { ServiceTreeItem } from "../explorer/ServiceTreeItem"; import { ext } from "../extensionVariables"; import { localize } from "../localize"; +import { apiUtil } from "../utils/apiUtil"; // tslint:disable-next-line: export-name export async function createGraphqlApi(context: IActionContext & Partial, node?: ApisTreeItem): Promise { @@ -17,10 +21,44 @@ export async function createGraphqlApi(context: IActionContext & Partial { + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "PUT", + headers: { + Authorization: SharedAccessToken + }, + body: JSON.stringify(body) + }; + // tslint:disable-next-line: no-non-null-assertion + await >requestPromise(`https://${node!.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node!.root.subscriptionId}/resourceGroups/${node!.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node!.root.serviceName}/apis/${apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + } + ).then(async () => { + window.showInformationMessage(localize("", `New Graphql API has been created!`)); + node?.refresh(context); + }); } diff --git a/src/constants.ts b/src/constants.ts index e48ad5b..bcf0cf5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -34,6 +34,8 @@ export const apimApiVersion = "2019-01-01"; export const maxTokenValidTimeSpan = 29; export const gatewayHostName = "CustomerHostName"; +export const SharedAccessToken = ""; + export enum GatewayKeyType { primary = "primary", secondary = "secondary" diff --git a/src/explorer/ApiTreeItem.ts b/src/explorer/ApiTreeItem.ts index fd371e7..e3f3968 100644 --- a/src/explorer/ApiTreeItem.ts +++ b/src/explorer/ApiTreeItem.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ApiManagementModels } from "@azure/arm-apimanagement"; import { ApiContract } from "@azure/arm-apimanagement/src/models"; import { ProgressLocation, window } from "vscode"; import { AzureParentTreeItem, AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui"; @@ -31,7 +30,7 @@ export class ApiTreeItem extends AzureParentTreeItem { constructor( parent: AzureParentTreeItem, - public apiContract: ApiManagementModels.ApiContract, + public apiContract: ApiContract, apiVersion?: string) { super(parent); @@ -42,7 +41,9 @@ export class ApiTreeItem extends AzureParentTreeItem { } this._name = nonNullProp(this.apiContract, 'name'); - this._root = this.createRoot(parent.root, this._name); + this._root = this.apiContract.apiType !== undefined ? + this.createRoot(parent.root, this._name, this.apiContract.apiType!.toString()) + : this.createRoot(parent.root, this._name, undefined); this._operationsTreeItem = new ApiOperationsTreeItem(this); this.policyTreeItem = new ApiPolicyTreeItem(this); } @@ -103,7 +104,7 @@ export class ApiTreeItem extends AzureParentTreeItem { this.apiContract = api; this._name = nonNullProp(api, 'name'); this._label = this.getRevisionDisplayName(api); - this._root = this.createRoot(this.root, this._name); + this._root = this.createRoot(this.root, this._name, this.apiContract.apiType!.toString()); this._operationsTreeItem = new ApiOperationsTreeItem(this); this.policyTreeItem = new ApiPolicyTreeItem(this); } @@ -117,9 +118,10 @@ export class ApiTreeItem extends AzureParentTreeItem { } } - private createRoot(subRoot: ISubscriptionContext, apiName: string): IApiTreeRoot { + private createRoot(subRoot: ISubscriptionContext, apiName: string, apiType: string | undefined): IApiTreeRoot { return Object.assign({}, subRoot, { - apiName: apiName + apiName: apiName, + apiType: apiType }); } } diff --git a/src/explorer/ApisTreeItem.ts b/src/explorer/ApisTreeItem.ts index 5a2856a..70bdcf3 100644 --- a/src/explorer/ApisTreeItem.ts +++ b/src/explorer/ApisTreeItem.ts @@ -5,8 +5,10 @@ import { ApiManagementModels } from "@azure/arm-apimanagement"; import { ApiContract, ApiCreateOrUpdateParameter } from "@azure/arm-apimanagement/src/models"; -import { ServiceClient } from "@azure/ms-rest-js"; -import { AzExtTreeItem, AzureParentTreeItem, createGenericClient, ICreateChildImplContext } from "vscode-azureextensionui"; +import requestPromise from 'request-promise'; +import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui"; +import { IApiContract } from "../azure/apim/TempApiContract"; +import { SharedAccessToken, topItemCount } from "../constants"; import { localize } from "../localize"; import { IOpenApiImportObject } from "../openApi/OpenApiImportObject"; import { apiUtil } from "../utils/apiUtil"; @@ -14,6 +16,7 @@ import { processError } from "../utils/errorUtil"; import { treeUtils } from "../utils/treeUtils"; import { ApiTreeItem } from "./ApiTreeItem"; import { ApiVersionSetTreeItem } from "./ApiVersionSetTreeItem"; +import { GraphqlApiTreeItem } from "./GraphqlApiTreeItem"; import { IServiceTreeRoot } from "./IServiceTreeRoot"; export interface IApiTreeItemContext extends ICreateChildImplContext { @@ -45,29 +48,45 @@ export class ApisTreeItem extends AzureParentTreeItem { } let apisToLoad : ApiContract[] = this.selectedApis; + let apisToLoad2 : IApiContract[] = []; if (this.selectedApis.length === 0) { - /* - const apiCollection: ApiManagementModels.ApiCollection = this._nextLink === undefined ? + const apiCollection1: ApiManagementModels.ApiCollection = this._nextLink === undefined ? await this.root.client.api.listByService(this.root.resourceGroupName, this.root.serviceName, { expandApiVersionSet: true, top: topItemCount }) : await this.root.client.api.listByServiceNext(this._nextLink); - this._nextLink = apiCollection.nextLink; + this._nextLink = apiCollection1.nextLink; - apisToLoad = apiCollection.map((s) => s).filter(s => apiUtil.isNotApiRevision(s));*/ - const client: ServiceClient = await createGenericClient(this.root.credentials); + apisToLoad = apiCollection1.map((s) => s).filter(s => apiUtil.isNotApiRevision(s)); + /*const client: ServiceClient = await createGenericClient(this.root.credentials); // tslint:disable-next-line: no-unsafe-any const apiCollection: ApiManagementModels.ApiCollection = (await client.sendRequest({ method: "GET", url: `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`, headers: { - Authorization: "SharedAccessSignature integration&202106122332&mkR/KwuXqDeXgsCEN6h8H/bTu5Btks+UnvTAl+rez9cv7Iq3BfRY/z0Wm2HHt8/SUc1kHDrvTNCafH7VkcVVeg==" + Authorization: "" + } + })).parsedBody;*/ + /* + const webResource = new WebResource(); + webResource.url = `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`; + webResource.method = "GET"; + webResource.headers.set("Authorization", ""); + const apiCollection: ApiManagementModels.ApiCollection = await sendRequest(webResource);*/ + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken } - })).parsedBody; - apisToLoad = apiCollection.map((s) => s).filter(s => apiUtil.isNotApiRevision(s)); + }; + const apiCollectionString = await >requestPromise(`https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-unsafe-any + const apiCollectionTemp : IApiContract[] = JSON.parse(apiCollectionString).value; + apisToLoad2 = this.findGraphqlApis(apiCollectionTemp); } const versionSetMap: Map = new Map(); - return await this.createTreeItemsWithErrorHandling( + // tslint:disable-next-line: no-unnecessary-local-variable + const children1 = await this.createTreeItemsWithErrorHandling( apisToLoad, "invalidApiManagementApi", async (api: ApiManagementModels.ApiContract) => { @@ -92,8 +111,55 @@ export class ApisTreeItem extends AzureParentTreeItem { (api: ApiManagementModels.ApiContract) => { return api.name; }); + + const children2 = await this.createTreeItemsWithErrorHandling( + apisToLoad2, + "invalidApiManagementApi", + async (api: IApiContract) => { + if (api.properties.isCurrent !== undefined && api.properties.isCurrent === true) { + return new GraphqlApiTreeItem(this, api); + } + return undefined; + }, + (api: IApiContract) => { + return api.name; + } + ); + return children1.concat(children2); } + public findGraphqlApis(apiCollection : IApiContract[]): IApiContract[] { + const collection : IApiContract[] = []; + for (const api of apiCollection) { + if (api.properties.type === 'graphql') { + collection.push(api); + } + } + return collection; + } + + /* + public convertToTempApiContract(apiCollectionTemp: IApiContract[]): ApiContract[] { + const apiCollection: ApiContract[] = []; + for (const api of apiCollectionTemp) { + const curApi : ApiContract = { + description: api.properties.description, + authenticationSettings: api.properties.authenticationSettings, + subscriptionKeyParameterNames: api.properties.subscriptionKeyParameterNames, + apiType: api.properties.type, + apiRevision: api.properties.apiRevision, + isCurrent: api.properties.isCurrent, + path: api.properties.path, + displayName: api.properties.displayName, + protocols: api.properties.protocols, + serviceUrl: api.properties.serviceUrl, + subscriptionRequired: api.properties.subscriptionRequired + }; + apiCollection.push(curApi); + } + return apiCollection; + }*/ + public async createChildImpl(context: IApiTreeItemContext): Promise { if (context.document) { return await this.createApiFromOpenApi(context.showCreatingTreeItem, context.apiName, context.document); diff --git a/src/explorer/GraphqlApiTreeItem.ts b/src/explorer/GraphqlApiTreeItem.ts new file mode 100644 index 0000000..67b7222 --- /dev/null +++ b/src/explorer/GraphqlApiTreeItem.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AzureParentTreeItem, AzureTreeItem, ISubscriptionContext } from "vscode-azureextensionui"; +import { IApiContract } from "../azure/apim/TempApiContract"; +import { nonNullProp } from "../utils/nonNull"; +import { treeUtils } from "../utils/treeUtils"; +import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; +import { IServiceTreeRoot } from "./IServiceTreeRoot"; + +export class GraphqlApiTreeItem extends AzureParentTreeItem { + public static contextValue: string = 'azureApiManagementGraphql'; + public contextValue: string = GraphqlApiTreeItem.contextValue; + public readonly commandId: string = 'azureApiManagement.showArmApi'; + public policyTreeItem: ApiPolicyTreeItem; + + private _name: string; + private _label: string; + private _root: IApiTreeRoot; + + constructor( + parent: AzureParentTreeItem, + public apiContract: IApiContract, + apiVersion?: string) { + super(parent); + + if (!apiVersion) { + const label = nonNullProp(this.apiContract.properties, 'displayName'); + this._label = label.concat(" (GraphqlAPI)"); + } else { + this._label = apiVersion.concat(" (GraphqlAPI)"); + } + + this._name = nonNullProp(this.apiContract, 'name'); + this._root = this.createRoot(parent.root, this._name, this.apiContract.properties.type); + this.policyTreeItem = new ApiPolicyTreeItem(this); + } + + public get id(): string { + return this._label; + } + + public get label(): string { + return this._label; + } + + public get root(): IApiTreeRoot { + return this._root; + } + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('api'); + } + + public async loadMoreChildrenImpl(): Promise[]> { + return [this.policyTreeItem]; + } + public hasMoreChildrenImpl(): boolean { + return false; + } + + private createRoot(subRoot: ISubscriptionContext, apiName: string, apiType: string): IApiTreeRoot { + return Object.assign({}, subRoot, { + apiName: apiName, + apiType: apiType + }); + } +} diff --git a/src/explorer/IApiTreeRoot.ts b/src/explorer/IApiTreeRoot.ts index 05414bb..7f4c6ac 100644 --- a/src/explorer/IApiTreeRoot.ts +++ b/src/explorer/IApiTreeRoot.ts @@ -7,4 +7,5 @@ import { IServiceTreeRoot } from "./IServiceTreeRoot"; export interface IApiTreeRoot extends IServiceTreeRoot { apiName: string; + apiType: string | undefined; } diff --git a/src/explorer/editors/arm/ApiResourceEditor.ts b/src/explorer/editors/arm/ApiResourceEditor.ts index 7a7487a..a812bfa 100644 --- a/src/explorer/editors/arm/ApiResourceEditor.ts +++ b/src/explorer/editors/arm/ApiResourceEditor.ts @@ -4,7 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ApiManagementModels } from "@azure/arm-apimanagement"; +import requestPromise from "request-promise"; import { AzureTreeItem } from "vscode-azureextensionui"; +import { IApiContract } from "../../../azure/apim/TempApiContract"; +import { SharedAccessToken } from "../../../constants"; import { IApiTreeRoot } from "../../IApiTreeRoot"; import { BaseArmResourceEditor } from "./BaseArmResourceEditor"; @@ -15,11 +18,38 @@ export class ApiResourceEditor extends BaseArmResourceEditor { super(); } - public async getDataInternal(context: AzureTreeItem): Promise { + public async getDataInternal(context: AzureTreeItem): Promise { + if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + const apiString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const apiTemp : IApiContract = JSON.parse(apiString); + return apiTemp; + } return await context.root.client.api.get(context.root.resourceGroupName, context.root.serviceName, context.root.apiName); } - public async updateDataInternal(context: AzureTreeItem, payload: ApiManagementModels.ApiCreateOrUpdateParameter): Promise { + public async updateDataInternal(context: AzureTreeItem, payload: ApiManagementModels.ApiCreateOrUpdateParameter): Promise { + if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "PUT", + headers: { + Authorization: SharedAccessToken + }, + body: JSON.stringify(payload) + }; + const apiString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const apiTemp : IApiContract = JSON.parse(apiString); + return apiTemp; + } return await context.root.client.api.createOrUpdate(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, payload); } } diff --git a/src/explorer/editors/policy/ApiPolicyEditor.ts b/src/explorer/editors/policy/ApiPolicyEditor.ts index 2a86e19..96774b2 100644 --- a/src/explorer/editors/policy/ApiPolicyEditor.ts +++ b/src/explorer/editors/policy/ApiPolicyEditor.ts @@ -4,20 +4,55 @@ *--------------------------------------------------------------------------------------------*/ import { ApiManagementModels } from "@azure/arm-apimanagement"; +import requestPromise from "request-promise"; import { AzureTreeItem } from "vscode-azureextensionui"; import { emptyPolicyXml, policyFormat } from "../../../constants"; +import { SharedAccessToken } from "../../../constants"; import { IApiTreeRoot } from "../../IApiTreeRoot"; import { BasePolicyEditor } from "./BasePolicyEditor"; export class ApiPolicyEditor extends BasePolicyEditor { public async getPolicy(context: AzureTreeItem): Promise { + if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + const policyString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const policyTemp = JSON.parse(policyString); + return policyTemp.properties.value; + } const policy = await context.root.client.apiPolicy.get(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, { format: policyFormat }); return policy._response.bodyAsText; } public async updatePolicy(context: AzureTreeItem, policy: ApiManagementModels.PolicyContract): Promise { - const policyResult = await context.root.client.apiPolicy.createOrUpdate(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, policy); - return policyResult._response.bodyAsText; + if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { + const body = { + properties: { + value: policy.value, + format: policy.format + } + }; + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "PUT", + headers: { + Authorization: SharedAccessToken + }, + body: JSON.stringify(body) + }; + const policyString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const policyTemp = JSON.parse(policyString); + return policyTemp.properties.value; + } + const policyResult = await context.root.client.apiPolicy.createOrUpdate(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, policy); + return policyResult._response.bodyAsText; } public getDefaultPolicy() : string { diff --git a/src/extension.ts b/src/extension.ts index 966425c..be55c04 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,6 +12,7 @@ import { addApiFilter } from './commands/addApiFilter'; import { addApiToGateway } from './commands/addApiToGateway'; import { addApiToProduct } from './commands/addApiToProduct'; import { copySubscriptionKey } from './commands/copySubscriptionKey'; +import { createGraphqlApi } from './commands/createGraphqlApi'; import { createService } from './commands/createService'; import { debugPolicy } from './commands/debugPolicies/debugPolicy'; import { deleteNode } from './commands/deleteNode'; @@ -104,6 +105,7 @@ function registerCommands(tree: AzExtTreeDataProvider): void { registerCommand('azureApiManagement.testOperation', testOperation); registerCommand('azureApiManagement.importOpenApiByFile', async (context: IActionContext, node?: ApisTreeItem) => { await importOpenApi(context, node, false); }); registerCommand('azureApiManagement.importOpenApiByLink', async (context: IActionContext, node?: ApisTreeItem) => { await importOpenApi(context, node, true); }); + registerCommand('azureApiManagement.importGraphqlAPIByLink', async (context: IActionContext, node?: ApisTreeItem) => { await createGraphqlApi(context, node); }); registerCommand('azureApiManagement.createNamedValue', async (context: IActionContext, node?: NamedValuesTreeItem) => { await createNamedValue(context, node); }); registerCommand('azureApiManagement.deleteNamedValue', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, NamedValueTreeItem.contextValue, node)); registerCommand('azureApiManagement.updateNamedValue', updateNamedValue); diff --git a/src/utils/apiUtil.ts b/src/utils/apiUtil.ts index 97db1dd..1c6412f 100644 --- a/src/utils/apiUtil.ts +++ b/src/utils/apiUtil.ts @@ -25,6 +25,13 @@ export namespace apiUtil { })).trim(); } + export async function askPath(): Promise { + const pathPrompt: string = localize('', "Enter API Path."); + return (await ext.ui.showInputBox({ + prompt: pathPrompt + })).trim(); + } + export function genApiId(apiName: string): string { const identifier = displayNameToIdentifier(apiName); return `/apis/${identifier}`; From 6333ff03aa37ed298c7ca64a94eedc78367ba7ee Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Mon, 21 Jun 2021 23:52:28 -0700 Subject: [PATCH 03/11] Add schema explorer --- package-lock.json | 9 +- package.json | 3 +- src/azure/apim/TempSchema.ts | 18 +++ src/explorer/GraphqlApiTreeItem.ts | 6 +- src/explorer/GraphqlObjectTypeTreeItem.ts | 45 ++++++++ src/explorer/GraphqlObjectTypesTreeItem.ts | 45 ++++++++ src/explorer/GraphqlOperationsTreeItem.ts | 67 +++++++++++ src/explorer/GraphqlSchemaTreeItem.ts | 126 +++++++++++++++++++++ src/test.http | 4 + 9 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 src/azure/apim/TempSchema.ts create mode 100644 src/explorer/GraphqlObjectTypeTreeItem.ts create mode 100644 src/explorer/GraphqlObjectTypesTreeItem.ts create mode 100644 src/explorer/GraphqlOperationsTreeItem.ts create mode 100644 src/explorer/GraphqlSchemaTreeItem.ts create mode 100644 src/test.http diff --git a/package-lock.json b/package-lock.json index de97614..543fb8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "vscode-apimanagement", - "version": "1.0.2-beta", + "version": "1.0.3-beta", "lockfileVersion": 1, "requires": true, "dependencies": { "@azure/abort-controller": { - "version": "1.0.3", + "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.2.tgz", "integrity": "sha512-XUyTo+bcyxHEf+jlN2MXA7YU9nxVehaubngHV1MIZZaqYmZqykkoeAz/JMMEeR7t3TcyDwbFa3Zw8BZywmIx4g==", "requires": { @@ -4412,6 +4412,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, + "graphql": { + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz", + "integrity": "sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==" + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", diff --git a/package.json b/package.json index 3ba9016..f36060e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-apimanagement", "displayName": "Azure API Management", "description": "An Azure API Management extension for Visual Studio Code.", - "version": "1.0.3", + "version": "1.0.3-beta", "publisher": "ms-azuretools", "icon": "resources/apim-icon-newone.png", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", @@ -740,6 +740,7 @@ "await-notify": "1.0.1", "decompress": "^4.2.1", "fs-extra": "^4.0.2", + "graphql": "^15.5.0", "guid-typescript": "^1.0.9", "gulp-decompress": "^3.0.0", "gulp-download": "0.0.1", diff --git a/src/azure/apim/TempSchema.ts b/src/azure/apim/TempSchema.ts new file mode 100644 index 0000000..94472b8 --- /dev/null +++ b/src/azure/apim/TempSchema.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class TempSchema { + public id: string; + public name: string; + // tslint:disable-next-line: no-reserved-keywords + public type: string; + public location?: string; + public properties: { + contentType: string, + document: { + value: string + } + }; +} diff --git a/src/explorer/GraphqlApiTreeItem.ts b/src/explorer/GraphqlApiTreeItem.ts index 67b7222..7360fee 100644 --- a/src/explorer/GraphqlApiTreeItem.ts +++ b/src/explorer/GraphqlApiTreeItem.ts @@ -8,6 +8,7 @@ import { IApiContract } from "../azure/apim/TempApiContract"; import { nonNullProp } from "../utils/nonNull"; import { treeUtils } from "../utils/treeUtils"; import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem"; +import { GraphqlOperationsTreeItem } from "./GraphqlOperationsTreeItem"; import { IApiTreeRoot } from "./IApiTreeRoot"; import { IServiceTreeRoot } from "./IServiceTreeRoot"; @@ -20,6 +21,7 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { private _name: string; private _label: string; private _root: IApiTreeRoot; + private _operationsTreeItem: GraphqlOperationsTreeItem; constructor( parent: AzureParentTreeItem, @@ -37,6 +39,7 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { this._name = nonNullProp(this.apiContract, 'name'); this._root = this.createRoot(parent.root, this._name, this.apiContract.properties.type); this.policyTreeItem = new ApiPolicyTreeItem(this); + this._operationsTreeItem = new GraphqlOperationsTreeItem(this); } public get id(): string { @@ -56,8 +59,9 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { } public async loadMoreChildrenImpl(): Promise[]> { - return [this.policyTreeItem]; + return [this._operationsTreeItem, this.policyTreeItem]; } + public hasMoreChildrenImpl(): boolean { return false; } diff --git a/src/explorer/GraphqlObjectTypeTreeItem.ts b/src/explorer/GraphqlObjectTypeTreeItem.ts new file mode 100644 index 0000000..75c247f --- /dev/null +++ b/src/explorer/GraphqlObjectTypeTreeItem.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLObjectType } from "graphql"; +import { AzureParentTreeItem, AzureTreeItem } from "vscode-azureextensionui"; +import { treeUtils } from "../utils/treeUtils"; +import { IOperationTreeRoot } from "./IOperationTreeRoot"; + +export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem { + public static contextValue: string = 'azureApiManagementGraphqlObjectType'; + public contextValue: string = GraphqlObjectTypeTreeItem.contextValue; + + private _name: string; + private _label: string; + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('op'); + } + + public get label() : string { + return this._label; + } + + public get id(): string { + return this._name; + } + + constructor( + parent: AzureParentTreeItem, + public readonly objectType: GraphQLObjectType) { + super(parent); + this._label = objectType.name; + this._name = objectType.name; + } + + public async loadMoreChildrenImpl(): Promise[]> { + return []; + } + + public hasMoreChildrenImpl(): boolean { + return false; + } +} diff --git a/src/explorer/GraphqlObjectTypesTreeItem.ts b/src/explorer/GraphqlObjectTypesTreeItem.ts new file mode 100644 index 0000000..b517eff --- /dev/null +++ b/src/explorer/GraphqlObjectTypesTreeItem.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLObjectType } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlObjectTypeTreeItem } from "./GraphqlObjectTypeTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +export class GraphqlObjectTypesTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlObjectTypes'; + public label: string = "Object Types"; + public contextValue: string = GraphqlObjectTypesTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlObjectTypeList', 'graphqlObjectTypeList'); + private graphqlObjectTypes : GraphQLObjectType[]; + + constructor( + parent: AzureParentTreeItem, + public types: GraphQLObjectType[]) { + super(parent); + this.graphqlObjectTypes = types; + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + + public async loadMoreChildrenImpl(_clearCache: boolean): Promise { + return this.createTreeItemsWithErrorHandling( + this.graphqlObjectTypes, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLObjectType) => new GraphqlObjectTypeTreeItem(this, objectType), + (objectType: GraphQLObjectType) => { + return objectType.name; + }); + + } +} diff --git a/src/explorer/GraphqlOperationsTreeItem.ts b/src/explorer/GraphqlOperationsTreeItem.ts new file mode 100644 index 0000000..a8edc6e --- /dev/null +++ b/src/explorer/GraphqlOperationsTreeItem.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { buildSchema, GraphQLObjectType, GraphQLSchema } from "graphql"; +import requestPromise from "request-promise"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { TempSchema } from "../azure/apim/TempSchema"; +import { SharedAccessToken } from "../constants"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlObjectTypesTreeItem } from "./GraphqlObjectTypesTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +export class GraphqlOperationsTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlList'; + public label: string = "Types\\Queries"; + public contextValue: string = GraphqlOperationsTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlList', 'graphqlList'); + private _nextLink: string | undefined; + + private graphqlObjectTypeTreeItems: GraphqlObjectTypesTreeItem; + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + const schemasString = await >requestPromise( + `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis/${this.root.apiName}/schemas?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const schemas : TempSchema[] = JSON.parse(schemasString).value; + + const valueList: GraphQLSchema[] = []; + const objectTypes: GraphQLObjectType[] = []; + for (const schema of schemas) { + const builtSchema : GraphQLSchema = buildSchema(schema.properties.document.value); + valueList.push(builtSchema); + const typeMap = builtSchema.getTypeMap(); + for (const key of Object.keys(typeMap)) { + const value = typeMap[key]; + if (value instanceof GraphQLObjectType) { + objectTypes.push(value); + } + } + } + + this.graphqlObjectTypeTreeItems = new GraphqlObjectTypesTreeItem(this, objectTypes); + return [this.graphqlObjectTypeTreeItems]; + + } +} diff --git a/src/explorer/GraphqlSchemaTreeItem.ts b/src/explorer/GraphqlSchemaTreeItem.ts new file mode 100644 index 0000000..d772020 --- /dev/null +++ b/src/explorer/GraphqlSchemaTreeItem.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ApiContract } from "@azure/arm-apimanagement/src/models"; +import { ProgressLocation, window } from "vscode"; +import { AzureParentTreeItem, AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { nonNullProp } from "../utils/nonNull"; +import { treeUtils } from "../utils/treeUtils"; +import { ApiOperationsTreeItem } from "./ApiOperationsTreeItem"; +import { ApiOperationTreeItem } from "./ApiOperationTreeItem"; +import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; +import { IServiceTreeRoot } from "./IServiceTreeRoot"; +import { OperationPolicyTreeItem } from "./OperationPolicyTreeItem"; + +// tslint:disable: no-non-null-assertion +export class GraphqlSchemaTreeItem extends AzureParentTreeItem { + public static contextValue: string = 'azureApiManagementGraphqlSchema'; + public contextValue: string = GraphqlSchemaTreeItem.contextValue; + public policyTreeItem: ApiPolicyTreeItem; + + private _name: string; + private _label: string; + private _root: IApiTreeRoot; + private _operationsTreeItem: ApiOperationsTreeItem; + + constructor( + parent: AzureParentTreeItem, + public apiContract: ApiContract, + apiVersion?: string) { + super(parent); + + if (!apiVersion) { + this._label = nonNullProp(this.apiContract, 'displayName'); + } else { + this._label = apiVersion; + } + + this._name = nonNullProp(this.apiContract, 'name'); + this._root = this.apiContract.apiType !== undefined ? + this.createRoot(parent.root, this._name, this.apiContract.apiType!.toString()) + : this.createRoot(parent.root, this._name, undefined); + this._operationsTreeItem = new ApiOperationsTreeItem(this); + this.policyTreeItem = new ApiPolicyTreeItem(this); + } + + public get id(): string { + return this._name; + } + + public get label() : string { + return this._label; + } + + public get root(): IApiTreeRoot { + return this._root; + } + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('api'); + } + + public async loadMoreChildrenImpl(): Promise[]> { + return [this._operationsTreeItem, this.policyTreeItem]; + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + + public async deleteTreeItemImpl(): Promise { + const message: string = localize("confirmDeleteApi", `Are you sure you want to delete API '${this.root.apiName}' and its contents?`); + const result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel); + if (result === DialogResponses.deleteResponse) { + const deletingMessage: string = localize("deletingApi", `Deleting API "${this.root.apiName}"...`); + await window.withProgress({ location: ProgressLocation.Notification, title: deletingMessage }, async () => { + await this.root.client.api.deleteMethod(this.root.resourceGroupName, this.root.serviceName, this.root.apiName, '*'); + }); + // don't wait + window.showInformationMessage(localize("deletedApi", `Successfully deleted API "${this.root.apiName}".`)); + + } else { + throw new UserCancelledError(); + } + } + + public pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): AzureTreeItem | undefined { + for (const expectedContextValue of expectedContextValues) { + switch (expectedContextValue) { + case OperationPolicyTreeItem.contextValue: + case ApiOperationTreeItem.contextValue: + return this._operationsTreeItem; + default: + } + } + return undefined; + } + + public async reloadApi(api: ApiContract): Promise { + this.apiContract = api; + this._name = nonNullProp(api, 'name'); + this._label = this.getRevisionDisplayName(api); + this._root = this.createRoot(this.root, this._name, this.apiContract.apiType!.toString()); + this._operationsTreeItem = new ApiOperationsTreeItem(this); + this.policyTreeItem = new ApiPolicyTreeItem(this); + } + + private getRevisionDisplayName(api: ApiContract): string { + if (api.isCurrent !== undefined && api.isCurrent === true) { + return api.displayName!; + } else { + const revNumber = api.name!.split(';rev=')[1]; + return api.displayName!.concat(';rev=', revNumber); + } + } + + private createRoot(subRoot: ISubscriptionContext, apiName: string, apiType: string | undefined): IApiTreeRoot { + return Object.assign({}, subRoot, { + apiName: apiName, + apiType: apiType + }); + } +} diff --git a/src/test.http b/src/test.http new file mode 100644 index 0000000..5ff5810 --- /dev/null +++ b/src/test.http @@ -0,0 +1,4 @@ + +POST https://api.spacex.land/graphql + +{"query":"{ launches { id, mission_name }}","variables":{}} \ No newline at end of file From 0dee883b14516844b04964cd4330c3a7f847d3d1 Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Thu, 1 Jul 2021 17:54:44 -0700 Subject: [PATCH 04/11] Add recursive nodes --- src/explorer/GraphqlApiTreeItem.ts | 73 ++++++++++++++++++-- src/explorer/GraphqlArgFieldTreeItem.ts | 37 ++++++++++ src/explorer/GraphqlArgsTreeItem.ts | 62 +++++++++++++++++ src/explorer/GraphqlFieldsTreeItem.ts | 70 +++++++++++++++++++ src/explorer/GraphqlMutationsTreeItem.ts | 65 +++++++++++++++++ src/explorer/GraphqlObjectTypeTreeItem.ts | 65 +++++++++++++++-- src/explorer/GraphqlObjectTypesTreeItem.ts | 45 ------------ src/explorer/GraphqlOperationsTreeItem.ts | 67 ------------------ src/explorer/GraphqlQueriesTreeItem.ts | 65 +++++++++++++++++ src/explorer/GraphqlSubscriptionsTreeItem.ts | 65 +++++++++++++++++ 10 files changed, 491 insertions(+), 123 deletions(-) create mode 100644 src/explorer/GraphqlArgFieldTreeItem.ts create mode 100644 src/explorer/GraphqlArgsTreeItem.ts create mode 100644 src/explorer/GraphqlFieldsTreeItem.ts create mode 100644 src/explorer/GraphqlMutationsTreeItem.ts delete mode 100644 src/explorer/GraphqlObjectTypesTreeItem.ts delete mode 100644 src/explorer/GraphqlOperationsTreeItem.ts create mode 100644 src/explorer/GraphqlQueriesTreeItem.ts create mode 100644 src/explorer/GraphqlSubscriptionsTreeItem.ts diff --git a/src/explorer/GraphqlApiTreeItem.ts b/src/explorer/GraphqlApiTreeItem.ts index 7360fee..ad40971 100644 --- a/src/explorer/GraphqlApiTreeItem.ts +++ b/src/explorer/GraphqlApiTreeItem.ts @@ -3,15 +3,22 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { buildSchema, GraphQLField, GraphQLFieldMap, GraphQLNamedType, GraphQLObjectType, GraphQLSchema } from "graphql"; +import requestPromise from "request-promise"; import { AzureParentTreeItem, AzureTreeItem, ISubscriptionContext } from "vscode-azureextensionui"; import { IApiContract } from "../azure/apim/TempApiContract"; +import { TempSchema } from "../azure/apim/TempSchema"; +import { SharedAccessToken } from "../constants"; import { nonNullProp } from "../utils/nonNull"; import { treeUtils } from "../utils/treeUtils"; import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem"; -import { GraphqlOperationsTreeItem } from "./GraphqlOperationsTreeItem"; +import { GraphqlMutationsTreeItem } from "./GraphqlMutationsTreeItem"; +import { GraphqlQueriesTreeItem } from "./GraphqlQueriesTreeItem"; +import { GraphqlSubscriptionsTreeItem } from "./GraphqlSubscriptionsTreeItem"; import { IApiTreeRoot } from "./IApiTreeRoot"; import { IServiceTreeRoot } from "./IServiceTreeRoot"; +// tslint:disable: no-any export class GraphqlApiTreeItem extends AzureParentTreeItem { public static contextValue: string = 'azureApiManagementGraphql'; public contextValue: string = GraphqlApiTreeItem.contextValue; @@ -21,7 +28,9 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { private _name: string; private _label: string; private _root: IApiTreeRoot; - private _operationsTreeItem: GraphqlOperationsTreeItem; + private _queriesTreeItem: GraphqlQueriesTreeItem; + private _mutationsTreeItem: GraphqlMutationsTreeItem; + private _subscriptionsTreeItem: GraphqlSubscriptionsTreeItem; constructor( parent: AzureParentTreeItem, @@ -39,7 +48,6 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { this._name = nonNullProp(this.apiContract, 'name'); this._root = this.createRoot(parent.root, this._name, this.apiContract.properties.type); this.policyTreeItem = new ApiPolicyTreeItem(this); - this._operationsTreeItem = new GraphqlOperationsTreeItem(this); } public get id(): string { @@ -59,7 +67,64 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { } public async loadMoreChildrenImpl(): Promise[]> { - return [this._operationsTreeItem, this.policyTreeItem]; + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + const schemasString = await >requestPromise( + `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis/${this.root.apiName}/schemas?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable: no-unsafe-any + // tslint:disable-next-line: no-unnecessary-local-variable + const schemas : TempSchema[] = JSON.parse(schemasString).value; + + const valueList: GraphQLSchema[] = []; + // tslint:disable-next-line: no-any + const queryTypes: GraphQLField[] = []; + const mutationTypes: GraphQLField[] = []; + const subscriptionTypes: GraphQLField[] = []; + for (const schema of schemas) { + const builtSchema : GraphQLSchema = buildSchema(schema.properties.document.value); + valueList.push(builtSchema); + const typeMap = builtSchema.getTypeMap(); + for (const key of Object.keys(typeMap)) { + const value: GraphQLNamedType = typeMap[key]; + if (key === "Query" && value instanceof GraphQLObjectType) { + const fields: GraphQLFieldMap = value.getFields(); + for (const fieldKey of Object.keys(fields)) { + const fieldValue = fields[fieldKey]; + queryTypes.push(fieldValue); + } + } else if (key === "Mutation" && value instanceof GraphQLObjectType) { + const fields: GraphQLFieldMap = value.getFields(); + for (const fieldKey of Object.keys(fields)) { + const fieldValue = fields[fieldKey]; + mutationTypes.push(fieldValue); + } + } else if (key === "Subscription" && value instanceof GraphQLObjectType) { + const fields: GraphQLFieldMap = value.getFields(); + for (const fieldKey of Object.keys(fields)) { + const fieldValue = fields[fieldKey]; + subscriptionTypes.push(fieldValue); + } + } + } + } + this._queriesTreeItem = new GraphqlQueriesTreeItem(this, queryTypes); + this._mutationsTreeItem = new GraphqlMutationsTreeItem(this, mutationTypes); + this._subscriptionsTreeItem = new GraphqlSubscriptionsTreeItem(this, subscriptionTypes); + + return [this._queriesTreeItem, this._mutationsTreeItem, this._subscriptionsTreeItem, this.policyTreeItem]; } public hasMoreChildrenImpl(): boolean { diff --git a/src/explorer/GraphqlArgFieldTreeItem.ts b/src/explorer/GraphqlArgFieldTreeItem.ts new file mode 100644 index 0000000..c724575 --- /dev/null +++ b/src/explorer/GraphqlArgFieldTreeItem.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLInputField } from "graphql"; +import { AzureParentTreeItem, AzureTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlArgFieldTreeItem extends AzureTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('op'); + } + public static contextValue: string = 'azureApiManagementGraphqlArgFieldList'; + public _label: string; + public contextValue: string = GraphqlArgFieldTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlArgFieldList', 'graphqlArgFieldList'); + + private argField: GraphQLInputField; + + public get label() : string { + return this._label; + } + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + argField: GraphQLInputField) { + super(parent); + this.argField = argField; + this._label = this.argField.name; + } +} diff --git a/src/explorer/GraphqlArgsTreeItem.ts b/src/explorer/GraphqlArgsTreeItem.ts new file mode 100644 index 0000000..64bee5c --- /dev/null +++ b/src/explorer/GraphqlArgsTreeItem.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLArgument, GraphQLInputField, GraphQLInputFieldMap, GraphQLInputObjectType, GraphQLInputType } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlArgFieldTreeItem } from "./GraphqlArgFieldTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlArgsTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('op'); + } + public static contextValue: string = 'azureApiManagementGraphqlArgsList'; + public _label: string; + public contextValue: string = GraphqlArgsTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlArgsList', 'graphqlArgsList'); + private _nextLink: string | undefined; + + private arg: GraphQLArgument; + + public get label() : string { + return this._label; + } + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + arg: GraphQLArgument) { + super(parent); + this.arg = arg; + this._label = this.arg.name; + } + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + const args : GraphQLInputType = this.arg.type; + if (args instanceof GraphQLInputObjectType) { + const fields : GraphQLInputFieldMap = args.getFields(); + const fieldValues : GraphQLInputField[] = Object.values(fields); + return await this.createTreeItemsWithErrorHandling( + fieldValues, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLInputField) => new GraphqlArgFieldTreeItem(this, objectType), + (objectType: GraphQLInputField) => { + return objectType.name; + }); + } + return []; + } +} diff --git a/src/explorer/GraphqlFieldsTreeItem.ts b/src/explorer/GraphqlFieldsTreeItem.ts new file mode 100644 index 0000000..dd46707 --- /dev/null +++ b/src/explorer/GraphqlFieldsTreeItem.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLField, GraphQLObjectType } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlFieldsTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlFieldsList'; + public _label: string; + public contextValue: string = GraphqlFieldsTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlFieldsList', 'graphqlFieldsList'); + private _nextLink: string | undefined; + + private field: GraphQLField; + + public get label() : string { + return this._label; + } + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + field: GraphQLField) { + super(parent); + this.field = field; + this._label = this.field.name; + } + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + const fieldChildren = this.field.type; + if (fieldChildren instanceof GraphQLObjectType) { + const fields = Object.values(fieldChildren.getFields()); + return await this.createTreeItemsWithErrorHandling( + fields, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlFieldsTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + } + return []; + } +} diff --git a/src/explorer/GraphqlMutationsTreeItem.ts b/src/explorer/GraphqlMutationsTreeItem.ts new file mode 100644 index 0000000..d1202c3 --- /dev/null +++ b/src/explorer/GraphqlMutationsTreeItem.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLField } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlObjectTypeTreeItem } from "./GraphqlObjectTypeTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlMutationsTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlMutationsList'; + public label: string = "Mutation"; + public contextValue: string = GraphqlMutationsTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlMutationList', 'graphqlMutationList'); + private _nextLink: string | undefined; + + private mutationTypes: GraphQLField[]; + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + mutationTypes: GraphQLField[]) { + super(parent); + this.mutationTypes = mutationTypes; + } + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + + return this.createTreeItemsWithErrorHandling( + this.mutationTypes, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlObjectTypeTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + + } +} diff --git a/src/explorer/GraphqlObjectTypeTreeItem.ts b/src/explorer/GraphqlObjectTypeTreeItem.ts index 75c247f..adaa169 100644 --- a/src/explorer/GraphqlObjectTypeTreeItem.ts +++ b/src/explorer/GraphqlObjectTypeTreeItem.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GraphQLObjectType } from "graphql"; -import { AzureParentTreeItem, AzureTreeItem } from "vscode-azureextensionui"; +import { GraphQLArgument, GraphQLField, GraphQLFieldMap, GraphQLList, GraphQLObjectType, GraphQLOutputType } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; import { treeUtils } from "../utils/treeUtils"; +import { GraphqlArgsTreeItem } from "./GraphqlArgsTreeItem"; +import { GraphqlFieldsTreeItem } from "./GraphqlFieldsTreeItem"; import { IOperationTreeRoot } from "./IOperationTreeRoot"; +// tslint:disable: no-any export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem { public static contextValue: string = 'azureApiManagementGraphqlObjectType'; public contextValue: string = GraphqlObjectTypeTreeItem.contextValue; @@ -29,14 +32,62 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem) { super(parent); - this._label = objectType.name; - this._name = objectType.name; + this._label = object.name; + this._name = object.name; } - public async loadMoreChildrenImpl(): Promise[]> { - return []; + public async loadMoreChildrenImpl(): Promise { + const args: GraphQLArgument[] = this.object.args; + let allNodes: AzExtTreeItem[] = []; + const argsNodes = await this.createTreeItemsWithErrorHandling( + args, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLArgument) => new GraphqlArgsTreeItem(this, objectType), + (objectType: GraphQLArgument) => { + return objectType.name; + }); + allNodes = allNodes.concat(argsNodes); + + const types: GraphQLOutputType = this.object.type; + if (types instanceof GraphQLList && types.ofType instanceof GraphQLObjectType) { + const objectTypes: GraphQLObjectType = types.ofType; + const fields: GraphQLFieldMap = objectTypes.getFields(); + const fieldValues = Object.values(fields); + const fieldNodes = await this.createTreeItemsWithErrorHandling( + fieldValues, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlFieldsTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + allNodes = allNodes.concat(fieldNodes); + } else if (types instanceof GraphQLObjectType) { + const fields: GraphQLFieldMap = types.getFields(); + const fieldValues = Object.values(fields); + const fieldNodes = await this.createTreeItemsWithErrorHandling( + fieldValues, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlFieldsTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + allNodes = allNodes.concat(fieldNodes); + } + return allNodes; } public hasMoreChildrenImpl(): boolean { diff --git a/src/explorer/GraphqlObjectTypesTreeItem.ts b/src/explorer/GraphqlObjectTypesTreeItem.ts deleted file mode 100644 index b517eff..0000000 --- a/src/explorer/GraphqlObjectTypesTreeItem.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { GraphQLObjectType } from "graphql"; -import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; -import { localize } from "../localize"; -import { treeUtils } from "../utils/treeUtils"; -import { GraphqlObjectTypeTreeItem } from "./GraphqlObjectTypeTreeItem"; -import { IApiTreeRoot } from "./IApiTreeRoot"; - -export class GraphqlObjectTypesTreeItem extends AzureParentTreeItem { - - public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); - } - public static contextValue: string = 'azureApiManagementGraphqlObjectTypes'; - public label: string = "Object Types"; - public contextValue: string = GraphqlObjectTypesTreeItem.contextValue; - public readonly childTypeLabel: string = localize('azureApiManagement.graphqlObjectTypeList', 'graphqlObjectTypeList'); - private graphqlObjectTypes : GraphQLObjectType[]; - - constructor( - parent: AzureParentTreeItem, - public types: GraphQLObjectType[]) { - super(parent); - this.graphqlObjectTypes = types; - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public async loadMoreChildrenImpl(_clearCache: boolean): Promise { - return this.createTreeItemsWithErrorHandling( - this.graphqlObjectTypes, - "invalidApiManagementGraphqlObjectTypes", - async (objectType: GraphQLObjectType) => new GraphqlObjectTypeTreeItem(this, objectType), - (objectType: GraphQLObjectType) => { - return objectType.name; - }); - - } -} diff --git a/src/explorer/GraphqlOperationsTreeItem.ts b/src/explorer/GraphqlOperationsTreeItem.ts deleted file mode 100644 index a8edc6e..0000000 --- a/src/explorer/GraphqlOperationsTreeItem.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { buildSchema, GraphQLObjectType, GraphQLSchema } from "graphql"; -import requestPromise from "request-promise"; -import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; -import { TempSchema } from "../azure/apim/TempSchema"; -import { SharedAccessToken } from "../constants"; -import { localize } from "../localize"; -import { treeUtils } from "../utils/treeUtils"; -import { GraphqlObjectTypesTreeItem } from "./GraphqlObjectTypesTreeItem"; -import { IApiTreeRoot } from "./IApiTreeRoot"; - -export class GraphqlOperationsTreeItem extends AzureParentTreeItem { - - public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); - } - public static contextValue: string = 'azureApiManagementGraphqlList'; - public label: string = "Types\\Queries"; - public contextValue: string = GraphqlOperationsTreeItem.contextValue; - public readonly childTypeLabel: string = localize('azureApiManagement.graphqlList', 'graphqlList'); - private _nextLink: string | undefined; - - private graphqlObjectTypeTreeItems: GraphqlObjectTypesTreeItem; - - public hasMoreChildrenImpl(): boolean { - return this._nextLink !== undefined; - } - - public async loadMoreChildrenImpl(clearCache: boolean): Promise { - if (clearCache) { - this._nextLink = undefined; - } - const requestOptions : requestPromise.RequestPromiseOptions = { - method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const schemasString = await >requestPromise( - `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis/${this.root.apiName}/schemas?api-version=2021-04-01-preview`, requestOptions).promise(); - // tslint:disable: no-unsafe-any - // tslint:disable-next-line: no-unnecessary-local-variable - const schemas : TempSchema[] = JSON.parse(schemasString).value; - - const valueList: GraphQLSchema[] = []; - const objectTypes: GraphQLObjectType[] = []; - for (const schema of schemas) { - const builtSchema : GraphQLSchema = buildSchema(schema.properties.document.value); - valueList.push(builtSchema); - const typeMap = builtSchema.getTypeMap(); - for (const key of Object.keys(typeMap)) { - const value = typeMap[key]; - if (value instanceof GraphQLObjectType) { - objectTypes.push(value); - } - } - } - - this.graphqlObjectTypeTreeItems = new GraphqlObjectTypesTreeItem(this, objectTypes); - return [this.graphqlObjectTypeTreeItems]; - - } -} diff --git a/src/explorer/GraphqlQueriesTreeItem.ts b/src/explorer/GraphqlQueriesTreeItem.ts new file mode 100644 index 0000000..510d5ef --- /dev/null +++ b/src/explorer/GraphqlQueriesTreeItem.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLField } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlObjectTypeTreeItem } from "./GraphqlObjectTypeTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlQueriesTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlList'; + public label: string = "Query"; + public contextValue: string = GraphqlQueriesTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlList', 'graphqlList'); + private _nextLink: string | undefined; + + private queryTypes: GraphQLField[]; + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + queryTypes: GraphQLField[]) { + super(parent); + this.queryTypes = queryTypes; + } + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + + return this.createTreeItemsWithErrorHandling( + this.queryTypes, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlObjectTypeTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + + } +} diff --git a/src/explorer/GraphqlSubscriptionsTreeItem.ts b/src/explorer/GraphqlSubscriptionsTreeItem.ts new file mode 100644 index 0000000..e29a4f3 --- /dev/null +++ b/src/explorer/GraphqlSubscriptionsTreeItem.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLField } from "graphql"; +import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { GraphqlObjectTypeTreeItem } from "./GraphqlObjectTypeTreeItem"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlSubscriptionsTreeItem extends AzureParentTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + public static contextValue: string = 'azureApiManagementGraphqlSubscriptionsList'; + public label: string = "Subscription"; + public contextValue: string = GraphqlSubscriptionsTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlSubscriptionList', 'graphqlSubscriptionList'); + private _nextLink: string | undefined; + + private mutationTypes: GraphQLField[]; + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + mutationTypes: GraphQLField[]) { + super(parent); + this.mutationTypes = mutationTypes; + } + + public hasMoreChildrenImpl(): boolean { + return this._nextLink !== undefined; + } + + public async loadMoreChildrenImpl(clearCache: boolean): Promise { + if (clearCache) { + this._nextLink = undefined; + } + + return this.createTreeItemsWithErrorHandling( + this.mutationTypes, + "invalidApiManagementGraphqlObjectTypes", + async (objectType: GraphQLField) => new GraphqlObjectTypeTreeItem(this, objectType), + (objectType: GraphQLField) => { + return objectType.name; + }); + + } +} From 6caad80e75cb0eae2f549670c038667904d0e0c9 Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Tue, 6 Jul 2021 23:55:18 -0700 Subject: [PATCH 05/11] Add leaf node and test console --- package.json | 6 ++ package.nls.json | 1 + src/commands/showGraphqlAPIQuery.ts | 94 +++++++++++++++++++++++ src/explorer/GraphqlArgFieldTreeItem.ts | 6 +- src/explorer/GraphqlArgsTreeItem.ts | 8 +- src/explorer/GraphqlFieldsLeafTreeItem.ts | 47 ++++++++++++ src/explorer/GraphqlFieldsTreeItem.ts | 23 ++++-- src/explorer/GraphqlObjectTypeTreeItem.ts | 24 ++++-- src/explorer/GraphqlQueriesTreeItem.ts | 6 -- src/extension.ts | 4 + 10 files changed, 199 insertions(+), 20 deletions(-) create mode 100644 src/commands/showGraphqlAPIQuery.ts create mode 100644 src/explorer/GraphqlFieldsLeafTreeItem.ts diff --git a/package.json b/package.json index f36060e..d0863da 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "onCommand:azureApiManagement.showApi", "onCommand:azureApiManagement.showArmApi", "onCommand:azureApiManagement.showArmGraphqlApi", + "onCommand:azureApiManagement.showGraphqlAPIQuery", "onCommand:azureApiManagement.showArmApiOperation", "onCommand:azureApiManagement.showArmProduct", "onCommand:azureApiManagement.showServicePolicy", @@ -222,6 +223,11 @@ "title": "%azureApiManagement.showArmApi%", "category": "Azure API Management" }, + { + "command": "azureApiManagement.showGraphqlAPIQuery", + "title": "%azureApiManagement.showGraphqlAPIQuery%", + "category": "Azure API Management" + }, { "command": "azureApiManagement.showArmGraphqlApi", "title": "%azureApiManagement.showArmGraphqlApi%", diff --git a/package.nls.json b/package.nls.json index b579b05..da3af6c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -12,6 +12,7 @@ "azureApiManagement.showArmApi": "Edit API", "azureApiManagement.showArmGraphqlApi": "Edit Graphql API", "azureApiManagement.showArmApiOperation": "Edit Operation", + "azureApiManagement.showGraphqlAPIQuery": "Edit Graphql API query", "azureApiManagement.showServicePolicy": "Edit Global policy", "azureApiManagement.showApiPolicy": "Edit API policy", "azureApiManagement.showOperationPolicy": "Edit Operation policy", diff --git a/src/commands/showGraphqlAPIQuery.ts b/src/commands/showGraphqlAPIQuery.ts new file mode 100644 index 0000000..317611b --- /dev/null +++ b/src/commands/showGraphqlAPIQuery.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLFieldMap, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType } from 'graphql'; +import requestPromise from 'request-promise'; +import * as vscode from 'vscode'; +import { IActionContext } from "vscode-azureextensionui"; +import { IApiContract } from '../azure/apim/TempApiContract'; +import { SharedAccessToken } from '../constants'; +import { GraphqlObjectTypeTreeItem } from "../explorer/GraphqlObjectTypeTreeItem"; +import { ext } from "../extensionVariables"; +import { createTemporaryFile } from "../utils/fsUtil"; +import { writeToEditor } from '../utils/vscodeUtils'; + +export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: GraphqlObjectTypeTreeItem): Promise { + if (!node) { + node = await ext.tree.showTreeItemPicker(GraphqlObjectTypeTreeItem.contextValue, actionContext); + } + + const query = node.object; + const fileName: string = node.root.apiName.concat("-").concat(query.name).concat(".http"); + const localFilePath: string = await createTemporaryFile(fileName); + let data = ""; + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + const apiContractString = await >requestPromise(`https://${node.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node.root.subscriptionId}/resourceGroups/${node.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node.root.serviceName}/apis/${node.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-unsafe-any + const apiTemp : IApiContract = JSON.parse(apiContractString); + let queryBuilder = ""; + const serviceUrl = apiTemp.properties.serviceUrl; + const args = query.args; + let argParams = ""; + let fieldStr = ""; + for (const arg of args) { + if (arg.type instanceof GraphQLScalarType) { + argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.name).concat(", "); + } else if (arg.type instanceof GraphQLNonNull && arg.type.ofType instanceof GraphQLScalarType) { + argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.ofType.name).concat("!").concat(", "); + } else if (arg.type instanceof GraphQLInputObjectType) { + let fieldsStr = ""; + const argFields = arg.type.getFields(); + for (const argKey of Object.keys(argFields)) { + const argValue = argFields[argKey]; + if (argValue.type instanceof GraphQLScalarType) { + fieldsStr = fieldsStr.concat(`${argValue.name}: ${argValue.type.name}`); + } + } + const subArgs = `${arg.name}: { ${fieldsStr} }`; + argParams = argParams.concat("$").concat(subArgs).concat(", "); + } + } + + if (query.type instanceof GraphQLObjectType) { + fieldStr = fieldStr.concat(getFields(query.type.getFields())); + } else if (query.type instanceof GraphQLList && query.type.ofType instanceof GraphQLObjectType) { + fieldStr = fieldStr.concat(getFields(query.type.ofType.getFields())); + } + + queryBuilder = queryBuilder.concat(` query(${argParams}) \n`).concat(`\t{ ${query.name} (${argParams}) \n\t {${fieldStr}}}`); + + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); + //await fse.writeFile(localFilePath, data); + const textEditor: vscode.TextEditor = await vscode.window.showTextDocument(document); + data = data.concat("Post ").concat(serviceUrl).concat("\n\n").concat(`{ "query": "${queryBuilder}","variables":{}}`); + // tslint:disable-next-line: strict-boolean-expressions + if (!!textEditor) { + await writeToEditor(textEditor, data); + await textEditor.document.save(); + } + vscode.commands.executeCommand('setContext', 'isEditorEnabled', true); +} + +// tslint:disable-next-line: no-any +function getFields(fields: GraphQLFieldMap): string { + let resStr = ""; + for (const fieldKey of Object.keys(fields)) { + const fieldValue = fields[fieldKey]; + resStr = resStr.concat(fieldValue.name); + if (fieldValue.type instanceof GraphQLObjectType) { + resStr.concat(": "); + const childStr = getFields(fieldValue.type.getFields()); + resStr = resStr.concat("{").concat(childStr).concat("}"); + } else { + resStr.concat(", "); + } + } + return resStr; +} diff --git a/src/explorer/GraphqlArgFieldTreeItem.ts b/src/explorer/GraphqlArgFieldTreeItem.ts index c724575..d146257 100644 --- a/src/explorer/GraphqlArgFieldTreeItem.ts +++ b/src/explorer/GraphqlArgFieldTreeItem.ts @@ -19,6 +19,7 @@ export class GraphqlArgFieldTreeItem extends AzureTreeItem { public _label: string; public contextValue: string = GraphqlArgFieldTreeItem.contextValue; public readonly childTypeLabel: string = localize('azureApiManagement.graphqlArgFieldList', 'graphqlArgFieldList'); + public argPath: string[]; private argField: GraphQLInputField; @@ -29,9 +30,12 @@ export class GraphqlArgFieldTreeItem extends AzureTreeItem { constructor( parent: AzureParentTreeItem, // tslint:disable-next-line: no-any - argField: GraphQLInputField) { + argField: GraphQLInputField, + argPath: string[]) { super(parent); this.argField = argField; this._label = this.argField.name; + this.argPath = argPath; + this.argPath.push(this.argField.name); } } diff --git a/src/explorer/GraphqlArgsTreeItem.ts b/src/explorer/GraphqlArgsTreeItem.ts index 64bee5c..c09bcda 100644 --- a/src/explorer/GraphqlArgsTreeItem.ts +++ b/src/explorer/GraphqlArgsTreeItem.ts @@ -20,6 +20,7 @@ export class GraphqlArgsTreeItem extends AzureParentTreeItem { public _label: string; public contextValue: string = GraphqlArgsTreeItem.contextValue; public readonly childTypeLabel: string = localize('azureApiManagement.graphqlArgsList', 'graphqlArgsList'); + public argPath: string[]; private _nextLink: string | undefined; private arg: GraphQLArgument; @@ -31,10 +32,13 @@ export class GraphqlArgsTreeItem extends AzureParentTreeItem { constructor( parent: AzureParentTreeItem, // tslint:disable-next-line: no-any - arg: GraphQLArgument) { + arg: GraphQLArgument, + argPath: string[]) { super(parent); this.arg = arg; this._label = this.arg.name; + this.argPath = argPath; + this.argPath.push(arg.name); } public hasMoreChildrenImpl(): boolean { @@ -52,7 +56,7 @@ export class GraphqlArgsTreeItem extends AzureParentTreeItem { return await this.createTreeItemsWithErrorHandling( fieldValues, "invalidApiManagementGraphqlObjectTypes", - async (objectType: GraphQLInputField) => new GraphqlArgFieldTreeItem(this, objectType), + async (objectType: GraphQLInputField) => new GraphqlArgFieldTreeItem(this, objectType, this.argPath), (objectType: GraphQLInputField) => { return objectType.name; }); diff --git a/src/explorer/GraphqlFieldsLeafTreeItem.ts b/src/explorer/GraphqlFieldsLeafTreeItem.ts new file mode 100644 index 0000000..633c1a4 --- /dev/null +++ b/src/explorer/GraphqlFieldsLeafTreeItem.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLField } from "graphql"; +import { AzureParentTreeItem, AzureTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlFieldsLeafTreeItem extends AzureTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('list'); + } + + public get label() : string { + return this._label; + } + public static contextValue: string = 'azureApiManagementGraphqlFieldsLeafNode'; + public _label: string; + public contextValue: string = GraphqlFieldsLeafTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlFieldsLeafNode', 'graphqlFieldsLeafNode'); + public fieldPath: string[]; + + private field: GraphQLField; + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + field: GraphQLField, + fieldPath: string[]) { + super(parent); + this.field = field; + this._label = this.field.name; + this.fieldPath = fieldPath; + this.fieldPath.push(this.field.name); + } +} diff --git a/src/explorer/GraphqlFieldsTreeItem.ts b/src/explorer/GraphqlFieldsTreeItem.ts index dd46707..5bb54b6 100644 --- a/src/explorer/GraphqlFieldsTreeItem.ts +++ b/src/explorer/GraphqlFieldsTreeItem.ts @@ -7,6 +7,7 @@ import { GraphQLField, GraphQLObjectType } from "graphql"; import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; import { localize } from "../localize"; import { treeUtils } from "../utils/treeUtils"; +import { GraphqlFieldsLeafTreeItem } from "./GraphqlFieldsLeafTreeItem"; import { IApiTreeRoot } from "./IApiTreeRoot"; // tslint:disable: no-any @@ -15,10 +16,15 @@ export class GraphqlFieldsTreeItem extends AzureParentTreeItem { public get iconPath(): { light: string, dark: string } { return treeUtils.getThemedIconPath('list'); } + + public get label() : string { + return this._label; + } public static contextValue: string = 'azureApiManagementGraphqlFieldsList'; public _label: string; public contextValue: string = GraphqlFieldsTreeItem.contextValue; public readonly childTypeLabel: string = localize('azureApiManagement.graphqlFieldsList', 'graphqlFieldsList'); + public fieldPath: string[]; private _nextLink: string | undefined; private field: GraphQLField { [key: string]: any; }>; - public get label() : string { - return this._label; - } - constructor( parent: AzureParentTreeItem, // tslint:disable-next-line: no-any field: GraphQLField) { + }>, + fieldPath: string[]) { super(parent); this.field = field; this._label = this.field.name; + this.fieldPath = fieldPath; + this.fieldPath.push(this.field.name); } public hasMoreChildrenImpl(): boolean { @@ -58,7 +63,13 @@ export class GraphqlFieldsTreeItem extends AzureParentTreeItem { "invalidApiManagementGraphqlObjectTypes", async (objectType: GraphQLField) => new GraphqlFieldsTreeItem(this, objectType), + }>) => { + if (this.field.type instanceof GraphQLObjectType) { + return new GraphqlFieldsTreeItem(this, objectType, this.fieldPath); + } else { + return new GraphqlFieldsLeafTreeItem(this, objectType, this.fieldPath); + } + }, (objectType: GraphQLField) => { diff --git a/src/explorer/GraphqlObjectTypeTreeItem.ts b/src/explorer/GraphqlObjectTypeTreeItem.ts index adaa169..36317cc 100644 --- a/src/explorer/GraphqlObjectTypeTreeItem.ts +++ b/src/explorer/GraphqlObjectTypeTreeItem.ts @@ -7,13 +7,15 @@ import { GraphQLArgument, GraphQLField, GraphQLFieldMap, GraphQLList, GraphQLObj import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; import { treeUtils } from "../utils/treeUtils"; import { GraphqlArgsTreeItem } from "./GraphqlArgsTreeItem"; +import { GraphqlFieldsLeafTreeItem } from "./GraphqlFieldsLeafTreeItem"; import { GraphqlFieldsTreeItem } from "./GraphqlFieldsTreeItem"; -import { IOperationTreeRoot } from "./IOperationTreeRoot"; +import { IApiTreeRoot } from "./IApiTreeRoot"; // tslint:disable: no-any -export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem { +export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem { public static contextValue: string = 'azureApiManagementGraphqlObjectType'; public contextValue: string = GraphqlObjectTypeTreeItem.contextValue; + public readonly commandId: string = 'azureApiManagement.showGraphqlAPIQuery'; private _name: string; private _label: string; @@ -48,7 +50,7 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem new GraphqlArgsTreeItem(this, objectType), + async (objectType: GraphQLArgument) => new GraphqlArgsTreeItem(this, objectType, [this.object.name]), (objectType: GraphQLArgument) => { return objectType.name; }); @@ -64,7 +66,13 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem) => new GraphqlFieldsTreeItem(this, objectType), + }>) => { + if (objectType.type instanceof GraphQLObjectType) { + return new GraphqlFieldsTreeItem(this, objectType, [this.object.name]); + } else { + return new GraphqlFieldsLeafTreeItem(this, objectType, [this.object.name]); + } + }, (objectType: GraphQLField) => { @@ -79,7 +87,13 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem) => new GraphqlFieldsTreeItem(this, objectType), + }>) => { + if (objectType.type instanceof GraphQLObjectType) { + return new GraphqlFieldsTreeItem(this, objectType, [this.object.name]); + } else { + return new GraphqlFieldsLeafTreeItem(this, objectType, [this.object.name]); + } + }, (objectType: GraphQLField) => { diff --git a/src/explorer/GraphqlQueriesTreeItem.ts b/src/explorer/GraphqlQueriesTreeItem.ts index 510d5ef..362b5ac 100644 --- a/src/explorer/GraphqlQueriesTreeItem.ts +++ b/src/explorer/GraphqlQueriesTreeItem.ts @@ -23,15 +23,12 @@ export class GraphqlQueriesTreeItem extends AzureParentTreeItem { private _nextLink: string | undefined; private queryTypes: GraphQLField[]; constructor( parent: AzureParentTreeItem, - // tslint:disable-next-line: no-any queryTypes: GraphQLField[]) { super(parent); @@ -51,15 +48,12 @@ export class GraphqlQueriesTreeItem extends AzureParentTreeItem { this.queryTypes, "invalidApiManagementGraphqlObjectTypes", async (objectType: GraphQLField) => new GraphqlObjectTypeTreeItem(this, objectType), (objectType: GraphQLField) => { return objectType.name; }); - } } diff --git a/src/extension.ts b/src/extension.ts index be55c04..7d64769 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -32,6 +32,7 @@ import { openWorkingFolder } from './commands/openWorkingFolder'; import { revisions } from './commands/revisions'; import { setCustomHostName } from './commands/setCustomHostName'; import { setupWorkingFolder } from './commands/setupWorkingFolder'; +import { showGraphqlAPIQuery } from './commands/showGraphqlAPIQuery'; import { testOperation } from './commands/testOperation'; import { doubleClickDebounceDelay } from './constants'; import { activate } from './debugger/extension'; @@ -51,6 +52,7 @@ import { ServicePolicyEditor } from './explorer/editors/policy/ServicePolicyEdit import { GatewayApisTreeItem } from './explorer/GatewayApisTreeItem'; import { GatewayApiTreeItem } from './explorer/GatewayApiTreeItem'; import { GatewayTreeItem } from './explorer/GatewayTreeItem'; +import { GraphqlObjectTypeTreeItem } from './explorer/GraphqlObjectTypeTreeItem'; import { NamedValuesTreeItem } from './explorer/NamedValuesTreeItem'; import { NamedValueTreeItem } from './explorer/NamedValueTreeItem'; import { OperationPolicyTreeItem } from './explorer/OperationPolicyTreeItem'; @@ -195,6 +197,8 @@ function registerEditors(context: vscode.ExtensionContext) : void { vscode.commands.executeCommand('setContext', 'isEditorEnabled', true); }, doubleClickDebounceDelay); + registerCommand('azureApiManagement.showGraphqlAPIQuery', async (actionContext: IActionContext, node?: GraphqlObjectTypeTreeItem) => await showGraphqlAPIQuery(actionContext, node), doubleClickDebounceDelay); + const servicePolicyEditor: ServicePolicyEditor = new ServicePolicyEditor(); context.subscriptions.push(servicePolicyEditor); registerEvent('azureApiManagement.servicePolicyEditor.onDidSaveTextDocument', From b5a95fd147493f214dd6275a3055f6c343e144da Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Wed, 7 Jul 2021 11:16:52 -0700 Subject: [PATCH 06/11] Small improvements --- src/commands/showGraphqlAPIQuery.ts | 38 ++++++++++++++++----- src/explorer/GraphqlArgsLeafTreeItem.ts | 41 +++++++++++++++++++++++ src/explorer/GraphqlArgsTreeItem.ts | 4 ++- src/explorer/GraphqlFieldsTreeItem.ts | 2 +- src/explorer/GraphqlObjectTypeTreeItem.ts | 11 ++++-- 5 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/explorer/GraphqlArgsLeafTreeItem.ts diff --git a/src/commands/showGraphqlAPIQuery.ts b/src/commands/showGraphqlAPIQuery.ts index 317611b..a1176fe 100644 --- a/src/commands/showGraphqlAPIQuery.ts +++ b/src/commands/showGraphqlAPIQuery.ts @@ -37,24 +37,44 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: const args = query.args; let argParams = ""; let fieldStr = ""; + let queryParams = ""; + let variables = ""; for (const arg of args) { if (arg.type instanceof GraphQLScalarType) { argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.name).concat(", "); + queryParams = queryParams.concat(arg.name).concat(": ").concat(`${arg.name}, `); + variables = variables.concat(`"${arg.name}": "", `); } else if (arg.type instanceof GraphQLNonNull && arg.type.ofType instanceof GraphQLScalarType) { argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.ofType.name).concat("!").concat(", "); + queryParams = queryParams.concat(arg.name).concat(": ").concat(`${arg.name}, `); + variables = variables.concat(`"${arg.name}": "", `); } else if (arg.type instanceof GraphQLInputObjectType) { let fieldsStr = ""; + let queryStr = ""; + let varaibleStr = ""; const argFields = arg.type.getFields(); for (const argKey of Object.keys(argFields)) { const argValue = argFields[argKey]; if (argValue.type instanceof GraphQLScalarType) { - fieldsStr = fieldsStr.concat(`${argValue.name}: ${argValue.type.name}`); + fieldsStr = fieldsStr.concat(`$${argValue.name}: ${argValue.type.name}, `); + queryStr = queryStr.concat(`${argValue.name}: $${argValue.type.name}, `); + varaibleStr = varaibleStr.concat(`"${argValue.name}": "", `); } } - const subArgs = `${arg.name}: { ${fieldsStr} }`; - argParams = argParams.concat("$").concat(subArgs).concat(", "); + fieldsStr = fieldsStr.trimRight(); + fieldsStr = fieldsStr.substring(0, fieldsStr.length - 1); + queryStr = queryStr.trimRight(); + queryStr = queryStr.substring(0, queryStr.length - 1); + varaibleStr = varaibleStr.trimRight(); + varaibleStr = varaibleStr.substring(0, varaibleStr.length - 1); + + argParams = argParams.concat("$").concat(`${arg.name}: { ${fieldsStr} }`).concat(", "); + queryParams = queryParams.concat(`${arg.name}: `).concat("$").concat(`{ ${queryStr} }`).concat(", "); + variables = variables.concat(`"${arg.name}": `).concat(`{ ${varaibleStr} }`).concat(", "); } } + argParams = argParams.trimRight(); + argParams = argParams.substring(0, argParams.length - 1); if (query.type instanceof GraphQLObjectType) { fieldStr = fieldStr.concat(getFields(query.type.getFields())); @@ -62,12 +82,12 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: fieldStr = fieldStr.concat(getFields(query.type.ofType.getFields())); } - queryBuilder = queryBuilder.concat(` query(${argParams}) \n`).concat(`\t{ ${query.name} (${argParams}) \n\t {${fieldStr}}}`); + queryBuilder = queryBuilder.concat(` query(${argParams}) \n`).concat(`\t{ ${query.name} (${queryParams}) \n\t{ ${fieldStr}}}`); const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); //await fse.writeFile(localFilePath, data); const textEditor: vscode.TextEditor = await vscode.window.showTextDocument(document); - data = data.concat("Post ").concat(serviceUrl).concat("\n\n").concat(`{ "query": "${queryBuilder}","variables":{}}`); + data = data.concat("Post ").concat(serviceUrl).concat("\n\n").concat(`{ "query": "${queryBuilder}", \n\t"variables": {${variables}}}`); // tslint:disable-next-line: strict-boolean-expressions if (!!textEditor) { await writeToEditor(textEditor, data); @@ -83,12 +103,14 @@ function getFields(fields: GraphQLFieldMap): string { const fieldValue = fields[fieldKey]; resStr = resStr.concat(fieldValue.name); if (fieldValue.type instanceof GraphQLObjectType) { - resStr.concat(": "); + resStr = resStr.concat(": "); const childStr = getFields(fieldValue.type.getFields()); - resStr = resStr.concat("{").concat(childStr).concat("}"); + resStr = resStr.concat("{ ").concat(childStr).concat("}, "); } else { - resStr.concat(", "); + resStr = resStr.concat(", "); } } + resStr = resStr.trimRight(); + resStr = resStr.substring(0, resStr.length - 1); return resStr; } diff --git a/src/explorer/GraphqlArgsLeafTreeItem.ts b/src/explorer/GraphqlArgsLeafTreeItem.ts new file mode 100644 index 0000000..d32dee4 --- /dev/null +++ b/src/explorer/GraphqlArgsLeafTreeItem.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { GraphQLArgument } from "graphql"; +import { AzureParentTreeItem, AzureTreeItem } from "vscode-azureextensionui"; +import { localize } from "../localize"; +import { treeUtils } from "../utils/treeUtils"; +import { IApiTreeRoot } from "./IApiTreeRoot"; + +// tslint:disable: no-any +export class GraphqlArgsLeafTreeItem extends AzureTreeItem { + + public get iconPath(): { light: string, dark: string } { + return treeUtils.getThemedIconPath('op'); + } + public static contextValue: string = 'azureApiManagementGraphqlArgsLeaf'; + public _label: string; + public contextValue: string = GraphqlArgsLeafTreeItem.contextValue; + public readonly childTypeLabel: string = localize('azureApiManagement.graphqlArgsLeaf', 'graphqlArgsLeaf'); + public argPath: string[]; + + private arg: GraphQLArgument; + + public get label() : string { + return this._label; + } + + constructor( + parent: AzureParentTreeItem, + // tslint:disable-next-line: no-any + arg: GraphQLArgument, + argPath: string[]) { + super(parent); + this.arg = arg; + this._label = this.arg.name; + this.argPath = argPath; + this.argPath.push(arg.name); + } +} diff --git a/src/explorer/GraphqlArgsTreeItem.ts b/src/explorer/GraphqlArgsTreeItem.ts index c09bcda..2c91667 100644 --- a/src/explorer/GraphqlArgsTreeItem.ts +++ b/src/explorer/GraphqlArgsTreeItem.ts @@ -56,7 +56,9 @@ export class GraphqlArgsTreeItem extends AzureParentTreeItem { return await this.createTreeItemsWithErrorHandling( fieldValues, "invalidApiManagementGraphqlObjectTypes", - async (objectType: GraphQLInputField) => new GraphqlArgFieldTreeItem(this, objectType, this.argPath), + async (objectType: GraphQLInputField) => { + return new GraphqlArgFieldTreeItem(this, objectType, this.argPath); + } , (objectType: GraphQLInputField) => { return objectType.name; }); diff --git a/src/explorer/GraphqlFieldsTreeItem.ts b/src/explorer/GraphqlFieldsTreeItem.ts index 5bb54b6..fb33b9b 100644 --- a/src/explorer/GraphqlFieldsTreeItem.ts +++ b/src/explorer/GraphqlFieldsTreeItem.ts @@ -64,7 +64,7 @@ export class GraphqlFieldsTreeItem extends AzureParentTreeItem { async (objectType: GraphQLField) => { - if (this.field.type instanceof GraphQLObjectType) { + if (objectType.type instanceof GraphQLObjectType) { return new GraphqlFieldsTreeItem(this, objectType, this.fieldPath); } else { return new GraphqlFieldsLeafTreeItem(this, objectType, this.fieldPath); diff --git a/src/explorer/GraphqlObjectTypeTreeItem.ts b/src/explorer/GraphqlObjectTypeTreeItem.ts index 36317cc..ec9ffc9 100644 --- a/src/explorer/GraphqlObjectTypeTreeItem.ts +++ b/src/explorer/GraphqlObjectTypeTreeItem.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GraphQLArgument, GraphQLField, GraphQLFieldMap, GraphQLList, GraphQLObjectType, GraphQLOutputType } from "graphql"; +import { GraphQLArgument, GraphQLField, GraphQLFieldMap, GraphQLInputObjectType, GraphQLList, GraphQLObjectType, GraphQLOutputType } from "graphql"; import { AzExtTreeItem, AzureParentTreeItem } from "vscode-azureextensionui"; import { treeUtils } from "../utils/treeUtils"; +import { GraphqlArgsLeafTreeItem } from "./GraphqlArgsLeafTreeItem"; import { GraphqlArgsTreeItem } from "./GraphqlArgsTreeItem"; import { GraphqlFieldsLeafTreeItem } from "./GraphqlFieldsLeafTreeItem"; import { GraphqlFieldsTreeItem } from "./GraphqlFieldsTreeItem"; @@ -50,7 +51,13 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem const argsNodes = await this.createTreeItemsWithErrorHandling( args, "invalidApiManagementGraphqlObjectTypes", - async (objectType: GraphQLArgument) => new GraphqlArgsTreeItem(this, objectType, [this.object.name]), + async (objectType: GraphQLArgument) => { + if (objectType.type instanceof GraphQLInputObjectType) { + return new GraphqlArgsTreeItem(this, objectType, [this.object.name]); + } else { + return new GraphqlArgsLeafTreeItem(this, objectType, [this.object.name]); + } + }, (objectType: GraphQLArgument) => { return objectType.name; }); From 61abdeb1ceb88f76f91b656bb469f8a90b58128d Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Wed, 7 Jul 2021 11:26:45 -0700 Subject: [PATCH 07/11] quick fix --- src/commands/showGraphqlAPIQuery.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/showGraphqlAPIQuery.ts b/src/commands/showGraphqlAPIQuery.ts index a1176fe..945cea1 100644 --- a/src/commands/showGraphqlAPIQuery.ts +++ b/src/commands/showGraphqlAPIQuery.ts @@ -75,6 +75,10 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: } argParams = argParams.trimRight(); argParams = argParams.substring(0, argParams.length - 1); + queryParams = queryParams.trimRight(); + queryParams = queryParams.substring(0, queryParams.length - 1); + variables = variables.trimRight(); + variables = variables.substring(0, variables.length - 1); if (query.type instanceof GraphQLObjectType) { fieldStr = fieldStr.concat(getFields(query.type.getFields())); From fa2f9fdb8de8fbcce7b726c2191423c71cc6f9ca Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Wed, 7 Jul 2021 11:55:08 -0700 Subject: [PATCH 08/11] construct right click operation --- package.json | 5 +++++ package.nls.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d0863da..c05e315 100644 --- a/package.json +++ b/package.json @@ -604,6 +604,11 @@ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementNamedValues", "group": "1@1" }, + { + "command": "azureApiManagement.showGraphqlAPIQuery", + "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementGraphqlObjectType", + "group": "1@1" + }, { "command": "azureApiManagement.deleteNamedValue", "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementNamedValue", diff --git a/package.nls.json b/package.nls.json index da3af6c..c32927e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -12,7 +12,7 @@ "azureApiManagement.showArmApi": "Edit API", "azureApiManagement.showArmGraphqlApi": "Edit Graphql API", "azureApiManagement.showArmApiOperation": "Edit Operation", - "azureApiManagement.showGraphqlAPIQuery": "Edit Graphql API query", + "azureApiManagement.showGraphqlAPIQuery": "Test Grapql Query", "azureApiManagement.showServicePolicy": "Edit Global policy", "azureApiManagement.showApiPolicy": "Edit API policy", "azureApiManagement.showOperationPolicy": "Edit Operation policy", From e378f057a28a8eaf30dc2219892e1f4e92ac8078 Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Thu, 8 Jul 2021 16:26:23 -0700 Subject: [PATCH 09/11] small fixes --- src/commands/showGraphqlAPIQuery.ts | 8 ++++---- src/test.http | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/showGraphqlAPIQuery.ts b/src/commands/showGraphqlAPIQuery.ts index 945cea1..a4f6d19 100644 --- a/src/commands/showGraphqlAPIQuery.ts +++ b/src/commands/showGraphqlAPIQuery.ts @@ -33,7 +33,7 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: // tslint:disable-next-line: no-unsafe-any const apiTemp : IApiContract = JSON.parse(apiContractString); let queryBuilder = ""; - const serviceUrl = apiTemp.properties.serviceUrl; + const serviceUrl = `https://${node.root.serviceName}.azure-api.net/${apiTemp.properties.path}`; const args = query.args; let argParams = ""; let fieldStr = ""; @@ -42,11 +42,11 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: for (const arg of args) { if (arg.type instanceof GraphQLScalarType) { argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.name).concat(", "); - queryParams = queryParams.concat(arg.name).concat(": ").concat(`${arg.name}, `); + queryParams = queryParams.concat(arg.name).concat(": $").concat(`${arg.name}, `); variables = variables.concat(`"${arg.name}": "", `); } else if (arg.type instanceof GraphQLNonNull && arg.type.ofType instanceof GraphQLScalarType) { argParams = argParams.concat("$").concat(arg.name).concat(": ").concat(arg.type.ofType.name).concat("!").concat(", "); - queryParams = queryParams.concat(arg.name).concat(": ").concat(`${arg.name}, `); + queryParams = queryParams.concat(arg.name).concat(": $").concat(`${arg.name}, `); variables = variables.concat(`"${arg.name}": "", `); } else if (arg.type instanceof GraphQLInputObjectType) { let fieldsStr = ""; @@ -69,7 +69,7 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: varaibleStr = varaibleStr.substring(0, varaibleStr.length - 1); argParams = argParams.concat("$").concat(`${arg.name}: { ${fieldsStr} }`).concat(", "); - queryParams = queryParams.concat(`${arg.name}: `).concat("$").concat(`{ ${queryStr} }`).concat(", "); + queryParams = queryParams.concat(`${arg.name}: `).concat(`{ ${queryStr} }`).concat(", "); variables = variables.concat(`"${arg.name}": `).concat(`{ ${varaibleStr} }`).concat(", "); } } diff --git a/src/test.http b/src/test.http index 5ff5810..116fc2c 100644 --- a/src/test.http +++ b/src/test.http @@ -1,4 +1,4 @@ -POST https://api.spacex.land/graphql +POST https://alzasloneuap03.azure-api.net/graphql-api -{"query":"{ launches { id, mission_name }}","variables":{}} \ No newline at end of file +{ "query": "query($id: ID!) { ship(id: $id) { id, active } }", "variables": { "id": "GOMSTREE" } } From d4c0ebba2377239a36a162fbd29f9461d6da823f Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Mon, 12 Jul 2021 22:34:23 -0700 Subject: [PATCH 10/11] Add commands --- package.json | 22 ++++++ package.nls.json | 2 + src/commands/importGraphqlSchema.ts | 76 ++++++++++++++++++ src/explorer/ApisTreeItem.ts | 37 --------- .../GraphqlApiSchemaEditor.ts | 79 +++++++++++++++++++ src/extension.ts | 19 +++++ 6 files changed, 198 insertions(+), 37 deletions(-) create mode 100644 src/commands/importGraphqlSchema.ts create mode 100644 src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts diff --git a/package.json b/package.json index c05e315..cd48d7d 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "onCommand:azureApiManagement.importOpenApiByLink", "onCommand:azureApiManagement.importGraphqlAPIByLink", "onCommand:azureApiManagement.testOperation", + "onCommand:azureApiManagement.importGraphqlSchemaByFile", "onCommand:azureApiManagement.openInPortal", "onCommand:azureApiManagement.showApi", "onCommand:azureApiManagement.showArmApi", @@ -52,6 +53,7 @@ "onCommand:azureApiManagement.showServicePolicy", "onCommand:azureApiManagement.showApiPolicy", "onCommand:azureApiManagement.showOperationPolicy", + "onCommand:azureApiManagement.showGraphqlSchema", "onCommand:azureApiManagement.createNamedValue", "onCommand:azureApiManagement.deleteNamedValue", "onCommand:azureApiManagement.updateNamedValue", @@ -182,6 +184,16 @@ "title": "%azureApiManagement.testOperation%", "category": "Azure API Management" }, + { + "command": "azureApiManagement.importGraphqlSchemaByFile", + "title": "%azureApiManagement.importGraphqlSchemaByFile%", + "category": "Azure API Management" + }, + { + "command": "azureApiManagement.showGraphqlSchema", + "title": "%azureApiManagement.showGraphqlSchema%", + "category": "Azure API Management" + }, { "command": "azureApiManagement.openInPortal", "title": "%azureApiManagement.openInPortal%", @@ -609,6 +621,16 @@ "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementGraphqlObjectType", "group": "1@1" }, + { + "command": "azureApiManagement.importGraphqlSchemaByFile", + "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementGraphql", + "group": "1@1" + }, + { + "command": "azureApiManagement.showGraphqlSchema", + "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementGraphql", + "group": "1@2" + }, { "command": "azureApiManagement.deleteNamedValue", "when": "view == azureApiManagementExplorer && viewItem == azureApiManagementNamedValue", diff --git a/package.nls.json b/package.nls.json index c32927e..8b18f9a 100644 --- a/package.nls.json +++ b/package.nls.json @@ -19,6 +19,8 @@ "azureApiManagement.deleteApi": "Delete API", "azureApiManagement.deleteOperation": "Delete Operation", "azureApiManagement.testOperation": "Test Operation", + "azureApiManagement.importGraphqlSchemaByFile": "Import Graphql API schema file", + "azureApiManagement.showGraphqlSchema": "Open Schema", "azureApiManagement.advancedCreationDescription":"Enables advanced creation of Azure API Management Instance, which will prompt for several additional values instead of using a default.", "azureApiManagement.createNamedValue": "Create Named value", "azureApiManagement.deleteNamedValue": "Delete Named value", diff --git a/src/commands/importGraphqlSchema.ts b/src/commands/importGraphqlSchema.ts new file mode 100644 index 0000000..8e934cf --- /dev/null +++ b/src/commands/importGraphqlSchema.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fse from 'fs-extra'; +import requestPromise from 'request-promise'; +import { OpenDialogOptions, ProgressLocation, Uri, window, workspace } from "vscode"; +import { IActionContext } from "vscode-azureextensionui"; +import { SharedAccessToken } from '../constants'; +import { GraphqlApiTreeItem } from "../explorer/GraphqlApiTreeItem"; +import { ServiceTreeItem } from "../explorer/ServiceTreeItem"; +import { ext } from "../extensionVariables"; +import { localize } from '../localize'; + +// tslint:disable-next-line: export-name +export async function importGraphqlSchemaByFile(actionContext: IActionContext, node?: GraphqlApiTreeItem): Promise { + if (!node) { + const serviceNode = await ext.tree.showTreeItemPicker(ServiceTreeItem.contextValue, actionContext); + node = serviceNode; + } + + const uris = await askSchemaDocument(); + const uri = uris[0]; + // tslint:disable-next-line: no-unsafe-any + const fileContent = await fse.readFile(uri.fsPath); + const documentString = fileContent.toString(); + const body = { + properties: { + contentType: "application/vnd.ms-azure-apim.graphql.schema", + document: { + value: documentString + } + } + }; + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "PUT", + headers: { + Authorization: SharedAccessToken + }, + body: JSON.stringify(body) + }; + window.withProgress( + { + location: ProgressLocation.Notification, + title: localize("addSchema", `Add Graphql Schema to Graphql API ${node.root.apiName} ...`), + cancellable: false + }, + // tslint:disable-next-line:no-non-null-assertion + async () => { + await >requestPromise( + `https://${node?.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node?.root.subscriptionId}/resourceGroups/${node?.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node?.root.serviceName}/apis/${node?.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); + } + ).then(async () => { + // tslint:disable-next-line:no-non-null-assertion + await node!.refresh(actionContext); + window.showInformationMessage(localize("addSchema", `Add Schema to graphql API '${node?.root.apiName}' succesfully.`)); + }); +} + +async function askSchemaDocument(): Promise { + const openDialogOptions: OpenDialogOptions = { + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + openLabel: "Import", + filters: { + JSON: ["js"] + } + }; + const rootPath = workspace.rootPath; + if (rootPath) { + openDialogOptions.defaultUri = Uri.file(rootPath); + } + return await ext.ui.showOpenDialog(openDialogOptions); +} diff --git a/src/explorer/ApisTreeItem.ts b/src/explorer/ApisTreeItem.ts index 70bdcf3..b14a85f 100644 --- a/src/explorer/ApisTreeItem.ts +++ b/src/explorer/ApisTreeItem.ts @@ -57,21 +57,6 @@ export class ApisTreeItem extends AzureParentTreeItem { this._nextLink = apiCollection1.nextLink; apisToLoad = apiCollection1.map((s) => s).filter(s => apiUtil.isNotApiRevision(s)); - /*const client: ServiceClient = await createGenericClient(this.root.credentials); - // tslint:disable-next-line: no-unsafe-any - const apiCollection: ApiManagementModels.ApiCollection = (await client.sendRequest({ - method: "GET", - url: `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`, - headers: { - Authorization: "" - } - })).parsedBody;*/ - /* - const webResource = new WebResource(); - webResource.url = `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`; - webResource.method = "GET"; - webResource.headers.set("Authorization", ""); - const apiCollection: ApiManagementModels.ApiCollection = await sendRequest(webResource);*/ const requestOptions : requestPromise.RequestPromiseOptions = { method: "GET", headers: { @@ -138,28 +123,6 @@ export class ApisTreeItem extends AzureParentTreeItem { return collection; } - /* - public convertToTempApiContract(apiCollectionTemp: IApiContract[]): ApiContract[] { - const apiCollection: ApiContract[] = []; - for (const api of apiCollectionTemp) { - const curApi : ApiContract = { - description: api.properties.description, - authenticationSettings: api.properties.authenticationSettings, - subscriptionKeyParameterNames: api.properties.subscriptionKeyParameterNames, - apiType: api.properties.type, - apiRevision: api.properties.apiRevision, - isCurrent: api.properties.isCurrent, - path: api.properties.path, - displayName: api.properties.displayName, - protocols: api.properties.protocols, - serviceUrl: api.properties.serviceUrl, - subscriptionRequired: api.properties.subscriptionRequired - }; - apiCollection.push(curApi); - } - return apiCollection; - }*/ - public async createChildImpl(context: IApiTreeItemContext): Promise { if (context.document) { return await this.createApiFromOpenApi(context.showCreatingTreeItem, context.apiName, context.document); diff --git a/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts b/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts new file mode 100644 index 0000000..a1ba550 --- /dev/null +++ b/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import requestPromise from "request-promise"; +import { ProgressLocation, window } from "vscode"; +import { SharedAccessToken, showSavePromptConfigKey } from "../../../constants"; +import { localize } from "../../../localize"; +import { GraphqlApiTreeItem } from "../../GraphqlApiTreeItem"; +import { Editor } from "../Editor"; + +// tslint:disable: no-unsafe-any +export class GraphqlApiSchemaEditor extends Editor { + constructor() { + super(showSavePromptConfigKey); + } + + public async getData(context: GraphqlApiTreeItem): Promise { + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "GET", + headers: { + Authorization: SharedAccessToken + } + }; + + const schemasString = await >requestPromise( + `https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); + + return JSON.parse(schemasString).properties.document.value; + } + + public async updateData(context: GraphqlApiTreeItem, data: string): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: localize("updateAPISchema", `Applying changes to Graphql API schema '${context.root.apiName}' in API Management instance ${context.root.serviceName}...`), + cancellable: false + }, + async () => { + const body = { + properties: { + contentType: "application/vnd.ms-azure-apim.graphql.schema", + document: { + value: data + } + } + }; + + const requestOptions : requestPromise.RequestPromiseOptions = { + method: "PUT", + headers: { + Authorization: SharedAccessToken + }, + body: JSON.stringify(body) + }; + await >requestPromise( + `https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); + } + ).then(async () => { + window.showInformationMessage(localize("updateAPISchema", `Changes to API '${context.apiContract.name}' were succefully uploaded to cloud.`)); + //await context.refresh(); + return this.getData(context); + }); + } + + public async getFilename(context: GraphqlApiTreeItem): Promise { + return `${context.root.serviceName}-${context.root.apiName}-schema.js`; + } + public async getDiffFilename(context: GraphqlApiTreeItem): Promise { + return `${context.root.serviceName}-${context.root.apiName}-schema-temp.js`; + } + public async getSaveConfirmationText(context: GraphqlApiTreeItem): Promise { + return localize("", `Saving will update the Grapql API Schema '${context.apiContract.name}'.`); + } + public async getSize(_context: GraphqlApiTreeItem): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/src/extension.ts b/src/extension.ts index 7d64769..e71bf02 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { generateFunctions } from './commands/generateFunctions'; import { generateNewGatewayToken } from './commands/generateNewGatewayToken'; import { importFunctionApp } from './commands/importFunctionApp/importFunctionApp'; import { importFunctionAppToApi } from './commands/importFunctionApp/importFunctionApp'; +import { importGraphqlSchemaByFile } from './commands/importGraphqlSchema'; import { importOpenApi } from './commands/importOpenApi'; import { importWebApp, importWebAppToApi } from './commands/importWebApp/importWebApp'; import { createNamedValue, updateNamedValue } from './commands/manageNamedValue'; @@ -44,6 +45,7 @@ import { AzureAccountTreeItem } from './explorer/AzureAccountTreeItem'; import { ApiResourceEditor } from './explorer/editors/arm/ApiResourceEditor'; import { OperationResourceEditor } from './explorer/editors/arm/OperationResourceEditor'; import { ProductResourceEditor } from './explorer/editors/arm/ProductResourceEditor'; +import { GraphqlApiSchemaEditor } from './explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor'; import { OpenApiEditor } from './explorer/editors/openApi/OpenApiEditor'; import { ApiPolicyEditor } from './explorer/editors/policy/ApiPolicyEditor'; import { OperationPolicyEditor } from './explorer/editors/policy/OperationPolicyEditor'; @@ -52,6 +54,7 @@ import { ServicePolicyEditor } from './explorer/editors/policy/ServicePolicyEdit import { GatewayApisTreeItem } from './explorer/GatewayApisTreeItem'; import { GatewayApiTreeItem } from './explorer/GatewayApiTreeItem'; import { GatewayTreeItem } from './explorer/GatewayTreeItem'; +import { GraphqlApiTreeItem } from './explorer/GraphqlApiTreeItem'; import { GraphqlObjectTypeTreeItem } from './explorer/GraphqlObjectTypeTreeItem'; import { NamedValuesTreeItem } from './explorer/NamedValuesTreeItem'; import { NamedValueTreeItem } from './explorer/NamedValueTreeItem'; @@ -127,6 +130,7 @@ function registerCommands(tree: AzExtTreeDataProvider): void { registerCommand('azureApiManagement.generateKubernetesDeployment', generateKubernetesDeployment); registerCommand('azureApiManagement.generateNewGatewayToken', generateNewGatewayToken); registerCommand('azureApiManagement.debugPolicy', debugPolicy); + registerCommand('azureApiManagement.importGraphqlSchemaByFile', importGraphqlSchemaByFile); registerCommand('azureApiManagement.openExtensionWorkspaceFolder', openWorkingFolder); registerCommand('azureApiManagement.initializeExtensionWorkspaceFolder', setupWorkingFolder); @@ -184,6 +188,21 @@ function registerEditors(context: vscode.ExtensionContext) : void { vscode.commands.executeCommand('setContext', 'isEditorEnabled', true); }, doubleClickDebounceDelay); + const graphqlApiSchemaEditor: GraphqlApiSchemaEditor = new GraphqlApiSchemaEditor(); + context.subscriptions.push(graphqlApiSchemaEditor); + registerEvent('azureApiManagement.graphqlApiSchemaEditor.onDidSaveTextDocument', + vscode.workspace.onDidSaveTextDocument, + async (actionContext: IActionContext, doc: vscode.TextDocument) => { + await graphqlApiSchemaEditor.onDidSaveTextDocument(actionContext, context.globalState, doc); + }); + registerCommand('azureApiManagement.showGraphqlSchema', async (actionContext: IActionContext, node?: GraphqlApiTreeItem) => { + if (!node) { + node = await ext.tree.showTreeItemPicker(GraphqlApiTreeItem.contextValue, actionContext); + } + await graphqlApiSchemaEditor.showEditor(node); + vscode.commands.executeCommand('setContext', 'isEditorEnabled', true); + }, doubleClickDebounceDelay); + const apiEditor: OpenApiEditor = new OpenApiEditor(); context.subscriptions.push(apiEditor); registerEvent('azureApiManagement.apiEditor.onDidSaveTextDocument', From f295ae5e5b646ee208b7111b2b00141eb6043404 Mon Sep 17 00:00:00 2001 From: Rupeng Liu Date: Mon, 30 Aug 2021 15:15:16 -0700 Subject: [PATCH 11/11] small fixes --- resources/dark/leafnode.svg | 3 ++ resources/dark/mutation.svg | 3 ++ resources/dark/querylist.svg | 3 ++ resources/dark/treelist.svg | 4 +++ resources/light/leafnode.svg | 3 ++ resources/light/mutation.svg | 3 ++ resources/light/querylist.svg | 3 ++ resources/light/treelist.svg | 4 +++ src/commands/createGraphqlApi.ts | 26 ++++++++------ src/commands/importGraphqlSchema.ts | 23 ++++++------ src/commands/showGraphqlAPIQuery.ts | 19 +++++----- src/constants.ts | 2 -- src/explorer/ApisTreeItem.ts | 20 +++++------ src/explorer/GraphqlApiTreeItem.ts | 27 +++++++------- src/explorer/GraphqlFieldsLeafTreeItem.ts | 2 +- src/explorer/GraphqlFieldsTreeItem.ts | 2 +- src/explorer/GraphqlMutationsTreeItem.ts | 2 +- src/explorer/GraphqlObjectTypeTreeItem.ts | 2 +- src/explorer/GraphqlQueriesTreeItem.ts | 2 +- src/explorer/editors/arm/ApiResourceEditor.ts | 33 ++++++++--------- .../GraphqlApiSchemaEditor.ts | 35 ++++++++----------- .../editors/policy/ApiPolicyEditor.ts | 34 +++++++++--------- 22 files changed, 134 insertions(+), 121 deletions(-) create mode 100644 resources/dark/leafnode.svg create mode 100644 resources/dark/mutation.svg create mode 100644 resources/dark/querylist.svg create mode 100644 resources/dark/treelist.svg create mode 100644 resources/light/leafnode.svg create mode 100644 resources/light/mutation.svg create mode 100644 resources/light/querylist.svg create mode 100644 resources/light/treelist.svg diff --git a/resources/dark/leafnode.svg b/resources/dark/leafnode.svg new file mode 100644 index 0000000..e2971d4 --- /dev/null +++ b/resources/dark/leafnode.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/dark/mutation.svg b/resources/dark/mutation.svg new file mode 100644 index 0000000..95d8beb --- /dev/null +++ b/resources/dark/mutation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/dark/querylist.svg b/resources/dark/querylist.svg new file mode 100644 index 0000000..748c55c --- /dev/null +++ b/resources/dark/querylist.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/dark/treelist.svg b/resources/dark/treelist.svg new file mode 100644 index 0000000..dcf7226 --- /dev/null +++ b/resources/dark/treelist.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/light/leafnode.svg b/resources/light/leafnode.svg new file mode 100644 index 0000000..c339a56 --- /dev/null +++ b/resources/light/leafnode.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/light/mutation.svg b/resources/light/mutation.svg new file mode 100644 index 0000000..ce678aa --- /dev/null +++ b/resources/light/mutation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/light/querylist.svg b/resources/light/querylist.svg new file mode 100644 index 0000000..77ad82c --- /dev/null +++ b/resources/light/querylist.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/resources/light/treelist.svg b/resources/light/treelist.svg new file mode 100644 index 0000000..a461b02 --- /dev/null +++ b/resources/light/treelist.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/commands/createGraphqlApi.ts b/src/commands/createGraphqlApi.ts index 0d04789..0d7e3b6 100644 --- a/src/commands/createGraphqlApi.ts +++ b/src/commands/createGraphqlApi.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ //import { ServiceClient } from "@azure/ms-rest-js"; -import requestPromise from "request-promise"; +import { ServiceClient } from "@azure/ms-rest-js"; import { ProgressLocation, window } from "vscode"; +import { createGenericClient } from 'vscode-azureextensionui'; import { IActionContext } from "../../extension.bundle"; -import { SharedAccessToken } from "../constants"; import { ApisTreeItem, IApiTreeItemContext } from "../explorer/ApisTreeItem"; import { ServiceTreeItem } from "../explorer/ServiceTreeItem"; import { ext } from "../extensionVariables"; @@ -45,21 +45,25 @@ export async function createGraphqlApi(context: IActionContext & Partial { - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(node?.root.credentials); + const response = await client.sendRequest({ method: "PUT", - headers: { - Authorization: SharedAccessToken - }, - body: JSON.stringify(body) - }; - // tslint:disable-next-line: no-non-null-assertion - await >requestPromise(`https://${node!.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node!.root.subscriptionId}/resourceGroups/${node!.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node!.root.serviceName}/apis/${apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + body: body, + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${node!.root.subscriptionId}/resourceGroups/${node!.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node!.root.serviceName}/apis/${apiName}?api-version=2021-04-01-preview` + }); + + if (response.status !== 200 && response.status !== 201 && response.status !== 202) { + // tslint:disable-next-line: no-non-null-assertion + throw new Error(localize("", response.bodyAsText!)); + } + // tslint:disable-next-line: no-console + console.log(response); } ).then(async () => { window.showInformationMessage(localize("", `New Graphql API has been created!`)); node?.refresh(context); }); - } async function askLink() : Promise { diff --git a/src/commands/importGraphqlSchema.ts b/src/commands/importGraphqlSchema.ts index 8e934cf..8f607b3 100644 --- a/src/commands/importGraphqlSchema.ts +++ b/src/commands/importGraphqlSchema.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ServiceClient } from '@azure/ms-rest-js'; import * as fse from 'fs-extra'; -import requestPromise from 'request-promise'; import { OpenDialogOptions, ProgressLocation, Uri, window, workspace } from "vscode"; -import { IActionContext } from "vscode-azureextensionui"; -import { SharedAccessToken } from '../constants'; +import { createGenericClient, IActionContext } from "vscode-azureextensionui"; import { GraphqlApiTreeItem } from "../explorer/GraphqlApiTreeItem"; import { ServiceTreeItem } from "../explorer/ServiceTreeItem"; import { ext } from "../extensionVariables"; @@ -33,13 +32,6 @@ export async function importGraphqlSchemaByFile(actionContext: IActionContext, n } } }; - const requestOptions : requestPromise.RequestPromiseOptions = { - method: "PUT", - headers: { - Authorization: SharedAccessToken - }, - body: JSON.stringify(body) - }; window.withProgress( { location: ProgressLocation.Notification, @@ -48,9 +40,14 @@ export async function importGraphqlSchemaByFile(actionContext: IActionContext, n }, // tslint:disable-next-line:no-non-null-assertion async () => { - await >requestPromise( - `https://${node?.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node?.root.subscriptionId}/resourceGroups/${node?.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node?.root.serviceName}/apis/${node?.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); - } + const client: ServiceClient = await createGenericClient(node?.root.credentials); + await client.sendRequest({ + method: "PUT", + body: body, + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${node!.root.subscriptionId}/resourceGroups/${node!.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node!.root.serviceName}/apis/${node?.root.apiName}/schemas/default?api-version=2021-04-01-preview` + }); + } ).then(async () => { // tslint:disable-next-line:no-non-null-assertion await node!.refresh(actionContext); diff --git a/src/commands/showGraphqlAPIQuery.ts b/src/commands/showGraphqlAPIQuery.ts index a4f6d19..dc33a98 100644 --- a/src/commands/showGraphqlAPIQuery.ts +++ b/src/commands/showGraphqlAPIQuery.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ServiceClient } from '@azure/ms-rest-js'; import { GraphQLFieldMap, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType } from 'graphql'; -import requestPromise from 'request-promise'; import * as vscode from 'vscode'; -import { IActionContext } from "vscode-azureextensionui"; +import { createGenericClient, IActionContext } from "vscode-azureextensionui"; import { IApiContract } from '../azure/apim/TempApiContract'; -import { SharedAccessToken } from '../constants'; import { GraphqlObjectTypeTreeItem } from "../explorer/GraphqlObjectTypeTreeItem"; import { ext } from "../extensionVariables"; import { createTemporaryFile } from "../utils/fsUtil"; @@ -23,15 +22,15 @@ export async function showGraphqlAPIQuery(actionContext: IActionContext, node?: const fileName: string = node.root.apiName.concat("-").concat(query.name).concat(".http"); const localFilePath: string = await createTemporaryFile(fileName); let data = ""; - const requestOptions : requestPromise.RequestPromiseOptions = { + + const client: ServiceClient = await createGenericClient(node?.root.credentials); + const apiContractString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const apiContractString = await >requestPromise(`https://${node.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${node.root.subscriptionId}/resourceGroups/${node.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node.root.serviceName}/apis/${node.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${node.root.subscriptionId}/resourceGroups/${node.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${node.root.serviceName}/apis/${node.root.apiName}?api-version=2021-04-01-preview` + }); // tslint:disable-next-line: no-unsafe-any - const apiTemp : IApiContract = JSON.parse(apiContractString); + const apiTemp : IApiContract = apiContractString.parsedBody; let queryBuilder = ""; const serviceUrl = `https://${node.root.serviceName}.azure-api.net/${apiTemp.properties.path}`; const args = query.args; diff --git a/src/constants.ts b/src/constants.ts index bcf0cf5..e48ad5b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -34,8 +34,6 @@ export const apimApiVersion = "2019-01-01"; export const maxTokenValidTimeSpan = 29; export const gatewayHostName = "CustomerHostName"; -export const SharedAccessToken = ""; - export enum GatewayKeyType { primary = "primary", secondary = "secondary" diff --git a/src/explorer/ApisTreeItem.ts b/src/explorer/ApisTreeItem.ts index b14a85f..e254f72 100644 --- a/src/explorer/ApisTreeItem.ts +++ b/src/explorer/ApisTreeItem.ts @@ -5,10 +5,10 @@ import { ApiManagementModels } from "@azure/arm-apimanagement"; import { ApiContract, ApiCreateOrUpdateParameter } from "@azure/arm-apimanagement/src/models"; -import requestPromise from 'request-promise'; -import { AzExtTreeItem, AzureParentTreeItem, ICreateChildImplContext } from "vscode-azureextensionui"; +import { ServiceClient } from "@azure/ms-rest-js"; +import { AzExtTreeItem, AzureParentTreeItem, createGenericClient, ICreateChildImplContext } from "vscode-azureextensionui"; import { IApiContract } from "../azure/apim/TempApiContract"; -import { SharedAccessToken, topItemCount } from "../constants"; +import { topItemCount } from "../constants"; import { localize } from "../localize"; import { IOpenApiImportObject } from "../openApi/OpenApiImportObject"; import { apiUtil } from "../utils/apiUtil"; @@ -57,15 +57,15 @@ export class ApisTreeItem extends AzureParentTreeItem { this._nextLink = apiCollection1.nextLink; apisToLoad = apiCollection1.map((s) => s).filter(s => apiUtil.isNotApiRevision(s)); - const requestOptions : requestPromise.RequestPromiseOptions = { + + const client: ServiceClient = await createGenericClient(this.root.credentials); + const apiCollectionString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const apiCollectionString = await >requestPromise(`https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-non-null-assertion + url: `${this.root.environment.resourceManagerEndpointUrl}/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis?api-version=2021-04-01-preview` + }); // tslint:disable-next-line: no-unsafe-any - const apiCollectionTemp : IApiContract[] = JSON.parse(apiCollectionString).value; + const apiCollectionTemp : IApiContract[] = apiCollectionString.parsedBody.value; apisToLoad2 = this.findGraphqlApis(apiCollectionTemp); } diff --git a/src/explorer/GraphqlApiTreeItem.ts b/src/explorer/GraphqlApiTreeItem.ts index ad40971..7a6162f 100644 --- a/src/explorer/GraphqlApiTreeItem.ts +++ b/src/explorer/GraphqlApiTreeItem.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ServiceClient } from "@azure/ms-rest-js"; import { buildSchema, GraphQLField, GraphQLFieldMap, GraphQLNamedType, GraphQLObjectType, GraphQLSchema } from "graphql"; -import requestPromise from "request-promise"; -import { AzureParentTreeItem, AzureTreeItem, ISubscriptionContext } from "vscode-azureextensionui"; +import { AzureParentTreeItem, AzureTreeItem, createGenericClient, ISubscriptionContext } from "vscode-azureextensionui"; import { IApiContract } from "../azure/apim/TempApiContract"; import { TempSchema } from "../azure/apim/TempSchema"; -import { SharedAccessToken } from "../constants"; import { nonNullProp } from "../utils/nonNull"; import { treeUtils } from "../utils/treeUtils"; import { ApiPolicyTreeItem } from "./ApiPolicyTreeItem"; import { GraphqlMutationsTreeItem } from "./GraphqlMutationsTreeItem"; import { GraphqlQueriesTreeItem } from "./GraphqlQueriesTreeItem"; -import { GraphqlSubscriptionsTreeItem } from "./GraphqlSubscriptionsTreeItem"; +//import { GraphqlSubscriptionsTreeItem } from "./GraphqlSubscriptionsTreeItem"; import { IApiTreeRoot } from "./IApiTreeRoot"; import { IServiceTreeRoot } from "./IServiceTreeRoot"; @@ -30,7 +29,7 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { private _root: IApiTreeRoot; private _queriesTreeItem: GraphqlQueriesTreeItem; private _mutationsTreeItem: GraphqlMutationsTreeItem; - private _subscriptionsTreeItem: GraphqlSubscriptionsTreeItem; + //private _subscriptionsTreeItem: GraphqlSubscriptionsTreeItem; constructor( parent: AzureParentTreeItem, @@ -67,17 +66,15 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { } public async loadMoreChildrenImpl(): Promise[]> { - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(this.root.credentials); + const schemasString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const schemasString = await >requestPromise( - `https://${this.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis/${this.root.apiName}/schemas?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${this.root.subscriptionId}/resourceGroups/${this.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${this.root.serviceName}/apis/${this.root.apiName}/schemas?api-version=2021-04-01-preview` + }); // tslint:disable: no-unsafe-any // tslint:disable-next-line: no-unnecessary-local-variable - const schemas : TempSchema[] = JSON.parse(schemasString).value; + const schemas : TempSchema[] = schemasString.parsedBody.value; const valueList: GraphQLSchema[] = []; // tslint:disable-next-line: no-any @@ -122,9 +119,11 @@ export class GraphqlApiTreeItem extends AzureParentTreeItem { } this._queriesTreeItem = new GraphqlQueriesTreeItem(this, queryTypes); this._mutationsTreeItem = new GraphqlMutationsTreeItem(this, mutationTypes); - this._subscriptionsTreeItem = new GraphqlSubscriptionsTreeItem(this, subscriptionTypes); + /*this._subscriptionsTreeItem = new GraphqlSubscriptionsTreeItem(this, subscriptionTypes); return [this._queriesTreeItem, this._mutationsTreeItem, this._subscriptionsTreeItem, this.policyTreeItem]; + */ + return [this._queriesTreeItem, this._mutationsTreeItem, this.policyTreeItem]; } public hasMoreChildrenImpl(): boolean { diff --git a/src/explorer/GraphqlFieldsLeafTreeItem.ts b/src/explorer/GraphqlFieldsLeafTreeItem.ts index 633c1a4..b8414eb 100644 --- a/src/explorer/GraphqlFieldsLeafTreeItem.ts +++ b/src/explorer/GraphqlFieldsLeafTreeItem.ts @@ -13,7 +13,7 @@ import { IApiTreeRoot } from "./IApiTreeRoot"; export class GraphqlFieldsLeafTreeItem extends AzureTreeItem { public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); + return treeUtils.getThemedIconPath('leafnode'); } public get label() : string { diff --git a/src/explorer/GraphqlFieldsTreeItem.ts b/src/explorer/GraphqlFieldsTreeItem.ts index fb33b9b..7959ebb 100644 --- a/src/explorer/GraphqlFieldsTreeItem.ts +++ b/src/explorer/GraphqlFieldsTreeItem.ts @@ -14,7 +14,7 @@ import { IApiTreeRoot } from "./IApiTreeRoot"; export class GraphqlFieldsTreeItem extends AzureParentTreeItem { public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); + return treeUtils.getThemedIconPath('treelist'); } public get label() : string { diff --git a/src/explorer/GraphqlMutationsTreeItem.ts b/src/explorer/GraphqlMutationsTreeItem.ts index d1202c3..9ce8305 100644 --- a/src/explorer/GraphqlMutationsTreeItem.ts +++ b/src/explorer/GraphqlMutationsTreeItem.ts @@ -14,7 +14,7 @@ import { IApiTreeRoot } from "./IApiTreeRoot"; export class GraphqlMutationsTreeItem extends AzureParentTreeItem { public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); + return treeUtils.getThemedIconPath('mutation'); } public static contextValue: string = 'azureApiManagementGraphqlMutationsList'; public label: string = "Mutation"; diff --git a/src/explorer/GraphqlObjectTypeTreeItem.ts b/src/explorer/GraphqlObjectTypeTreeItem.ts index ec9ffc9..beb5617 100644 --- a/src/explorer/GraphqlObjectTypeTreeItem.ts +++ b/src/explorer/GraphqlObjectTypeTreeItem.ts @@ -22,7 +22,7 @@ export class GraphqlObjectTypeTreeItem extends AzureParentTreeItem private _label: string; public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('op'); + return treeUtils.getThemedIconPath('treelist'); } public get label() : string { diff --git a/src/explorer/GraphqlQueriesTreeItem.ts b/src/explorer/GraphqlQueriesTreeItem.ts index 362b5ac..0018bec 100644 --- a/src/explorer/GraphqlQueriesTreeItem.ts +++ b/src/explorer/GraphqlQueriesTreeItem.ts @@ -14,7 +14,7 @@ import { IApiTreeRoot } from "./IApiTreeRoot"; export class GraphqlQueriesTreeItem extends AzureParentTreeItem { public get iconPath(): { light: string, dark: string } { - return treeUtils.getThemedIconPath('list'); + return treeUtils.getThemedIconPath('querylist'); } public static contextValue: string = 'azureApiManagementGraphqlList'; public label: string = "Query"; diff --git a/src/explorer/editors/arm/ApiResourceEditor.ts b/src/explorer/editors/arm/ApiResourceEditor.ts index a812bfa..9d22cf5 100644 --- a/src/explorer/editors/arm/ApiResourceEditor.ts +++ b/src/explorer/editors/arm/ApiResourceEditor.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { ApiManagementModels } from "@azure/arm-apimanagement"; -import requestPromise from "request-promise"; -import { AzureTreeItem } from "vscode-azureextensionui"; +import { ServiceClient } from "@azure/ms-rest-js"; +import { AzureTreeItem, createGenericClient } from "vscode-azureextensionui"; import { IApiContract } from "../../../azure/apim/TempApiContract"; -import { SharedAccessToken } from "../../../constants"; import { IApiTreeRoot } from "../../IApiTreeRoot"; import { BaseArmResourceEditor } from "./BaseArmResourceEditor"; @@ -20,16 +19,15 @@ export class ApiResourceEditor extends BaseArmResourceEditor { public async getDataInternal(context: AzureTreeItem): Promise { if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(context.root.credentials); + const apiString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const apiString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview` + }); // tslint:disable: no-unsafe-any // tslint:disable-next-line: no-unnecessary-local-variable - const apiTemp : IApiContract = JSON.parse(apiString); + const apiTemp : IApiContract = apiString.parsedBody; return apiTemp; } return await context.root.client.api.get(context.root.resourceGroupName, context.root.serviceName, context.root.apiName); @@ -37,17 +35,16 @@ export class ApiResourceEditor extends BaseArmResourceEditor { public async updateDataInternal(context: AzureTreeItem, payload: ApiManagementModels.ApiCreateOrUpdateParameter): Promise { if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(context.root.credentials); + const apiString = await client.sendRequest({ method: "PUT", - headers: { - Authorization: SharedAccessToken - }, - body: JSON.stringify(payload) - }; - const apiString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview`, requestOptions).promise(); + body: payload, + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}?api-version=2021-04-01-preview` + }); // tslint:disable: no-unsafe-any // tslint:disable-next-line: no-unnecessary-local-variable - const apiTemp : IApiContract = JSON.parse(apiString); + const apiTemp : IApiContract = apiString.parsedBody; return apiTemp; } return await context.root.client.api.createOrUpdate(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, payload); diff --git a/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts b/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts index a1ba550..9562982 100644 --- a/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts +++ b/src/explorer/editors/graphqlApiSchema/GraphqlApiSchemaEditor.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import requestPromise from "request-promise"; +import { ServiceClient } from "@azure/ms-rest-js"; import { ProgressLocation, window } from "vscode"; -import { SharedAccessToken, showSavePromptConfigKey } from "../../../constants"; +import { createGenericClient } from "vscode-azureextensionui"; +import { showSavePromptConfigKey } from "../../../constants"; import { localize } from "../../../localize"; import { GraphqlApiTreeItem } from "../../GraphqlApiTreeItem"; import { Editor } from "../Editor"; @@ -17,17 +18,13 @@ export class GraphqlApiSchemaEditor extends Editor { } public async getData(context: GraphqlApiTreeItem): Promise { - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(context.root.credentials); + const schemasString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - - const schemasString = await >requestPromise( - `https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); - - return JSON.parse(schemasString).properties.document.value; + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview` + }); + return schemasString.parsedBody.properties.document.value; } public async updateData(context: GraphqlApiTreeItem, data: string): Promise { @@ -47,15 +44,13 @@ export class GraphqlApiSchemaEditor extends Editor { } }; - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(context.root.credentials); + await client.sendRequest({ method: "PUT", - headers: { - Authorization: SharedAccessToken - }, - body: JSON.stringify(body) - }; - await >requestPromise( - `https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview`, requestOptions).promise(); + body: body, + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/schemas/default?api-version=2021-04-01-preview` + }); } ).then(async () => { window.showInformationMessage(localize("updateAPISchema", `Changes to API '${context.apiContract.name}' were succefully uploaded to cloud.`)); diff --git a/src/explorer/editors/policy/ApiPolicyEditor.ts b/src/explorer/editors/policy/ApiPolicyEditor.ts index 96774b2..dc63da2 100644 --- a/src/explorer/editors/policy/ApiPolicyEditor.ts +++ b/src/explorer/editors/policy/ApiPolicyEditor.ts @@ -4,26 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import { ApiManagementModels } from "@azure/arm-apimanagement"; -import requestPromise from "request-promise"; -import { AzureTreeItem } from "vscode-azureextensionui"; +import { ServiceClient } from "@azure/ms-rest-js"; +import { AzureTreeItem, createGenericClient } from "vscode-azureextensionui"; import { emptyPolicyXml, policyFormat } from "../../../constants"; -import { SharedAccessToken } from "../../../constants"; import { IApiTreeRoot } from "../../IApiTreeRoot"; import { BasePolicyEditor } from "./BasePolicyEditor"; export class ApiPolicyEditor extends BasePolicyEditor { public async getPolicy(context: AzureTreeItem): Promise { if (context.root.apiType !== undefined && context.root.apiType === 'graphql') { - const requestOptions : requestPromise.RequestPromiseOptions = { + + const client: ServiceClient = await createGenericClient(context.root.credentials); + const policyString = await client.sendRequest({ method: "GET", - headers: { - Authorization: SharedAccessToken - } - }; - const policyString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview`, requestOptions).promise(); + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview` + }); // tslint:disable: no-unsafe-any // tslint:disable-next-line: no-unnecessary-local-variable - const policyTemp = JSON.parse(policyString); + const policyTemp = policyString.parsedBody; return policyTemp.properties.value; } const policy = await context.root.client.apiPolicy.get(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, { format: policyFormat }); @@ -38,17 +37,16 @@ export class ApiPolicyEditor extends BasePolicyEditor { format: policy.format } }; - const requestOptions : requestPromise.RequestPromiseOptions = { + const client: ServiceClient = await createGenericClient(context.root.credentials); + const policyString = await client.sendRequest({ method: "PUT", - headers: { - Authorization: SharedAccessToken - }, - body: JSON.stringify(body) - }; - const policyString = await >requestPromise(`https://${context.root.serviceName}.management.preview.int-azure-api.net/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview`, requestOptions).promise(); + body: body, + // tslint:disable-next-line: no-non-null-assertion + url: `https://management.azure.com/subscriptions/${context.root.subscriptionId}/resourceGroups/${context.root.resourceGroupName}/providers/Microsoft.ApiManagement/service/${context.root.serviceName}/apis/${context.root.apiName}/policies/policy?api-version=2021-04-01-preview` + }); // tslint:disable: no-unsafe-any // tslint:disable-next-line: no-unnecessary-local-variable - const policyTemp = JSON.parse(policyString); + const policyTemp = policyString.parsedBody; return policyTemp.properties.value; } const policyResult = await context.root.client.apiPolicy.createOrUpdate(context.root.resourceGroupName, context.root.serviceName, context.root.apiName, policy);