Test Explorer Improvements (#541)

This commit is contained in:
Steve Faulkner 2021-03-14 22:53:16 -05:00 committed by GitHub
parent f86883de6c
commit 254c551999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 427 additions and 506 deletions

View File

@ -15,10 +15,10 @@ jobs:
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: node utils/codeMetrics.js - run: node utils/codeMetrics.js
env: env:
@ -28,10 +28,10 @@ jobs:
name: "Compile TypeScript" name: "Compile TypeScript"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm run compile - run: npm run compile
- run: npm run compile:strict - run: npm run compile:strict
@ -40,10 +40,10 @@ jobs:
name: "Check Format" name: "Check Format"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm run format:check - run: npm run format:check
lint: lint:
@ -51,10 +51,10 @@ jobs:
name: "Lint" name: "Lint"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm run lint - run: npm run lint
unittest: unittest:
@ -62,10 +62,10 @@ jobs:
name: "Unit Tests" name: "Unit Tests"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm run test - run: npm run test
build: build:
@ -74,10 +74,10 @@ jobs:
name: "Build" name: "Build"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- run: npm ci - run: npm ci
- run: npm run build:contracts - run: npm run build:contracts
- name: Restore Build Cache - name: Restore Build Cache
@ -98,10 +98,10 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- uses: southpolesteve/cosmos-emulator-github-action@v1 - uses: southpolesteve/cosmos-emulator-github-action@v1
- name: End to End Tests - name: End to End Tests
run: | run: |
@ -125,10 +125,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js 12.x - name: Use Node.js 14.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- name: Accessibility Check - name: Accessibility Check
run: | run: |
# Ubuntu gets mad when webpack runs too many files watchers # Ubuntu gets mad when webpack runs too many files watchers
@ -163,6 +163,7 @@ jobs:
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }} TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html" DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
strategy: strategy:
fail-fast: false
matrix: matrix:
test-file: test-file:
- ./test/cassandra/container.spec.ts - ./test/cassandra/container.spec.ts

110
package-lock.json generated
View File

@ -949,14 +949,20 @@
} }
}, },
"node_modules/@babel/plugin-syntax-class-properties": { "node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.1", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.10.4" "@babel/helper-plugin-utils": "^7.12.13"
} }
}, },
"node_modules/@babel/plugin-syntax-class-properties/node_modules/@babel/helper-plugin-utils": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
},
"node_modules/@babel/plugin-syntax-decorators": { "node_modules/@babel/plugin-syntax-decorators": {
"version": "7.12.1", "version": "7.12.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz",
@ -1951,9 +1957,9 @@
} }
}, },
"node_modules/@istanbuljs/schema": { "node_modules/@istanbuljs/schema": {
"version": "0.1.2", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -2127,9 +2133,9 @@
} }
}, },
"node_modules/@jest/globals/node_modules/@types/yargs": { "node_modules/@jest/globals/node_modules/@types/yargs": {
"version": "15.0.12", "version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
@ -4908,9 +4914,9 @@
} }
}, },
"node_modules/@types/graceful-fs": { "node_modules/@types/graceful-fs": {
"version": "4.1.4", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
@ -15439,9 +15445,9 @@
} }
}, },
"node_modules/jest/node_modules/@types/yargs": { "node_modules/jest/node_modules/@types/yargs": {
"version": "15.0.12", "version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
@ -15756,9 +15762,9 @@
} }
}, },
"node_modules/jest/node_modules/fsevents": { "node_modules/jest/node_modules/fsevents": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -16751,9 +16757,9 @@
} }
}, },
"node_modules/jest/node_modules/string-width": { "node_modules/jest/node_modules/string-width": {
"version": "4.2.0", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@ -22760,11 +22766,6 @@
"safer-buffer": "^2.0.2", "safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0" "tweetnacl": "~0.14.0"
}, },
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -25098,8 +25099,7 @@
"node_modules/webcrypto-liner/node_modules/core-js": { "node_modules/webcrypto-liner/node_modules/core-js": {
"version": "3.8.3", "version": "3.8.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz",
"integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==", "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q=="
"hasInstallScript": true
}, },
"node_modules/webfontloader": { "node_modules/webfontloader": {
"version": "1.6.28", "version": "1.6.28",
@ -27019,12 +27019,20 @@
} }
}, },
"@babel/plugin-syntax-class-properties": { "@babel/plugin-syntax-class-properties": {
"version": "7.12.1", "version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.10.4" "@babel/helper-plugin-utils": "^7.12.13"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true
}
} }
}, },
"@babel/plugin-syntax-decorators": { "@babel/plugin-syntax-decorators": {
@ -27993,9 +28001,9 @@
} }
}, },
"@istanbuljs/schema": { "@istanbuljs/schema": {
"version": "0.1.2", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true "dev": true
}, },
"@jest/console": { "@jest/console": {
@ -28135,9 +28143,9 @@
} }
}, },
"@types/yargs": { "@types/yargs": {
"version": "15.0.12", "version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
@ -30752,9 +30760,9 @@
} }
}, },
"@types/graceful-fs": { "@types/graceful-fs": {
"version": "4.1.4", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
@ -39218,9 +39226,9 @@
} }
}, },
"@types/yargs": { "@types/yargs": {
"version": "15.0.12", "version": "15.0.13",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
@ -39473,9 +39481,9 @@
} }
}, },
"fsevents": { "fsevents": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
@ -40261,9 +40269,9 @@
} }
}, },
"string-width": { "string-width": {
"version": "4.2.0", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true, "dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",

View File

@ -1,10 +1,10 @@
import * as Cosmos from "@azure/cosmos"; import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos"; import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext"; import { configContext, Platform } from "../ConfigContext";
import { getErrorMessage } from "./ErrorHandlingUtils"; import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { userContext } from "../UserContext"; import { getErrorMessage } from "./ErrorHandlingUtils";
const _global = typeof self === "undefined" ? window : self; const _global = typeof self === "undefined" ? window : self;

View File

@ -775,14 +775,8 @@ export default class Explorer {
$(document.body).click(() => $(".commandDropdownContainer").hide()); $(document.body).click(() => $(".commandDropdownContainer").hide());
}); });
// TODO move this to API customization class switch (userContext.apiType) {
this.defaultExperience.subscribe((defaultExperience) => { case "SQL":
const defaultExperienceNormalizedString = (
defaultExperience || Constants.DefaultAccountExperience.Default
).toLowerCase();
switch (defaultExperienceNormalizedString) {
case Constants.DefaultAccountExperience.DocumentDB.toLowerCase():
this.addCollectionText("New Container"); this.addCollectionText("New Container");
this.addDatabaseText("New Database"); this.addDatabaseText("New Database");
this.collectionTitle("SQL API"); this.collectionTitle("SQL API");
@ -798,8 +792,7 @@ export default class Explorer {
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id"); this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
this.refreshTreeTitle("Refresh containers"); this.refreshTreeTitle("Refresh containers");
break; break;
case Constants.DefaultAccountExperience.MongoDB.toLowerCase(): case "Mongo":
case Constants.DefaultAccountExperience.ApiForMongoDB.toLowerCase():
this.addCollectionText("New Collection"); this.addCollectionText("New Collection");
this.addDatabaseText("New Database"); this.addDatabaseText("New Database");
this.collectionTitle("Collections"); this.collectionTitle("Collections");
@ -813,7 +806,7 @@ export default class Explorer {
); );
this.refreshTreeTitle("Refresh collections"); this.refreshTreeTitle("Refresh collections");
break; break;
case Constants.DefaultAccountExperience.Graph.toLowerCase(): case "Gremlin":
this.addCollectionText("New Graph"); this.addCollectionText("New Graph");
this.addDatabaseText("New Database"); this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Graph"); this.deleteCollectionText("Delete Graph");
@ -827,7 +820,7 @@ export default class Explorer {
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id"); this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
this.refreshTreeTitle("Refresh graphs"); this.refreshTreeTitle("Refresh graphs");
break; break;
case Constants.DefaultAccountExperience.Table.toLowerCase(): case "Tables":
this.addCollectionText("New Table"); this.addCollectionText("New Table");
this.addDatabaseText("New Database"); this.addDatabaseText("New Database");
this.deleteCollectionText("Delete Table"); this.deleteCollectionText("Delete Table");
@ -844,7 +837,7 @@ export default class Explorer {
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id"); this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
this.tableDataClient = new TablesAPIDataClient(); this.tableDataClient = new TablesAPIDataClient();
break; break;
case Constants.DefaultAccountExperience.Cassandra.toLowerCase(): case "Cassandra":
this.addCollectionText("New Table"); this.addCollectionText("New Table");
this.addDatabaseText("New Keyspace"); this.addDatabaseText("New Keyspace");
this.deleteCollectionText("Delete Table"); this.deleteCollectionText("Delete Table");
@ -864,7 +857,6 @@ export default class Explorer {
this.tableDataClient = new CassandraAPIDataClient(); this.tableDataClient = new CassandraAPIDataClient();
break; break;
} }
});
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this); this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);

View File

@ -1,74 +1,70 @@
// CSS Dependencies // CSS Dependencies
import "abort-controller/polyfill";
import "babel-polyfill";
import "bootstrap/dist/css/bootstrap.css"; import "bootstrap/dist/css/bootstrap.css";
import "../less/documentDB.less"; import "es6-object-assign/auto";
import "../less/tree.less"; import "es6-symbol/implement";
import "../less/forms.less"; import "object.entries/auto";
import "../less/menus.less"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import "../less/infobox.less"; import "promise-polyfill/src/polyfill";
import "../less/messagebox.less"; import "promise.prototype.finally/auto";
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less"; import React, { useState } from "react";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less"; import ReactDOM from "react-dom";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less"; import "url-polyfill/url-polyfill.min";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less"; import "webcrypto-liner/build/webcrypto-liner.shim.min";
import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less"; import "whatwg-fetch";
import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Panes/PanelComponent.less";
import "../less/TableStyles/queryBuilder.less";
import "../externals/jquery.dataTables.min.css";
import "../less/TableStyles/fulldatatables.less";
import "../less/TableStyles/EntityEditor.less";
import "../less/TableStyles/CustomizeColumns.less";
import "../less/resourceTree.less";
import "../externals/jquery.typeahead.min.css";
import "../externals/jquery-ui.min.css"; import "../externals/jquery-ui.min.css";
import "../externals/jquery-ui.min.js";
import "../externals/jquery-ui.structure.min.css"; import "../externals/jquery-ui.structure.min.css";
import "../externals/jquery-ui.theme.min.css"; import "../externals/jquery-ui.theme.min.css";
import "./Explorer/Graph/NewVertexComponent/newVertexComponent.less"; import "../externals/jquery.dataTables.min.css";
import "./Explorer/Panes/GraphNewVertexPane.less"; import "../externals/jquery.typeahead.min.css";
import "./Explorer/Tabs/QueryTab.less"; import "../externals/jquery.typeahead.min.js";
import "./Explorer/Controls/TreeComponent/treeComponent.less";
import "./Explorer/Controls/Accordion/AccordionComponent.less";
import "./Explorer/SplashScreen/SplashScreen.less";
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
// Image Dependencies // Image Dependencies
import "../images/CosmosDB_rgb_ui_lighttheme.ico"; import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import "../images/favicon.ico"; import "../images/favicon.ico";
import "./Shared/appInsights";
import "babel-polyfill";
import "es6-symbol/implement";
import "webcrypto-liner/build/webcrypto-liner.shim.min";
import "./Libs/jquery";
import "bootstrap/dist/js/npm";
import "../externals/jquery.typeahead.min.js";
import "../externals/jquery-ui.min.js";
import "promise-polyfill/src/polyfill";
import "abort-controller/polyfill";
import "whatwg-fetch";
import "es6-object-assign/auto";
import "promise.prototype.finally/auto";
import "object.entries/auto";
import "./Libs/is-integer-polyfill";
import "url-polyfill/url-polyfill.min";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { ExplorerParams } from "./Explorer/Explorer";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import refreshImg from "../images/refresh-cosmos.svg";
import arrowLeftImg from "../images/imgarrowlefticon.svg"; import arrowLeftImg from "../images/imgarrowlefticon.svg";
import { KOCommentEnd, KOCommentIfStart } from "./koComment"; import refreshImg from "../images/refresh-cosmos.svg";
import "../less/documentDB.less";
import "../less/forms.less";
import "../less/infobox.less";
import "../less/menus.less";
import "../less/messagebox.less";
import "../less/resourceTree.less";
import "../less/TableStyles/CustomizeColumns.less";
import "../less/TableStyles/EntityEditor.less";
import "../less/TableStyles/fulldatatables.less";
import "../less/TableStyles/queryBuilder.less";
import "../less/tree.less";
import "./Explorer/Controls/Accordion/AccordionComponent.less";
import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
import { Dialog, DialogProps } from "./Explorer/Controls/Dialog";
import "./Explorer/Controls/DynamicList/DynamicListComponent.less";
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
import "./Explorer/Controls/JsonEditor/JsonEditorComponent.less";
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
import "./Explorer/Controls/TreeComponent/treeComponent.less";
import { ExplorerParams } from "./Explorer/Explorer";
import "./Explorer/Graph/GraphExplorerComponent/graphExplorer.less";
import "./Explorer/Graph/NewVertexComponent/newVertexComponent.less";
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
import { NotificationConsoleComponent } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import "./Explorer/Panes/GraphNewVertexPane.less";
import "./Explorer/Panes/PanelComponent.less";
import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent";
import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen";
import "./Explorer/SplashScreen/SplashScreen.less";
import "./Explorer/Tabs/QueryTab.less";
import { useConfig } from "./hooks/useConfig"; import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer"; import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useSidePanel } from "./hooks/useSidePanel"; import { useSidePanel } from "./hooks/useSidePanel";
import { NotificationConsoleComponent } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { KOCommentEnd, KOCommentIfStart } from "./koComment";
import { PanelContainerComponent } from "./Explorer/Panes/PanelContainerComponent"; import "./Libs/is-integer-polyfill";
import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen"; import "./Libs/jquery";
import { Dialog, DialogProps } from "./Explorer/Controls/Dialog"; import "./Shared/appInsights";
initializeIcons(); initializeIcons();
@ -103,6 +99,10 @@ const App: React.FunctionComponent = () => {
const config = useConfig(); const config = useConfig();
const explorer = useKnockoutExplorer(config?.platform, explorerParams); const explorer = useKnockoutExplorer(config?.platform, explorerParams);
if (!explorer) {
return <LoadingExplorer />;
}
return ( return (
<div className="flexContainer"> <div className="flexContainer">
<div id="divExplorer" className="flexContainer hideOverflows" style={{ display: "none" }}> <div id="divExplorer" className="flexContainer hideOverflows" style={{ display: "none" }}>
@ -236,21 +236,6 @@ const App: React.FunctionComponent = () => {
/> />
</div> </div>
</div> </div>
{/* Global loader - Start */}
<div className="splashLoaderContainer" data-bind="visible: isRefreshingExplorer">
<div className="splashLoaderContentContainer">
<p className="connectExplorerContent">
<img src={hdeConnectImage} alt="Azure Cosmos DB" />
</p>
<p className="splashLoaderTitle" id="explorerLoadingStatusTitle">
Welcome to Azure Cosmos DB
</p>
<p className="splashLoaderText" id="explorerLoadingStatusText" role="alert">
Connecting...
</p>
</div>
</div>
{/* Global loader - End */}
<PanelContainerComponent <PanelContainerComponent
isOpen={isPanelOpen} isOpen={isPanelOpen}
panelContent={panelContent} panelContent={panelContent}
@ -294,3 +279,21 @@ const App: React.FunctionComponent = () => {
}; };
ReactDOM.render(<App />, document.body); ReactDOM.render(<App />, document.body);
function LoadingExplorer(): JSX.Element {
return (
<div className="splashLoaderContainer">
<div className="splashLoaderContentContainer">
<p className="connectExplorerContent">
<img src={hdeConnectImage} alt="Azure Cosmos DB" />
</p>
<p className="splashLoaderTitle" id="explorerLoadingStatusTitle">
Welcome to Azure Cosmos DB
</p>
<p className="splashLoaderText" id="explorerLoadingStatusText" role="alert">
Connecting...
</p>
</div>
</div>
);
}

View File

@ -1,4 +1,4 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { applyExplorerBindings } from "../applyExplorerBindings"; import { applyExplorerBindings } from "../applyExplorerBindings";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { AccountKind, DefaultAccountExperience } from "../Common/Constants"; import { AccountKind, DefaultAccountExperience } from "../Common/Constants";
@ -32,53 +32,65 @@ import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
// This hook will create a new instance of Explorer.ts and bind it to the DOM // This hook will create a new instance of Explorer.ts and bind it to the DOM
// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React // This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
// Pleas tread carefully :) // Pleas tread carefully :)
let explorer: Explorer;
export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer { export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer {
explorer = explorer || new Explorer(explorerParams); const [explorer, setExplorer] = useState<Explorer>();
useEffect(() => { useEffect(() => {
const effect = async () => { const effect = async () => {
if (platform) { if (platform) {
if (platform === Platform.Hosted) { if (platform === Platform.Hosted) {
await configureHosted(); const explorer = await configureHosted(explorerParams);
applyExplorerBindings(explorer); setExplorer(explorer);
} else if (platform === Platform.Emulator) { } else if (platform === Platform.Emulator) {
configureEmulator(); const explorer = configureEmulator(explorerParams);
applyExplorerBindings(explorer); setExplorer(explorer);
} else if (platform === Platform.Portal) { } else if (platform === Platform.Portal) {
configurePortal(); const explorer = await configurePortal(explorerParams);
setExplorer(explorer);
} }
} }
}; };
effect(); effect();
}, [platform]); }, [platform]);
useEffect(() => {
if (explorer) {
applyExplorerBindings(explorer);
}
}, [explorer]);
return explorer; return explorer;
} }
async function configureHosted() { async function configureHosted(explorerParams: ExplorerParams): Promise<Explorer> {
const win = (window as unknown) as HostedExplorerChildFrame; const win = (window as unknown) as HostedExplorerChildFrame;
if (win.hostedConfig.authType === AuthType.EncryptedToken) { if (win.hostedConfig.authType === AuthType.EncryptedToken) {
configureHostedWithEncryptedToken(win.hostedConfig); return configureHostedWithEncryptedToken(win.hostedConfig, explorerParams);
} else if (win.hostedConfig.authType === AuthType.ResourceToken) { } else if (win.hostedConfig.authType === AuthType.ResourceToken) {
configureHostedWithResourceToken(win.hostedConfig); return configureHostedWithResourceToken(win.hostedConfig, explorerParams);
} else if (win.hostedConfig.authType === AuthType.ConnectionString) { } else if (win.hostedConfig.authType === AuthType.ConnectionString) {
configureHostedWithConnectionString(win.hostedConfig); return configureHostedWithConnectionString(win.hostedConfig, explorerParams);
} else if (win.hostedConfig.authType === AuthType.AAD) { } else if (win.hostedConfig.authType === AuthType.AAD) {
await configureHostedWithAAD(win.hostedConfig); return configureHostedWithAAD(win.hostedConfig, explorerParams);
} }
throw new Error(`Unknown hosted config: ${win.hostedConfig}`);
} }
async function configureHostedWithAAD(config: AAD) { async function configureHostedWithAAD(config: AAD, explorerParams: ExplorerParams): Promise<Explorer> {
const account = config.databaseAccount; const account = config.databaseAccount;
const accountResourceId = account.id; const accountResourceId = account.id;
const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0]; const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0]; const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
updateUserContext({ updateUserContext({
subscriptionId,
resourceGroup,
authType: AuthType.AAD, authType: AuthType.AAD,
authorizationToken: `Bearer ${config.authorizationToken}`, authorizationToken: `Bearer ${config.authorizationToken}`,
databaseAccount: config.databaseAccount, databaseAccount: config.databaseAccount,
}); });
const keys = await listKeys(subscriptionId, resourceGroup, account.name); const keys = await listKeys(subscriptionId, resourceGroup, account.name);
const explorer = new Explorer(explorerParams);
explorer.configure({ explorer.configure({
databaseAccount: account, databaseAccount: account,
subscriptionId, subscriptionId,
@ -87,9 +99,10 @@ async function configureHostedWithAAD(config: AAD) {
authorizationToken: `Bearer ${config.authorizationToken}`, authorizationToken: `Bearer ${config.authorizationToken}`,
features: extractFeatures(), features: extractFeatures(),
}); });
return explorer;
} }
function configureHostedWithConnectionString(config: ConnectionString) { function configureHostedWithConnectionString(config: ConnectionString, explorerParams: ExplorerParams): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind); const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
const databaseAccount = { const databaseAccount = {
id: "", id: "",
@ -106,14 +119,16 @@ function configureHostedWithConnectionString(config: ConnectionString) {
accessToken: encodeURIComponent(config.encryptedToken), accessToken: encodeURIComponent(config.encryptedToken),
databaseAccount, databaseAccount,
}); });
const explorer = new Explorer(explorerParams);
explorer.configure({ explorer.configure({
databaseAccount, databaseAccount,
masterKey: config.masterKey, masterKey: config.masterKey,
features: extractFeatures(), features: extractFeatures(),
}); });
return explorer;
} }
function configureHostedWithResourceToken(config: ResourceToken) { function configureHostedWithResourceToken(config: ResourceToken, explorerParams: ExplorerParams): Explorer {
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken); const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
const databaseAccount = { const databaseAccount = {
id: "", id: "",
@ -131,6 +146,7 @@ function configureHostedWithResourceToken(config: ResourceToken) {
resourceToken: parsedResourceToken.resourceToken, resourceToken: parsedResourceToken.resourceToken,
endpoint: parsedResourceToken.accountEndpoint, endpoint: parsedResourceToken.accountEndpoint,
}); });
const explorer = new Explorer(explorerParams);
explorer.resourceTokenDatabaseId(parsedResourceToken.databaseId); explorer.resourceTokenDatabaseId(parsedResourceToken.databaseId);
explorer.resourceTokenCollectionId(parsedResourceToken.collectionId); explorer.resourceTokenCollectionId(parsedResourceToken.collectionId);
if (parsedResourceToken.partitionKey) { if (parsedResourceToken.partitionKey) {
@ -142,9 +158,10 @@ function configureHostedWithResourceToken(config: ResourceToken) {
isAuthWithresourceToken: true, isAuthWithresourceToken: true,
}); });
explorer.isRefreshingExplorer(false); explorer.isRefreshingExplorer(false);
return explorer;
} }
function configureHostedWithEncryptedToken(config: EncryptedToken) { function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParams: ExplorerParams): Explorer {
updateUserContext({ updateUserContext({
authType: AuthType.EncryptedToken, authType: AuthType.EncryptedToken,
accessToken: encodeURIComponent(config.encryptedToken), accessToken: encodeURIComponent(config.encryptedToken),
@ -152,6 +169,7 @@ function configureHostedWithEncryptedToken(config: EncryptedToken) {
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind( const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
config.encryptedTokenMetadata.apiKind config.encryptedTokenMetadata.apiKind
); );
const explorer = new Explorer(explorerParams);
explorer.configure({ explorer.configure({
databaseAccount: { databaseAccount: {
id: "", id: "",
@ -162,21 +180,25 @@ function configureHostedWithEncryptedToken(config: EncryptedToken) {
}, },
features: extractFeatures(), features: extractFeatures(),
}); });
return explorer;
} }
function configureEmulator() { function configureEmulator(explorerParams: ExplorerParams): Explorer {
updateUserContext({ updateUserContext({
databaseAccount: emulatorAccount, databaseAccount: emulatorAccount,
authType: AuthType.MasterKey, authType: AuthType.MasterKey,
}); });
const explorer = new Explorer(explorerParams);
explorer.databaseAccount(emulatorAccount); explorer.databaseAccount(emulatorAccount);
explorer.isAccountReady(true); explorer.isAccountReady(true);
return explorer;
} }
function configurePortal() { async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer> {
updateUserContext({ updateUserContext({
authType: AuthType.AAD, authType: AuthType.AAD,
}); });
return new Promise((resolve) => {
// In development mode, try to load the iframe message from session storage. // In development mode, try to load the iframe message from session storage.
// This allows webpack hot reload to function properly in the portal // This allows webpack hot reload to function properly in the portal
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) { if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
@ -187,8 +209,9 @@ function configurePortal() {
"Loaded cached portal iframe message from session storage. Do a full page refresh to get a new message" "Loaded cached portal iframe message from session storage. Do a full page refresh to get a new message"
); );
console.dir(message); console.dir(message);
const explorer = new Explorer(explorerParams);
explorer.configure(message); explorer.configure(message);
applyExplorerBindings(explorer); resolve(explorer);
} }
} }
@ -236,8 +259,9 @@ function configurePortal() {
quotaId: inputs.quotaId, quotaId: inputs.quotaId,
}); });
const explorer = new Explorer(explorerParams);
explorer.configure(inputs); explorer.configure(inputs);
applyExplorerBindings(explorer); resolve(explorer);
if (openAction) { if (openAction) {
handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer); handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer);
} }
@ -247,6 +271,7 @@ function configurePortal() {
); );
sendMessage("ready"); sendMessage("ready");
});
} }
function shouldProcessMessage(event: MessageEvent): boolean { function shouldProcessMessage(event: MessageEvent): boolean {

View File

@ -1,8 +1,5 @@
import "expect-puppeteer"; import "expect-puppeteer";
import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils"; import { createDatabase, generateUniqueName, onClickSaveButton } from "../utils/shared";
import { createDatabase, onClickSaveButton } from "../utils/shared";
import { generateUniqueName } from "../utils/shared";
import { ApiKind } from "../../src/Contracts/DataModels";
const LOADING_STATE_DELAY = 5000; const LOADING_STATE_DELAY = 5000;
jest.setTimeout(300000); jest.setTimeout(300000);
@ -12,7 +9,9 @@ describe("MongoDB Index policy tests", () => {
try { try {
const singleFieldId = generateUniqueName("key"); const singleFieldId = generateUniqueName("key");
const wildCardId = generateUniqueName("key") + "$**"; const wildCardId = generateUniqueName("key") + "$**";
const frame = await getTestExplorerFrame(ApiKind.MongoDB); await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
const handle = await page.waitForSelector("iframe");
const frame = await handle.contentFrame();
const dropDown = "Index Type "; const dropDown = "Index Type ";
let index = 0; let index = 0;
@ -20,24 +19,18 @@ describe("MongoDB Index policy tests", () => {
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
await frame.waitFor(LOADING_STATE_DELAY); await frame.waitFor(LOADING_STATE_DELAY);
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true }); await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
const dbId = await createDatabase(frame); const { databaseId, collectionId } = await createDatabase(frame);
await frame.waitFor(25000); await frame.waitFor(25000);
// click on database // click on database
await frame.waitForSelector(`div[data-test="${dbId}"]`); await frame.waitForSelector(`div[data-test="${databaseId}"]`);
await frame.waitFor(LOADING_STATE_DELAY); await frame.waitFor(LOADING_STATE_DELAY);
await frame.click(`div[data-test="${dbId}"]`); await frame.click(`div[data-test="${databaseId}"]`);
await frame.waitFor(LOADING_STATE_DELAY); await frame.waitFor(LOADING_STATE_DELAY);
// click on scale & setting // click on scale & setting
const containers = await frame.$$( await frame.waitFor(`div[data-test="${collectionId}"]`), { visible: true };
`div[class="nodeChildren"] > div[class="collectionHeader main2 nodeItem "]> div[class="treeNodeHeader "]`
);
const selectedContainer = (await frame.evaluate((element) => element.innerText, containers[0]))
.replace(/[\u{0080}-\u{FFFF}]/gu, "")
.trim();
await frame.waitFor(`div[data-test="${selectedContainer}"]`), { visible: true };
await frame.waitFor(LOADING_STATE_DELAY); await frame.waitFor(LOADING_STATE_DELAY);
await frame.click(`div[data-test="${selectedContainer}"]`); await frame.click(`div[data-test="${collectionId}"]`);
await frame.waitFor(`div[data-test="Scale & Settings"]`), { visible: true }; await frame.waitFor(`div[data-test="Scale & Settings"]`), { visible: true };
await frame.waitFor(LOADING_STATE_DELAY); await frame.waitFor(LOADING_STATE_DELAY);

View File

@ -1,19 +1,17 @@
import { uploadNotebookIfNotExist } from "./notebookTestUtils"; import { uploadNotebookIfNotExist } from "./notebookTestUtils";
import { ElementHandle, Frame } from "puppeteer";
import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
jest.setTimeout(300000); jest.setTimeout(300000);
const notebookName = "GettingStarted.ipynb"; const notebookName = "GettingStarted.ipynb";
let frame: Frame;
let uploadedNotebookNode: ElementHandle<Element>;
describe("Notebook UI tests", () => { describe("Notebook UI tests", () => {
it("Upload, Open and Delete Notebook", async () => { it("Upload, Open and Delete Notebook", async () => {
try { try {
frame = await getTestExplorerFrame(); await page.goto("https://localhost:1234/testExplorer.html");
const handle = await page.waitForSelector("iframe");
const frame = await handle.contentFrame();
await frame.waitForSelector(".galleryHeader"); await frame.waitForSelector(".galleryHeader");
uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName); const uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
await uploadedNotebookNode.click(); await uploadedNotebookNode.click();
await frame.waitForSelector(".tabNavText"); await frame.waitForSelector(".tabNavText");
const tabTitle = await frame.$eval(".tabNavText", (element) => element.textContent); const tabTitle = await frame.$eval(".tabNavText", (element) => element.textContent);

View File

@ -1,19 +1,11 @@
import { Frame } from "puppeteer";
import { TestExplorerParams } from "../testExplorer/TestExplorerParams";
import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
import { SelfServeType } from "../../src/SelfServe/SelfServeUtils";
import { ApiKind } from "../../src/Contracts/DataModels";
jest.setTimeout(300000); jest.setTimeout(300000);
let frame: Frame;
describe("Self Serve", () => { describe("Self Serve", () => {
it("Launch Self Serve Example", async () => { it("Launch Self Serve Example", async () => {
try { try {
frame = await getTestExplorerFrame( await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
ApiKind.SQL, const handle = await page.waitForSelector("iframe");
new Map<string, string>([[TestExplorerParams.selfServeType, SelfServeType.example]]) const frame = await handle.contentFrame();
);
// wait for refresh RP call to end // wait for refresh RP call to end
await frame.waitFor(10000); await frame.waitFor(10000);

View File

@ -1,82 +1,50 @@
/* eslint-disable no-console */
import { ClientSecretCredential } from "@azure/identity";
import "../../less/hostedexplorer.less"; import "../../less/hostedexplorer.less";
import { TestExplorerParams } from "./TestExplorerParams"; import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb"; import { updateUserContext } from "../../src/UserContext";
import * as msRest from "@azure/ms-rest-js"; import { get, listKeys } from "../../src/Utils/arm/generatedClients/2020-04-01/databaseAccounts";
import * as ViewModels from "../../src/Contracts/ViewModels";
import { Capability, DatabaseAccount } from "../../src/Contracts/DataModels";
class CustomSigner implements msRest.ServiceClientCredentials { const resourceGroup = process.env.RESOURCE_GROUP || "";
private token: string; const subscriptionId = process.env.SUBSCRIPTION_ID || "";
constructor(token: string) { const urlSearchParams = new URLSearchParams(window.location.search);
this.token = token; const accountName = urlSearchParams.get("accountName") || "portal-sql-runner";
const selfServeType = urlSearchParams.get("selfServeType") || "example";
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
if (!process.env.AZURE_CLIENT_SECRET) {
throw new Error(
"process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server"
);
} }
async signRequest(webResource: msRest.WebResourceLike): Promise<msRest.WebResourceLike> { // Azure SDK clients accept the credential as a parameter
webResource.headers.set("authorization", `bearer ${this.token}`); const credentials = new ClientSecretCredential(
return webResource; process.env.AZURE_TENANT_ID,
process.env.AZURE_CLIENT_ID,
process.env.AZURE_CLIENT_SECRET,
{
authorityHost: "https://localhost:1234",
} }
}
const getDatabaseAccount = async (
token: string,
notebooksAccountSubscriptonId: string,
notebooksAccountResourceGroup: string,
notebooksAccountName: string
): Promise<DatabaseAccount> => {
const client = new CosmosDBManagementClient(new CustomSigner(token), notebooksAccountSubscriptonId);
const databaseAccountGetResponse = await client.databaseAccounts.get(
notebooksAccountResourceGroup,
notebooksAccountName
); );
const databaseAccount: DatabaseAccount = { console.log("Resource Group:", resourceGroup);
id: databaseAccountGetResponse.id, console.log("Subcription: ", subscriptionId);
name: databaseAccountGetResponse.name, console.log("Account Name: ", accountName);
location: databaseAccountGetResponse.location,
type: databaseAccountGetResponse.type,
kind: databaseAccountGetResponse.kind,
tags: databaseAccountGetResponse.tags,
properties: {
documentEndpoint: databaseAccountGetResponse.documentEndpoint,
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
capabilities: databaseAccountGetResponse.capabilities.map((capability) => {
return { name: capability.name } as Capability;
}),
},
};
return databaseAccount;
};
const initTestExplorer = async (): Promise<void> => { const initTestExplorer = async (): Promise<void> => {
const urlSearchParams = new URLSearchParams(window.location.search); const { token } = await credentials.getToken("https://management.core.windows.net/.default");
const portalRunnerDatabaseAccount = decodeURIComponent( updateUserContext({
urlSearchParams.get(TestExplorerParams.portalRunnerDatabaseAccount) authorizationToken: `bearer ${token}`,
); });
const portalRunnerDatabaseAccountKey = decodeURIComponent( const databaseAccount = await get(subscriptionId, resourceGroup, accountName);
urlSearchParams.get(TestExplorerParams.portalRunnerDatabaseAccountKey) const keys = await listKeys(subscriptionId, resourceGroup, accountName);
);
const portalRunnerSubscripton = decodeURIComponent(urlSearchParams.get(TestExplorerParams.portalRunnerSubscripton));
const portalRunnerResourceGroup = decodeURIComponent(
urlSearchParams.get(TestExplorerParams.portalRunnerResourceGroup)
);
const selfServeType = urlSearchParams.get(TestExplorerParams.selfServeType);
const token = decodeURIComponent(urlSearchParams.get(TestExplorerParams.token));
const databaseAccount = await getDatabaseAccount(
token,
portalRunnerSubscripton,
portalRunnerResourceGroup,
portalRunnerDatabaseAccount
);
const initTestExplorerContent = { const initTestExplorerContent = {
inputs: { inputs: {
databaseAccount: databaseAccount, databaseAccount: databaseAccount,
subscriptionId: portalRunnerSubscripton, subscriptionId,
resourceGroup: portalRunnerResourceGroup, resourceGroup,
authorizationToken: `Bearer ${token}`, authorizationToken: `Bearer ${token}`,
features: {}, features: {},
hasWriteAccess: true, hasWriteAccess: true,
@ -88,7 +56,7 @@ const initTestExplorer = async (): Promise<void> => {
quotaId: "Internal_2014-09-01", quotaId: "Internal_2014-09-01",
addCollectionDefaultFlight: "2", addCollectionDefaultFlight: "2",
isTryCosmosDBSubscription: false, isTryCosmosDBSubscription: false,
masterKey: portalRunnerDatabaseAccountKey, masterKey: keys.primaryMasterKey,
loadDatabaseAccountTimestamp: 1604663109836, loadDatabaseAccountTimestamp: 1604663109836,
dataExplorerVersion: "1.0.1", dataExplorerVersion: "1.0.1",
sharedThroughputMinimum: 400, sharedThroughputMinimum: 400,
@ -101,7 +69,7 @@ const initTestExplorer = async (): Promise<void> => {
// add UI test only when feature is not dependent on flights anymore // add UI test only when feature is not dependent on flights anymore
flights: [], flights: [],
selfServeType, selfServeType,
} as ViewModels.DataExplorerInputsFrame, } as DataExplorerInputsFrame,
}; };
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
@ -127,16 +95,8 @@ const initTestExplorer = async (): Promise<void> => {
iframe.name = "explorer"; iframe.name = "explorer";
iframe.classList.add("iframe"); iframe.classList.add("iframe");
iframe.title = "explorer"; iframe.title = "explorer";
iframe.src = getIframeSrc(selfServeType); iframe.src = iframeSrc;
document.body.appendChild(iframe); document.body.appendChild(iframe);
}; };
const getIframeSrc = (selfServeType: string): string => {
let iframeSrc = "explorer.html?platform=Portal&disablePortalInitCache";
if (selfServeType) {
iframeSrc = `selfServe.html?selfServeType=${selfServeType}`;
}
return iframeSrc;
};
initTestExplorer(); initTestExplorer();

View File

@ -1,8 +0,0 @@
export enum TestExplorerParams {
portalRunnerDatabaseAccount = "portalRunnerDatabaseAccount",
portalRunnerDatabaseAccountKey = "portalRunnerDatabaseAccountKey",
portalRunnerSubscripton = "portalRunnerSubscripton",
portalRunnerResourceGroup = "portalRunnerResourceGroup",
selfServeType = "selfServeType",
token = "token",
}

View File

@ -1,64 +0,0 @@
import { Frame } from "puppeteer";
import { TestExplorerParams } from "./TestExplorerParams";
import { ClientSecretCredential } from "@azure/identity";
import { ApiKind } from "../../src/Contracts/DataModels";
let testExplorerFrame: Frame;
export const getTestExplorerFrame = async (apiKind?: ApiKind, params?: Map<string, string>): Promise<Frame> => {
if (testExplorerFrame) {
return testExplorerFrame;
}
let portalRunnerDatabaseAccount: string;
let portalRunnerDatabaseAccountKey: string;
switch (apiKind) {
case ApiKind.MongoDB:
portalRunnerDatabaseAccount = process.env.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT;
portalRunnerDatabaseAccountKey = process.env.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY;
break;
default:
portalRunnerDatabaseAccount = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT;
portalRunnerDatabaseAccountKey = process.env.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY;
}
const notebooksTestRunnerTenantId = process.env.NOTEBOOKS_TEST_RUNNER_TENANT_ID;
const notebooksTestRunnerClientId = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_ID;
const notebooksTestRunnerClientSecret = process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET;
const portalRunnerSubscripton = process.env.PORTAL_RUNNER_SUBSCRIPTION;
const portalRunnerResourceGroup = process.env.PORTAL_RUNNER_RESOURCE_GROUP;
const credentials = new ClientSecretCredential(
notebooksTestRunnerTenantId,
notebooksTestRunnerClientId,
notebooksTestRunnerClientSecret
);
const { token } = await credentials.getToken("https://management.core.windows.net/.default");
const testExplorerUrl = new URL("testExplorer.html", "https://localhost:1234");
testExplorerUrl.searchParams.append(
TestExplorerParams.portalRunnerDatabaseAccount,
encodeURI(portalRunnerDatabaseAccount)
);
testExplorerUrl.searchParams.append(
TestExplorerParams.portalRunnerDatabaseAccountKey,
encodeURI(portalRunnerDatabaseAccountKey)
);
testExplorerUrl.searchParams.append(TestExplorerParams.portalRunnerSubscripton, encodeURI(portalRunnerSubscripton));
testExplorerUrl.searchParams.append(
TestExplorerParams.portalRunnerResourceGroup,
encodeURI(portalRunnerResourceGroup)
);
testExplorerUrl.searchParams.append(TestExplorerParams.token, encodeURI(token));
if (params) {
for (const key of params.keys()) {
testExplorerUrl.searchParams.append(key, encodeURI(params.get(key)));
}
}
await page.goto(testExplorerUrl.toString());
const handle = await page.waitForSelector("iframe");
return await handle.contentFrame();
};

View File

@ -30,8 +30,8 @@ export function generateDatabaseName(baseName = "db", length = 1): string {
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`; return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
} }
export async function createDatabase(frame: Frame): Promise<string> { export async function createDatabase(frame: Frame): Promise<{ databaseId: string; collectionId: string }> {
const dbId = generateDatabaseName(); const databaseId = generateDatabaseName();
const collectionId = generateUniqueName("col"); const collectionId = generateUniqueName("col");
const shardKey = "partitionKey"; const shardKey = "partitionKey";
// create new collection // create new collection
@ -50,7 +50,7 @@ export async function createDatabase(frame: Frame): Promise<string> {
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]'); await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]'); const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
await dbInput.press("Backspace"); await dbInput.press("Backspace");
await dbInput.type(dbId); await dbInput.type(databaseId);
// type collection id // type collection id
await frame.waitFor('input[data-test="addCollection-collectionId"]'); await frame.waitFor('input[data-test="addCollection-collectionId"]');
@ -67,7 +67,7 @@ export async function createDatabase(frame: Frame): Promise<string> {
// click submit // click submit
await frame.waitFor("#submitBtnAddCollection"); await frame.waitFor("#submitBtnAddCollection");
await frame.click("#submitBtnAddCollection"); await frame.click("#submitBtnAddCollection");
return dbId; return { databaseId, collectionId };
} }
export async function onClickSaveButton(frame: Frame): Promise<void> { export async function onClickSaveButton(frame: Frame): Promise<void> {

View File

@ -1,7 +1,6 @@
const msRestNodeAuth = require("@azure/ms-rest-nodeauth"); const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb"); const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
const ms = require("ms"); const ms = require("ms");
const { time } = require("console");
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"]; const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"]; const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
@ -9,7 +8,7 @@ const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c"; const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const resourceGroupName = "runners"; const resourceGroupName = "runners";
const sixtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 60).getTime(); const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
function friendlyTime(date) { function friendlyTime(date) {
try { try {
@ -29,7 +28,7 @@ async function main() {
const mongoDatabases = await client.mongoDBResources.listMongoDBDatabases(resourceGroupName, account.name); const mongoDatabases = await client.mongoDBResources.listMongoDBDatabases(resourceGroupName, account.name);
for (const database of mongoDatabases) { for (const database of mongoDatabases) {
const timestamp = Number(database.name.split("-")[1]); const timestamp = Number(database.name.split("-")[1]);
if (timestamp && timestamp < sixtyMinutesAgo) { if (timestamp && timestamp < thirtyMinutesAgo) {
await client.mongoDBResources.deleteMongoDBDatabase(resourceGroupName, account.name, database.name); await client.mongoDBResources.deleteMongoDBDatabase(resourceGroupName, account.name, database.name);
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
} else { } else {
@ -40,7 +39,7 @@ async function main() {
const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name); const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name);
for (const database of sqlDatabases) { for (const database of sqlDatabases) {
const timestamp = Number(database.name.split("-")[1]); const timestamp = Number(database.name.split("-")[1]);
if (timestamp && timestamp < sixtyMinutesAgo) { if (timestamp && timestamp < thirtyMinutesAgo) {
await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name); await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name);
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`); console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
} else { } else {

View File

@ -1,3 +1,4 @@
require("dotenv/config");
const path = require("path"); const path = require("path");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin"); const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
@ -14,6 +15,16 @@ const isCI = require("is-ci");
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8"); const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
const AZURE_CLIENT_ID = "fd8753b0-0707-4e32-84e9-2532af865fb4";
const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const SUBSCRIPTION_ID = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const RESOURCE_GROUP = "runners";
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET
if (!AZURE_CLIENT_SECRET) {
console.warn("AZURE_CLIENT_SECRET is not set. testExplorer.html will not work.");
}
const cssRule = { const cssRule = {
test: /\.css$/, test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"], use: [MiniCssExtractPlugin.loader, "css-loader"],
@ -102,6 +113,11 @@ module.exports = function (env = {}, argv = {}) {
if (mode === "development") { if (mode === "development") {
envVars.NODE_ENV = "development"; envVars.NODE_ENV = "development";
envVars.AZURE_CLIENT_ID = AZURE_CLIENT_ID;
envVars.AZURE_TENANT_ID = AZURE_TENANT_ID;
envVars.AZURE_CLIENT_SECRET = AZURE_CLIENT_SECRET;
envVars.SUBSCRIPTION_ID = SUBSCRIPTION_ID;
envVars.RESOURCE_GROUP = RESOURCE_GROUP;
typescriptRule.use[0].options.compilerOptions = { target: "ES2018" }; typescriptRule.use[0].options.compilerOptions = { target: "ES2018" };
} }
@ -282,6 +298,12 @@ module.exports = function (env = {}, argv = {}) {
secure: false, secure: false,
logLevel: "debug", logLevel: "debug",
}, },
[`/${AZURE_TENANT_ID}`]: {
target: "https://login.microsoftonline.com/",
changeOrigin: true,
secure: false,
logLevel: "debug",
},
}, },
}, },
stats: "minimal", stats: "minimal",