From dc40b6618eb73adaa97645df7b09a4d12acdd8f1 Mon Sep 17 00:00:00 2001 From: Srinath Narayanan Date: Tue, 17 Nov 2020 03:50:12 -0800 Subject: [PATCH] Addressed PR comments --- .env.example | 9 +- src/Contracts/ExplorerContracts.ts | 3 +- src/Explorer/Explorer.ts | 5 - src/TestExplorer.ts | 153 ------------------ test/notebooks/notebookTestUtils.ts | 35 ++-- test/notebooks/testExplorer/TestExplorer.ts | 136 ++++++++++++++++ .../testExplorer}/TestExplorerParams.ts | 0 .../notebooks/testExplorer}/testExplorer.html | 11 +- .../uploadOpenAndDeleteNotebook.spec.ts | 33 ++-- tsconfig.json | 2 +- webpack.config.js | 4 +- 11 files changed, 201 insertions(+), 190 deletions(-) delete mode 100644 src/TestExplorer.ts create mode 100644 test/notebooks/testExplorer/TestExplorer.ts rename {src => test/notebooks/testExplorer}/TestExplorerParams.ts (100%) rename {src => test/notebooks/testExplorer}/testExplorer.html (59%) diff --git a/.env.example b/.env.example index b37aeff49..d42af528c 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,11 @@ PORTAL_RUNNER_PASSWORD= PORTAL_RUNNER_SUBSCRIPTION= PORTAL_RUNNER_RESOURCE_GROUP= PORTAL_RUNNER_DATABASE_ACCOUNT= -PORTAL_RUNNER_CONNECTION_STRING= \ No newline at end of file +PORTAL_RUNNER_CONNECTION_STRING= +NOTEBOOKS_TEST_RUNNER_TENANT_ID= +NOTEBOOKS_TEST_RUNNER_CLIENT_ID= +NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET= +NOTEBOOKS_ACCOUNT_SUBSCRIPTION_ID= +NOTEBOOKS_ACCOUNT_RESOURCE_GROUP= +NOTEBOOKS_ACCOUNT_NAME= +NOTEBOOKS_ACCOUNT_KEY= \ No newline at end of file diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts index 96171d30b..0a7807297 100644 --- a/src/Contracts/ExplorerContracts.ts +++ b/src/Contracts/ExplorerContracts.ts @@ -33,8 +33,7 @@ export enum MessageTypes { CreateWorkspace, CreateSparkPool, RefreshDatabaseAccount, - InitTestExplorer, - HideConnectScreen + InitTestExplorer } export { Versions, ActionContracts, Diagnostics }; diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 0cf235f56..1dc345c66 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -1725,7 +1725,6 @@ export default class Explorer { case MessageTypes.ClearNotification: case MessageTypes.LoadingStatus: case MessageTypes.InitTestExplorer: - case MessageTypes.HideConnectScreen: return true; } } @@ -1744,10 +1743,6 @@ export default class Explorer { return; } - if (event.data.data.type === MessageTypes.HideConnectScreen) { - this.hideConnectExplorerForm(); - } - const message: any = event.data.data; const inputs: ViewModels.DataExplorerInputsFrame = message.inputs; diff --git a/src/TestExplorer.ts b/src/TestExplorer.ts deleted file mode 100644 index 5a6789e0a..000000000 --- a/src/TestExplorer.ts +++ /dev/null @@ -1,153 +0,0 @@ -import * as ko from "knockout"; -import { MessageTypes } from "./Contracts/ExplorerContracts"; -import * as ViewModels from "./Contracts/ViewModels"; -import "../less/hostedexplorer.less"; -import "./Explorer/Menus/NavBar/MeControlComponent.less"; -import { ClientSecretCredential } from "@azure/identity"; -import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; -import * as msRest from "@azure/ms-rest-js"; -import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models"; -import { TestExplorerParams } from "./TestExplorerParams"; - -class CustomSigner implements msRest.ServiceClientCredentials { - private token: string; - constructor(token: string) { - this.token = token; - } - - async signRequest(webResource: msRest.WebResourceLike): Promise { - webResource.headers.set("authorization", `bearer ${this.token}`); - return webResource; - } -} - -class TestExplorer { - private notebooksTestRunnerApplicationId: string; - private notebooksTestRunnerClientId: string; - private notebooksTestRunnerClientSecret: string; - private notebooksAccountName: string; - private notebooksAccountKey: string; - private notebooksAccountSubscriptonId: string; - private notebooksAccountResourceGroup: string; - - constructor() { - window.onload = () => { - this.initTestExplorer(); - }; - window.addEventListener("message", this.handleMessage.bind(this), false); - } - - private parseUrlParams = (): void => { - window.location.search - .substr(1) - .split("&") - .forEach(item => { - const tmp = item.split("="); - const value = decodeURIComponent(tmp[1]); - switch (tmp[0]) { - case TestExplorerParams.notebooksTestRunnerApplicationId: - this.notebooksTestRunnerApplicationId = value; - break; - case TestExplorerParams.notebooksTestRunnerClientId: - this.notebooksTestRunnerClientId = value; - break; - case TestExplorerParams.notebooksTestRunnerClientSecret: - this.notebooksTestRunnerClientSecret = value; - break; - case TestExplorerParams.notebooksAccountName: - this.notebooksAccountName = value; - break; - case TestExplorerParams.notebooksAccountKey: - this.notebooksAccountKey = value; - break; - case TestExplorerParams.notebooksAccountSubscriptonId: - this.notebooksAccountSubscriptonId = value; - break; - case TestExplorerParams.notebooksAccountResourceGroup: - this.notebooksAccountResourceGroup = value; - break; - } - }); - }; - - private handleMessage(event: MessageEvent) { - if (event.data.type === MessageTypes.InitTestExplorer || event.data.type === MessageTypes.HideConnectScreen) { - this.sendMessageToExplorerFrame(event.data); - } - } - - private async AADLogin(): Promise { - const credentials = new ClientSecretCredential( - this.notebooksTestRunnerApplicationId, - this.notebooksTestRunnerClientId, - this.notebooksTestRunnerClientSecret - ); - const token = await credentials.getToken("https://management.core.windows.net/.default"); - return token.token; - } - - private async getDatabaseAccount(token: string): Promise { - const client = new CosmosDBManagementClient(new CustomSigner(token), this.notebooksAccountSubscriptonId); - return await client.databaseAccounts.get(this.notebooksAccountResourceGroup, this.notebooksAccountName); - } - - private async initTestExplorer(): Promise { - this.parseUrlParams(); - const token = await this.AADLogin(); - const databaseAccount = await this.getDatabaseAccount(token); - - const content = { - type: MessageTypes.InitTestExplorer, - inputs: { - databaseAccount: databaseAccount, - subscriptionId: this.notebooksAccountSubscriptonId, - resourceGroup: this.notebooksAccountResourceGroup, - authorizationToken: `Bearer ${token}`, - features: {}, - hasWriteAccess: true, - csmEndpoint: "https://management.azure.com", - dnsSuffix: "documents.azure.com", - serverId: "prod1", - extensionEndpoint: "/proxy", - subscriptionType: 3, - quotaId: "Internal_2014-09-01", - addCollectionDefaultFlight: "2", - isTryCosmosDBSubscription: false, - masterKey: this.notebooksAccountKey, - loadDatabaseAccountTimestamp: 1604663109836, - dataExplorerVersion: "1.0.1", - sharedThroughputMinimum: 400, - sharedThroughputMaximum: 1000000, - sharedThroughputDefault: 400, - defaultCollectionThroughput: { - storage: "100", - throughput: { fixed: 400, unlimited: 400, unlimitedmax: 100000, unlimitedmin: 400, shared: 400 } - }, - flights: ["mongoindexeditor", "settingsv2"] - } as ViewModels.DataExplorerInputsFrame - }; - window.postMessage(content, window.location.href); - - const hideConnectContent = { - type: MessageTypes.HideConnectScreen - }; - window.postMessage(hideConnectContent, window.location.href); - } - - private sendMessageToExplorerFrame(data: unknown): void { - const explorerFrame = document.getElementById("explorerMenu") as HTMLIFrameElement; - explorerFrame && - explorerFrame.contentDocument && - explorerFrame.contentDocument.referrer && - explorerFrame.contentWindow.postMessage( - { - signature: "pcIframe", - data: data - }, - explorerFrame.contentDocument.referrer || window.location.href - ); - } -} - -const testExplorer = new TestExplorer(); -ko.applyBindings(testExplorer); diff --git a/test/notebooks/notebookTestUtils.ts b/test/notebooks/notebookTestUtils.ts index 0e6227091..7cc26cea2 100644 --- a/test/notebooks/notebookTestUtils.ts +++ b/test/notebooks/notebookTestUtils.ts @@ -1,5 +1,5 @@ import { ElementHandle, Frame } from "puppeteer"; -import { TestExplorerParams } from "../../src/TestExplorerParams"; +import { TestExplorerParams } from "./testExplorer/TestExplorerParams"; export const NOTEBOOK_OPERATION_DELAY = 5000; export const RENDER_DELAY = 1000; @@ -18,16 +18,31 @@ export const getTestExplorerFrame = async (): Promise => { const notebooksAccountSubscriptonId = process.env.NOTEBOOKS_ACCOUNT_SUBSCRIPTION_ID; const notebooksAccountResourceGroup = process.env.NOTEBOOKS_ACCOUNT_RESOURCE_GROUP; - const prodUrl = `https://localhost:1234/testExplorer.html? -${TestExplorerParams.notebooksTestRunnerApplicationId}=${encodeURI(notebooksTestRunnerApplicationId)}& -${TestExplorerParams.notebooksTestRunnerClientId}=${encodeURI(notebooksTestRunnerClientId)}& -${TestExplorerParams.notebooksTestRunnerClientSecret}=${encodeURI(notebooksTestRunnerClientSecret)}& -${TestExplorerParams.notebooksAccountName}=${encodeURI(notebooksAccountName)}& -${TestExplorerParams.notebooksAccountKey}=${encodeURI(notebooksAccountKey)}& -${TestExplorerParams.notebooksAccountSubscriptonId}=${encodeURI(notebooksAccountSubscriptonId)}& -${TestExplorerParams.notebooksAccountResourceGroup}=${encodeURI(notebooksAccountResourceGroup)}`; + const testExplorerUrl = new URL("testExplorer.html", "https://localhost:1234"); + testExplorerUrl.searchParams.append( + TestExplorerParams.notebooksTestRunnerApplicationId, + encodeURI(notebooksTestRunnerApplicationId) + ); + testExplorerUrl.searchParams.append( + TestExplorerParams.notebooksTestRunnerClientId, + encodeURI(notebooksTestRunnerClientId) + ); + testExplorerUrl.searchParams.append( + TestExplorerParams.notebooksTestRunnerClientSecret, + encodeURI(notebooksTestRunnerClientSecret) + ); + testExplorerUrl.searchParams.append(TestExplorerParams.notebooksAccountName, encodeURI(notebooksAccountName)); + testExplorerUrl.searchParams.append(TestExplorerParams.notebooksAccountKey, encodeURI(notebooksAccountKey)); + testExplorerUrl.searchParams.append( + TestExplorerParams.notebooksAccountSubscriptonId, + encodeURI(notebooksAccountSubscriptonId) + ); + testExplorerUrl.searchParams.append( + TestExplorerParams.notebooksAccountResourceGroup, + encodeURI(notebooksAccountResourceGroup) + ); - await page.goto(prodUrl); + await page.goto(testExplorerUrl.toString()); const handle = await page.waitForSelector("iframe"); testExplorerFrame = await handle.contentFrame(); diff --git a/test/notebooks/testExplorer/TestExplorer.ts b/test/notebooks/testExplorer/TestExplorer.ts new file mode 100644 index 000000000..c843b5a45 --- /dev/null +++ b/test/notebooks/testExplorer/TestExplorer.ts @@ -0,0 +1,136 @@ +import { MessageTypes } from "../../../src/Contracts/ExplorerContracts"; +import "../../../less/hostedexplorer.less"; +import { TestExplorerParams } from "./TestExplorerParams"; +import { ClientSecretCredential } from "@azure/identity"; +import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models"; +import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; +import * as msRest from "@azure/ms-rest-js"; +import * as ViewModels from "../../../src/Contracts/ViewModels"; + +class CustomSigner implements msRest.ServiceClientCredentials { + private token: string; + constructor(token: string) { + this.token = token; + } + + async signRequest(webResource: msRest.WebResourceLike): Promise { + webResource.headers.set("authorization", `bearer ${this.token}`); + return webResource; + } +} + +const handleMessage = (event: MessageEvent): void => { + if (event.data.type === MessageTypes.InitTestExplorer) { + sendMessageToExplorerFrame(event.data); + } +}; + +const AADLogin = async ( + notebooksTestRunnerApplicationId: string, + notebooksTestRunnerClientId: string, + notebooksTestRunnerClientSecret: string +): Promise => { + const credentials = new ClientSecretCredential( + notebooksTestRunnerApplicationId, + notebooksTestRunnerClientId, + notebooksTestRunnerClientSecret + ); + const token = await credentials.getToken("https://management.core.windows.net/.default"); + return token.token; +}; + +const getDatabaseAccount = async ( + token: string, + notebooksAccountSubscriptonId: string, + notebooksAccountResourceGroup: string, + notebooksAccountName: string +): Promise => { + const client = new CosmosDBManagementClient(new CustomSigner(token), notebooksAccountSubscriptonId); + return await client.databaseAccounts.get(notebooksAccountResourceGroup, notebooksAccountName); +}; + +const sendMessageToExplorerFrame = (data: unknown): void => { + const explorerFrame = document.getElementById("explorerMenu") as HTMLIFrameElement; + + explorerFrame && + explorerFrame.contentDocument && + explorerFrame.contentDocument.referrer && + explorerFrame.contentWindow.postMessage( + { + signature: "pcIframe", + data: data + }, + explorerFrame.contentDocument.referrer || window.location.href + ); +}; + +const initTestExplorer = async (): Promise => { + window.addEventListener("message", handleMessage, false); + + const urlSearchParams = new URLSearchParams(window.location.search); + const notebooksTestRunnerApplicationId = decodeURIComponent( + urlSearchParams.get(TestExplorerParams.notebooksTestRunnerApplicationId) + ); + const notebooksTestRunnerClientId = decodeURIComponent( + urlSearchParams.get(TestExplorerParams.notebooksTestRunnerClientId) + ); + const notebooksTestRunnerClientSecret = decodeURIComponent( + urlSearchParams.get(TestExplorerParams.notebooksTestRunnerClientSecret) + ); + const notebooksAccountName = decodeURIComponent(urlSearchParams.get(TestExplorerParams.notebooksAccountName)); + const notebooksAccountKey = decodeURIComponent(urlSearchParams.get(TestExplorerParams.notebooksAccountKey)); + const notebooksAccountSubscriptonId = decodeURIComponent( + urlSearchParams.get(TestExplorerParams.notebooksAccountSubscriptonId) + ); + const notebooksAccountResourceGroup = decodeURIComponent( + urlSearchParams.get(TestExplorerParams.notebooksAccountResourceGroup) + ); + + const token = await AADLogin( + notebooksTestRunnerApplicationId, + notebooksTestRunnerClientId, + notebooksTestRunnerClientSecret + ); + const databaseAccount = await getDatabaseAccount( + token, + notebooksAccountSubscriptonId, + notebooksAccountResourceGroup, + notebooksAccountName + ); + + const initTestExplorerContent = { + type: MessageTypes.InitTestExplorer, + inputs: { + databaseAccount: databaseAccount, + subscriptionId: notebooksAccountSubscriptonId, + resourceGroup: notebooksAccountResourceGroup, + authorizationToken: `Bearer ${token}`, + features: {}, + hasWriteAccess: true, + csmEndpoint: "https://management.azure.com", + dnsSuffix: "documents.azure.com", + serverId: "prod1", + extensionEndpoint: "/proxy", + subscriptionType: 3, + quotaId: "Internal_2014-09-01", + addCollectionDefaultFlight: "2", + isTryCosmosDBSubscription: false, + masterKey: notebooksAccountKey, + loadDatabaseAccountTimestamp: 1604663109836, + dataExplorerVersion: "1.0.1", + sharedThroughputMinimum: 400, + sharedThroughputMaximum: 1000000, + sharedThroughputDefault: 400, + defaultCollectionThroughput: { + storage: "100", + throughput: { fixed: 400, unlimited: 400, unlimitedmax: 100000, unlimitedmin: 400, shared: 400 } + }, + // add UI test only when feature is not dependent on flights anymore + flights: [] + } as ViewModels.DataExplorerInputsFrame + }; + + window.postMessage(initTestExplorerContent, window.location.href); +}; + +window.addEventListener("load", initTestExplorer); diff --git a/src/TestExplorerParams.ts b/test/notebooks/testExplorer/TestExplorerParams.ts similarity index 100% rename from src/TestExplorerParams.ts rename to test/notebooks/testExplorer/TestExplorerParams.ts diff --git a/src/testExplorer.html b/test/notebooks/testExplorer/testExplorer.html similarity index 59% rename from src/testExplorer.html rename to test/notebooks/testExplorer/testExplorer.html index 2da273bff..d9a0c8184 100644 --- a/src/testExplorer.html +++ b/test/notebooks/testExplorer/testExplorer.html @@ -7,9 +7,12 @@ - - - + diff --git a/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts b/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts index 7a37ba159..eea20671e 100644 --- a/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts +++ b/test/notebooks/uploadOpenAndDeleteNotebook.spec.ts @@ -1,29 +1,38 @@ import "expect-puppeteer"; import { deleteNotebook, getNotebookNode, getTestExplorerFrame, uploadNotebook } from "./notebookTestUtils"; import * as path from "path"; +import { ElementHandle, Frame } from "puppeteer"; jest.setTimeout(300000); +const notebookName = "GettingStarted.ipynb"; +let frame: Frame; +let uploadedNotebookNode: ElementHandle; + describe("Notebook UI tests", () => { + beforeAll(async () => { + frame = await getTestExplorerFrame(); + const uploadNotebookPath = path.join(__dirname, "testNotebooks", notebookName); + await uploadNotebook(frame, uploadNotebookPath); + uploadedNotebookNode = await getNotebookNode(frame, notebookName); + }); + + afterAll(async () => { + await deleteNotebook(frame, uploadedNotebookNode); + const deletedNotebookNode = await getNotebookNode(frame, notebookName); + if (deletedNotebookNode) { + throw new Error(`Deletion of notebook ${notebookName} failed`); + } + }); + it("Upload, Open and Delete Notebook", async () => { try { - const frame = await getTestExplorerFrame(); - const uploadNotebookName = "GettingStarted.ipynb"; - const uploadNotebookPath = path.join(__dirname, "testNotebooks", uploadNotebookName); - - await uploadNotebook(frame, uploadNotebookPath); - const uploadedNotebookNode = await getNotebookNode(frame, uploadNotebookName); - await uploadedNotebookNode.click(); await frame.waitForSelector(".tabNavText"); const tabTitle = await frame.$eval(".tabNavText", element => element.textContent); - expect(tabTitle).toEqual(uploadNotebookName); + expect(tabTitle).toEqual(notebookName); const closeIcon = await frame.waitForSelector(".close-Icon"); await closeIcon.click(); - - await deleteNotebook(frame, uploadedNotebookNode); - const deletedNotebookNode = await getNotebookNode(frame, uploadNotebookName); - expect(deletedNotebookNode).toBeUndefined(); } catch (error) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const testName = (expect as any).getState().currentTestName; diff --git a/tsconfig.json b/tsconfig.json index 85238fc47..0c45faa01 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,6 @@ "noEmit": true, "types": ["jest"] }, - "include": ["./src/**/*"], + "include": ["./src/**/*", "./test/notebooks/testExplorer/**/*"], "exclude": ["./src/**/__mocks__/**/*"] } diff --git a/webpack.config.js b/webpack.config.js index 225287409..b7e65aa3e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -142,7 +142,7 @@ module.exports = function(env = {}, argv = {}) { }), new HtmlWebpackPlugin({ filename: "testExplorer.html", - template: "src/testExplorer.html", + template: "test/notebooks/testExplorer/testExplorer.html", chunks: ["testExplorer"] }), new HtmlWebpackPlugin({ @@ -183,7 +183,7 @@ module.exports = function(env = {}, argv = {}) { index: "./src/Index.ts", quickstart: "./src/quickstart.ts", hostedExplorer: "./src/HostedExplorer.ts", - testExplorer: "./src/TestExplorer.ts", + testExplorer: "./test/notebooks/testExplorer/TestExplorer.ts", heatmap: "./src/Controls/Heatmap/Heatmap.ts", terminal: "./src/Terminal/index.ts", notebookViewer: "./src/NotebookViewer/NotebookViewer.tsx",