Addressed PR comments

This commit is contained in:
Srinath Narayanan
2020-11-17 03:50:12 -08:00
parent 394e1398d1
commit dc40b6618e
11 changed files with 201 additions and 190 deletions

View File

@@ -4,4 +4,11 @@ PORTAL_RUNNER_PASSWORD=
PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT=
PORTAL_RUNNER_CONNECTION_STRING=
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=

View File

@@ -33,8 +33,7 @@ export enum MessageTypes {
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
InitTestExplorer,
HideConnectScreen
InitTestExplorer
}
export { Versions, ActionContracts, Diagnostics };

View File

@@ -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;

View File

@@ -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<msRest.WebResourceLike> {
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<string> {
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<DatabaseAccountsGetResponse> {
const client = new CosmosDBManagementClient(new CustomSigner(token), this.notebooksAccountSubscriptonId);
return await client.databaseAccounts.get(this.notebooksAccountResourceGroup, this.notebooksAccountName);
}
private async initTestExplorer(): Promise<void> {
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);

View File

@@ -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<Frame> => {
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();

View File

@@ -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<msRest.WebResourceLike> {
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<string> => {
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<DatabaseAccountsGetResponse> => {
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<void> => {
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);

View File

@@ -7,9 +7,12 @@
</head>
<body>
<switch-directory-pane params="{data: switchDirectoryPane}"></switch-directory-pane>
<iframe id="explorerMenu" name="explorer" class="iframe" title="explorer" src="explorer.html?v=1.0.1&platform=Test">
</iframe>
<iframe
id="explorerMenu"
name="explorer"
class="iframe"
title="explorer"
src="explorer.html?v=1.0.1&platform=Portal"
></iframe>
</body>
</html>

View File

@@ -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<Element>;
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;

View File

@@ -19,6 +19,6 @@
"noEmit": true,
"types": ["jest"]
},
"include": ["./src/**/*"],
"include": ["./src/**/*", "./test/notebooks/testExplorer/**/*"],
"exclude": ["./src/**/__mocks__/**/*"]
}

View File

@@ -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",