mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-30 17:37:01 +00:00
Merge branch 'master' into languy-resource-tree-to-react
This commit is contained in:
commit
a17d71d76e
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@ -15,10 +15,10 @@ jobs:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: node utils/codeMetrics.js
|
||||
env:
|
||||
@ -28,10 +28,10 @@ jobs:
|
||||
name: "Compile TypeScript"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm run compile
|
||||
- run: npm run compile:strict
|
||||
@ -40,10 +40,10 @@ jobs:
|
||||
name: "Check Format"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm run format:check
|
||||
lint:
|
||||
@ -51,10 +51,10 @@ jobs:
|
||||
name: "Lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
unittest:
|
||||
@ -62,10 +62,10 @@ jobs:
|
||||
name: "Unit Tests"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm run test
|
||||
build:
|
||||
@ -74,10 +74,10 @@ jobs:
|
||||
name: "Build"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- run: npm ci
|
||||
- run: npm run build:contracts
|
||||
- name: Restore Build Cache
|
||||
@ -98,10 +98,10 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- uses: southpolesteve/cosmos-emulator-github-action@v1
|
||||
- name: End to End Tests
|
||||
run: |
|
||||
@ -125,10 +125,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- name: Accessibility Check
|
||||
run: |
|
||||
# Ubuntu gets mad when webpack runs too many files watchers
|
||||
@ -163,6 +163,7 @@ jobs:
|
||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test-file:
|
||||
- ./test/cassandra/container.spec.ts
|
||||
|
110
package-lock.json
generated
110
package-lock.json
generated
@ -949,14 +949,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-class-properties": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz",
|
||||
"integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==",
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
|
||||
"integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz",
|
||||
@ -1951,9 +1957,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/schema": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
|
||||
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -2127,9 +2133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/globals/node_modules/@types/yargs": {
|
||||
"version": "15.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
|
||||
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
@ -4908,9 +4914,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
|
||||
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
"integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
@ -15439,9 +15445,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest/node_modules/@types/yargs": {
|
||||
"version": "15.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
|
||||
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
@ -15756,9 +15762,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest/node_modules/fsevents": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
|
||||
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@ -16751,9 +16757,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest/node_modules/string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
|
||||
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
@ -22760,11 +22766,6 @@
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
},
|
||||
"bin": {
|
||||
"sshpk-conv": "bin/sshpk-conv",
|
||||
"sshpk-sign": "bin/sshpk-sign",
|
||||
"sshpk-verify": "bin/sshpk-verify"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -25098,8 +25099,7 @@
|
||||
"node_modules/webcrypto-liner/node_modules/core-js": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz",
|
||||
"integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==",
|
||||
"hasInstallScript": true
|
||||
"integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q=="
|
||||
},
|
||||
"node_modules/webfontloader": {
|
||||
"version": "1.6.28",
|
||||
@ -27019,12 +27019,20 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-syntax-class-properties": {
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz",
|
||||
"integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==",
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
|
||||
"integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
|
||||
"dev": true,
|
||||
"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": {
|
||||
@ -27993,9 +28001,9 @@
|
||||
}
|
||||
},
|
||||
"@istanbuljs/schema": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz",
|
||||
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==",
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
|
||||
"dev": true
|
||||
},
|
||||
"@jest/console": {
|
||||
@ -28135,9 +28143,9 @@
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
|
||||
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
@ -30752,9 +30760,9 @@
|
||||
}
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz",
|
||||
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
"integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
@ -39218,9 +39226,9 @@
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
|
||||
"integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
|
||||
"version": "15.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz",
|
||||
"integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
@ -39473,9 +39481,9 @@
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
|
||||
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@ -40261,9 +40269,9 @@
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
|
||||
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||
import { configContext, Platform } from "../ConfigContext";
|
||||
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||
import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||
|
||||
const _global = typeof self === "undefined" ? window : self;
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { ARMError } from "../Utils/arm/request";
|
||||
import { HttpStatusCodes } from "./Constants";
|
||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||
import { SubscriptionType } from "../Contracts/SubscriptionType";
|
||||
import { userContext } from "../UserContext";
|
||||
import { ARMError } from "../Utils/arm/request";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
import { HttpStatusCodes } from "./Constants";
|
||||
import { logError } from "./Logger";
|
||||
import { sendMessage } from "./MessageHandler";
|
||||
|
||||
@ -44,7 +45,7 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
|
||||
|
||||
const replaceKnownError = (errorMessage: string): string => {
|
||||
if (
|
||||
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||
userContext.subscriptionType === SubscriptionType.Internal &&
|
||||
errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||
) {
|
||||
return "Database throughput is not supported for internal subscriptions.";
|
||||
|
@ -777,96 +777,88 @@ export default class Explorer {
|
||||
$(document.body).click(() => $(".commandDropdownContainer").hide());
|
||||
});
|
||||
|
||||
// TODO move this to API customization class
|
||||
this.defaultExperience.subscribe((defaultExperience) => {
|
||||
const defaultExperienceNormalizedString = (
|
||||
defaultExperience || Constants.DefaultAccountExperience.Default
|
||||
).toLowerCase();
|
||||
|
||||
switch (defaultExperienceNormalizedString) {
|
||||
case Constants.DefaultAccountExperience.DocumentDB.toLowerCase():
|
||||
this.addCollectionText("New Container");
|
||||
this.addDatabaseText("New Database");
|
||||
this.collectionTitle("SQL API");
|
||||
this.collectionTreeNodeAltText("Container");
|
||||
this.deleteCollectionText("Delete Container");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.addCollectionPane.title("Add Container");
|
||||
this.addCollectionPane.collectionIdTitle("Container id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
||||
"Provision dedicated throughput for this container"
|
||||
);
|
||||
this.deleteCollectionConfirmationPane.title("Delete Container");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
|
||||
this.refreshTreeTitle("Refresh containers");
|
||||
break;
|
||||
case Constants.DefaultAccountExperience.MongoDB.toLowerCase():
|
||||
case Constants.DefaultAccountExperience.ApiForMongoDB.toLowerCase():
|
||||
this.addCollectionText("New Collection");
|
||||
this.addDatabaseText("New Database");
|
||||
this.collectionTitle("Collections");
|
||||
this.collectionTreeNodeAltText("Collection");
|
||||
this.deleteCollectionText("Delete Collection");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.addCollectionPane.title("Add Collection");
|
||||
this.addCollectionPane.collectionIdTitle("Collection id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
||||
"Provision dedicated throughput for this collection"
|
||||
);
|
||||
this.refreshTreeTitle("Refresh collections");
|
||||
break;
|
||||
case Constants.DefaultAccountExperience.Graph.toLowerCase():
|
||||
this.addCollectionText("New Graph");
|
||||
this.addDatabaseText("New Database");
|
||||
this.deleteCollectionText("Delete Graph");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.collectionTitle("Gremlin API");
|
||||
this.collectionTreeNodeAltText("Graph");
|
||||
this.addCollectionPane.title("Add Graph");
|
||||
this.addCollectionPane.collectionIdTitle("Graph id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Graph");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
|
||||
this.refreshTreeTitle("Refresh graphs");
|
||||
break;
|
||||
case Constants.DefaultAccountExperience.Table.toLowerCase():
|
||||
this.addCollectionText("New Table");
|
||||
this.addDatabaseText("New Database");
|
||||
this.deleteCollectionText("Delete Table");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.collectionTitle("Azure Table API");
|
||||
this.collectionTreeNodeAltText("Table");
|
||||
this.addCollectionPane.title("Add Table");
|
||||
this.addCollectionPane.collectionIdTitle("Table id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||
this.refreshTreeTitle("Refresh tables");
|
||||
this.addTableEntityPane.title("Add Table Entity");
|
||||
this.editTableEntityPane.title("Edit Table Entity");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
||||
this.tableDataClient = new TablesAPIDataClient();
|
||||
break;
|
||||
case Constants.DefaultAccountExperience.Cassandra.toLowerCase():
|
||||
this.addCollectionText("New Table");
|
||||
this.addDatabaseText("New Keyspace");
|
||||
this.deleteCollectionText("Delete Table");
|
||||
this.deleteDatabaseText("Delete Keyspace");
|
||||
this.collectionTitle("Cassandra API");
|
||||
this.collectionTreeNodeAltText("Table");
|
||||
this.addCollectionPane.title("Add Table");
|
||||
this.addCollectionPane.collectionIdTitle("Table id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||
this.refreshTreeTitle("Refresh tables");
|
||||
this.addTableEntityPane.title("Add Table Row");
|
||||
this.editTableEntityPane.title("Edit Table Row");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
||||
this.deleteDatabaseConfirmationPane.title("Delete Keyspace");
|
||||
this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id");
|
||||
this.tableDataClient = new CassandraAPIDataClient();
|
||||
break;
|
||||
}
|
||||
});
|
||||
switch (userContext.apiType) {
|
||||
case "SQL":
|
||||
this.addCollectionText("New Container");
|
||||
this.addDatabaseText("New Database");
|
||||
this.collectionTitle("SQL API");
|
||||
this.collectionTreeNodeAltText("Container");
|
||||
this.deleteCollectionText("Delete Container");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.addCollectionPane.title("Add Container");
|
||||
this.addCollectionPane.collectionIdTitle("Container id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
||||
"Provision dedicated throughput for this container"
|
||||
);
|
||||
this.deleteCollectionConfirmationPane.title("Delete Container");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
|
||||
this.refreshTreeTitle("Refresh containers");
|
||||
break;
|
||||
case "Mongo":
|
||||
this.addCollectionText("New Collection");
|
||||
this.addDatabaseText("New Database");
|
||||
this.collectionTitle("Collections");
|
||||
this.collectionTreeNodeAltText("Collection");
|
||||
this.deleteCollectionText("Delete Collection");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.addCollectionPane.title("Add Collection");
|
||||
this.addCollectionPane.collectionIdTitle("Collection id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
||||
"Provision dedicated throughput for this collection"
|
||||
);
|
||||
this.refreshTreeTitle("Refresh collections");
|
||||
break;
|
||||
case "Gremlin":
|
||||
this.addCollectionText("New Graph");
|
||||
this.addDatabaseText("New Database");
|
||||
this.deleteCollectionText("Delete Graph");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.collectionTitle("Gremlin API");
|
||||
this.collectionTreeNodeAltText("Graph");
|
||||
this.addCollectionPane.title("Add Graph");
|
||||
this.addCollectionPane.collectionIdTitle("Graph id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Graph");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
|
||||
this.refreshTreeTitle("Refresh graphs");
|
||||
break;
|
||||
case "Tables":
|
||||
this.addCollectionText("New Table");
|
||||
this.addDatabaseText("New Database");
|
||||
this.deleteCollectionText("Delete Table");
|
||||
this.deleteDatabaseText("Delete Database");
|
||||
this.collectionTitle("Azure Table API");
|
||||
this.collectionTreeNodeAltText("Table");
|
||||
this.addCollectionPane.title("Add Table");
|
||||
this.addCollectionPane.collectionIdTitle("Table id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||
this.refreshTreeTitle("Refresh tables");
|
||||
this.addTableEntityPane.title("Add Table Entity");
|
||||
this.editTableEntityPane.title("Edit Table Entity");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
||||
this.tableDataClient = new TablesAPIDataClient();
|
||||
break;
|
||||
case "Cassandra":
|
||||
this.addCollectionText("New Table");
|
||||
this.addDatabaseText("New Keyspace");
|
||||
this.deleteCollectionText("Delete Table");
|
||||
this.deleteDatabaseText("Delete Keyspace");
|
||||
this.collectionTitle("Cassandra API");
|
||||
this.collectionTreeNodeAltText("Table");
|
||||
this.addCollectionPane.title("Add Table");
|
||||
this.addCollectionPane.collectionIdTitle("Table id");
|
||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||
this.refreshTreeTitle("Refresh tables");
|
||||
this.addTableEntityPane.title("Add Table Row");
|
||||
this.editTableEntityPane.title("Edit Table Row");
|
||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
||||
this.deleteDatabaseConfirmationPane.title("Delete Keyspace");
|
||||
this.deleteDatabaseConfirmationPane.databaseIdConfirmationText("Confirm by typing the keyspace id");
|
||||
this.tableDataClient = new CassandraAPIDataClient();
|
||||
break;
|
||||
}
|
||||
|
||||
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);
|
||||
|
||||
|
86
src/Explorer/MostRecentActivity/MostRecentActivity.test.ts
Normal file
86
src/Explorer/MostRecentActivity/MostRecentActivity.test.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { observable } from "knockout";
|
||||
import { mostRecentActivity } from "./MostRecentActivity";
|
||||
|
||||
describe("MostRecentActivity", () => {
|
||||
const accountId = "some account";
|
||||
|
||||
beforeEach(() => mostRecentActivity.clear(accountId));
|
||||
|
||||
it("Has no items at first", () => {
|
||||
expect(mostRecentActivity.getItems(accountId)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("Can record collections being opened", () => {
|
||||
const collectionId = "some collection";
|
||||
const databaseId = "some database";
|
||||
const collection = {
|
||||
id: observable(collectionId),
|
||||
databaseId,
|
||||
};
|
||||
|
||||
mostRecentActivity.collectionWasOpened(accountId, collection);
|
||||
|
||||
const activity = mostRecentActivity.getItems(accountId);
|
||||
expect(activity).toEqual([
|
||||
expect.objectContaining({
|
||||
collectionId,
|
||||
databaseId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("Can record notebooks being opened", () => {
|
||||
const name = "some notebook";
|
||||
const path = "some path";
|
||||
const notebook = { name, path };
|
||||
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
|
||||
|
||||
const activity = mostRecentActivity.getItems(accountId);
|
||||
expect(activity).toEqual([expect.objectContaining(notebook)]);
|
||||
});
|
||||
|
||||
it("Filters out duplicates", () => {
|
||||
const name = "some notebook";
|
||||
const path = "some path";
|
||||
const notebook = { name, path };
|
||||
const sameNotebook = { name, path };
|
||||
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, sameNotebook);
|
||||
|
||||
const activity = mostRecentActivity.getItems(accountId);
|
||||
expect(activity.length).toEqual(1);
|
||||
expect(activity).toEqual([expect.objectContaining(notebook)]);
|
||||
});
|
||||
|
||||
it("Allows for multiple accounts", () => {
|
||||
const name = "some notebook";
|
||||
const path = "some path";
|
||||
const notebook = { name, path };
|
||||
|
||||
const anotherNotebook = { name: "Another " + name, path };
|
||||
const anotherAccountId = "Another " + accountId;
|
||||
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, notebook);
|
||||
mostRecentActivity.notebookWasItemOpened(anotherAccountId, anotherNotebook);
|
||||
|
||||
expect(mostRecentActivity.getItems(accountId)).toEqual([expect.objectContaining(notebook)]);
|
||||
expect(mostRecentActivity.getItems(anotherAccountId)).toEqual([expect.objectContaining(anotherNotebook)]);
|
||||
});
|
||||
|
||||
it("Can store multiple distinct elements, in FIFO order", () => {
|
||||
const name = "some notebook";
|
||||
const path = "some path";
|
||||
const first = { name, path };
|
||||
const second = { name: "Another " + name, path };
|
||||
const third = { name, path: "Another " + path };
|
||||
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, first);
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, second);
|
||||
mostRecentActivity.notebookWasItemOpened(accountId, third);
|
||||
|
||||
const activity = mostRecentActivity.getItems(accountId);
|
||||
expect(activity).toEqual([third, second, first].map(expect.objectContaining));
|
||||
});
|
||||
});
|
@ -1,4 +1,6 @@
|
||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||
import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
|
||||
export enum Type {
|
||||
OpenCollection,
|
||||
@ -6,21 +8,18 @@ export enum Type {
|
||||
}
|
||||
|
||||
export interface OpenNotebookItem {
|
||||
type: Type.OpenNotebook;
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface OpenCollectionItem {
|
||||
type: Type.OpenCollection;
|
||||
databaseId: string;
|
||||
collectionId: string;
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
type: Type;
|
||||
title: string;
|
||||
description: string;
|
||||
data: OpenNotebookItem | OpenCollectionItem;
|
||||
}
|
||||
type Item = OpenNotebookItem | OpenCollectionItem;
|
||||
|
||||
// Update schemaVersion if you are going to change this interface
|
||||
interface StoredData {
|
||||
@ -32,7 +31,7 @@ interface StoredData {
|
||||
* Stores most recent activity
|
||||
*/
|
||||
class MostRecentActivity {
|
||||
private static readonly schemaVersion: string = "1";
|
||||
private static readonly schemaVersion: string = "2";
|
||||
private static itemsMaxNumber: number = 5;
|
||||
private storedData: StoredData;
|
||||
constructor() {
|
||||
@ -92,7 +91,7 @@ class MostRecentActivity {
|
||||
LocalStorageUtility.setEntryString(StorageKey.MostRecentActivity, JSON.stringify(this.storedData));
|
||||
}
|
||||
|
||||
public addItem(accountId: string, newItem: Item): void {
|
||||
private addItem(accountId: string, newItem: Item): void {
|
||||
// When debugging, accountId is "undefined": most recent activity cannot be saved by account. Uncomment to disable.
|
||||
// if (!accountId) {
|
||||
// return;
|
||||
@ -111,6 +110,23 @@ class MostRecentActivity {
|
||||
return this.storedData.itemsMap[accountId] || [];
|
||||
}
|
||||
|
||||
public collectionWasOpened(accountId: string, { id, databaseId }: Pick<CollectionBase, "id" | "databaseId">) {
|
||||
const collectionId = id();
|
||||
this.addItem(accountId, {
|
||||
type: Type.OpenCollection,
|
||||
databaseId,
|
||||
collectionId,
|
||||
});
|
||||
}
|
||||
|
||||
public notebookWasItemOpened(accountId: string, { name, path }: Pick<NotebookContentItem, "name" | "path">) {
|
||||
this.addItem(accountId, {
|
||||
type: Type.OpenNotebook,
|
||||
name,
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
public clear(accountId: string): void {
|
||||
delete this.storedData.itemsMap[accountId];
|
||||
this.saveToLocalStorage();
|
||||
@ -128,11 +144,7 @@ class MostRecentActivity {
|
||||
let index = -1;
|
||||
for (let i = 0; i < itemsArray.length; i++) {
|
||||
const currentItem = itemsArray[i];
|
||||
if (
|
||||
currentItem.title === item.title &&
|
||||
currentItem.description === item.description &&
|
||||
JSON.stringify(currentItem.data) === JSON.stringify(item.data)
|
||||
) {
|
||||
if (JSON.stringify(currentItem) === JSON.stringify(item)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
@ -217,42 +217,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
return heroes;
|
||||
}
|
||||
|
||||
private getItemIcon(item: MostRecentActivity.Item): string {
|
||||
switch (item.type) {
|
||||
case MostRecentActivity.Type.OpenCollection:
|
||||
return CollectionIcon;
|
||||
case MostRecentActivity.Type.OpenNotebook:
|
||||
return NotebookIcon;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private onItemClicked(item: MostRecentActivity.Item) {
|
||||
switch (item.type) {
|
||||
case MostRecentActivity.Type.OpenCollection: {
|
||||
const openCollectionitem = item.data as MostRecentActivity.OpenCollectionItem;
|
||||
const collection = this.container.findCollection(
|
||||
openCollectionitem.databaseId,
|
||||
openCollectionitem.collectionId
|
||||
);
|
||||
if (collection) {
|
||||
collection.openTab();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MostRecentActivity.Type.OpenNotebook: {
|
||||
const openNotebookItem = item.data as MostRecentActivity.OpenNotebookItem;
|
||||
const notebookItem = this.container.createNotebookContentItemFile(openNotebookItem.name, openNotebookItem.path);
|
||||
notebookItem && this.container.openNotebook(notebookItem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error("Unknown item type", item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private createCommonTaskItems(): SplashScreenItem[] {
|
||||
const items: SplashScreenItem[] = [];
|
||||
|
||||
@ -333,23 +297,45 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
return items;
|
||||
}
|
||||
|
||||
private static getInfo(item: MostRecentActivity.Item): string {
|
||||
if (item.type === MostRecentActivity.Type.OpenNotebook) {
|
||||
const data = item.data as MostRecentActivity.OpenNotebookItem;
|
||||
return data.path;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
|
||||
return {
|
||||
iconSrc: NotebookIcon,
|
||||
title: collectionId,
|
||||
description: "Data",
|
||||
onClick: () => {
|
||||
const collection = this.container.findCollection(databaseId, collectionId);
|
||||
collection && collection.openTab();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private decorateOpenNotebookActivity({ name, path }: MostRecentActivity.OpenNotebookItem) {
|
||||
return {
|
||||
info: path,
|
||||
iconSrc: CollectionIcon,
|
||||
title: name,
|
||||
description: "Notebook",
|
||||
onClick: () => {
|
||||
const notebookItem = this.container.createNotebookContentItemFile(name, path);
|
||||
notebookItem && this.container.openNotebook(notebookItem);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private createRecentItems(): SplashScreenItem[] {
|
||||
return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((item) => ({
|
||||
iconSrc: this.getItemIcon(item),
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
info: SplashScreen.getInfo(item),
|
||||
onClick: () => this.onItemClicked(item),
|
||||
}));
|
||||
return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((activity) => {
|
||||
switch (activity.type) {
|
||||
default: {
|
||||
const unknownActivity: never = activity;
|
||||
throw new Error(`Unknown activity: ${unknownActivity}`);
|
||||
}
|
||||
case MostRecentActivity.Type.OpenNotebook:
|
||||
return this.decorateOpenNotebookActivity(activity);
|
||||
|
||||
case MostRecentActivity.Type.OpenCollection:
|
||||
return this.decorateOpenCollectionActivity(activity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createTipsItems(): SplashScreenItem[] {
|
||||
|
@ -5,7 +5,7 @@ import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeCompo
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
||||
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
|
||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||
import CollectionIcon from "../../../images/tree-collection.svg";
|
||||
@ -179,15 +179,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps> {
|
||||
onClick: () => {
|
||||
collection.openTab();
|
||||
// push to most recent
|
||||
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, {
|
||||
type: MostRecentActivity.Type.OpenCollection,
|
||||
title: collection.id(),
|
||||
description: "Data",
|
||||
data: {
|
||||
databaseId: collection.databaseId,
|
||||
collectionId: collection.id(),
|
||||
},
|
||||
});
|
||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), [
|
||||
@ -492,7 +484,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps> {
|
||||
(item: NotebookContentItem) => {
|
||||
this.container.openNotebook(item).then((hasOpened) => {
|
||||
if (hasOpened) {
|
||||
this.pushItemToMostRecent(item);
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -513,7 +505,7 @@ export class ResourceTree extends React.Component<ResourceTreeProps> {
|
||||
(item: NotebookContentItem) => {
|
||||
this.container.openNotebook(item).then((hasOpened) => {
|
||||
if (hasOpened) {
|
||||
this.pushItemToMostRecent(item);
|
||||
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -543,18 +535,6 @@ export class ResourceTree extends React.Component<ResourceTreeProps> {
|
||||
return gitHubNotebooksTree;
|
||||
}
|
||||
|
||||
private pushItemToMostRecent(item: NotebookContentItem) {
|
||||
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, {
|
||||
type: MostRecentActivity.Type.OpenNotebook,
|
||||
title: item.name,
|
||||
description: "Notebook",
|
||||
data: {
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private buildChildNodes(
|
||||
item: NotebookContentItem,
|
||||
onFileClick: (item: NotebookContentItem) => void,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as ko from "knockout";
|
||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||
import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
@ -44,15 +44,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
|
||||
onClick: () => {
|
||||
collection.onDocumentDBDocumentsClick();
|
||||
// push to most recent
|
||||
MostRecentActivity.mostRecentActivity.addItem(userContext.databaseAccount?.id, {
|
||||
type: MostRecentActivity.Type.OpenCollection,
|
||||
title: collection.id(),
|
||||
description: "Data",
|
||||
data: {
|
||||
databaseId: collection.databaseId,
|
||||
collectionId: collection.id(),
|
||||
},
|
||||
});
|
||||
mostRecentActivity.collectionWasOpened(userContext.databaseAccount?.id, collection);
|
||||
},
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.databaseId, collection.id(), ViewModels.CollectionTabKind.Documents),
|
||||
|
149
src/Main.tsx
149
src/Main.tsx
@ -1,76 +1,71 @@
|
||||
// CSS Dependencies
|
||||
import "abort-controller/polyfill";
|
||||
import "babel-polyfill";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "../less/documentDB.less";
|
||||
import "../less/tree.less";
|
||||
import "../less/forms.less";
|
||||
import "../less/menus.less";
|
||||
import "../less/infobox.less";
|
||||
import "../less/messagebox.less";
|
||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
|
||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||
import "./Explorer/Menus/CommandBar/MemoryTrackerComponent.less";
|
||||
import "./Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.less";
|
||||
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 "es6-object-assign/auto";
|
||||
import "es6-symbol/implement";
|
||||
import "object.entries/auto";
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
import "promise-polyfill/src/polyfill";
|
||||
import "promise.prototype.finally/auto";
|
||||
import React, { useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "url-polyfill/url-polyfill.min";
|
||||
import "webcrypto-liner/build/webcrypto-liner.shim.min";
|
||||
import "whatwg-fetch";
|
||||
import "../externals/jquery-ui.min.css";
|
||||
import "../externals/jquery-ui.min.js";
|
||||
import "../externals/jquery-ui.structure.min.css";
|
||||
import "../externals/jquery-ui.theme.min.css";
|
||||
import "./Explorer/Graph/NewVertexComponent/newVertexComponent.less";
|
||||
import "./Explorer/Panes/GraphNewVertexPane.less";
|
||||
import "./Explorer/Tabs/QueryTab.less";
|
||||
import "./Explorer/Controls/TreeComponent/treeComponent.less";
|
||||
import "./Explorer/Controls/Accordion/AccordionComponent.less";
|
||||
import "./Explorer/SplashScreen/SplashScreen.less";
|
||||
import "./Explorer/Controls/Notebook/NotebookTerminalComponent.less";
|
||||
|
||||
import "../externals/jquery.dataTables.min.css";
|
||||
import "../externals/jquery.typeahead.min.css";
|
||||
import "../externals/jquery.typeahead.min.js";
|
||||
// Image Dependencies
|
||||
import "../images/CosmosDB_rgb_ui_lighttheme.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 Explorer, { ExplorerParams } from "./Explorer/Explorer";
|
||||
import React, { useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
||||
import refreshImg from "../images/refresh-cosmos.svg";
|
||||
import arrowLeftImg from "../images/imgarrowlefticon.svg";
|
||||
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
|
||||
import { useConfig } from "./hooks/useConfig";
|
||||
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
|
||||
import { useSidePanel } from "./hooks/useSidePanel";
|
||||
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 Explorer, { 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 { Dialog, DialogProps } from "./Explorer/Controls/Dialog";
|
||||
import { ResourceTree } from "./Explorer/Tree/ResourceTree";
|
||||
import "./Explorer/SplashScreen/SplashScreen.less";
|
||||
import "./Explorer/Tabs/QueryTab.less";
|
||||
import { useConfig } from "./hooks/useConfig";
|
||||
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
|
||||
import { useNotebooks } from "./hooks/useNotebooks";
|
||||
import { useSidePanel } from "./hooks/useSidePanel";
|
||||
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
|
||||
import "./Libs/is-integer-polyfill";
|
||||
import "./Libs/jquery";
|
||||
import "./Shared/appInsights";
|
||||
|
||||
initializeIcons();
|
||||
|
||||
@ -121,6 +116,9 @@ const App: React.FunctionComponent = () => {
|
||||
const explorer = useKnockoutExplorer(config?.platform, explorerParams);
|
||||
|
||||
context.container = explorer;
|
||||
if (!explorer) {
|
||||
return <LoadingExplorer />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flexContainer">
|
||||
@ -263,21 +261,6 @@ const App: React.FunctionComponent = () => {
|
||||
/>
|
||||
</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
|
||||
isOpen={isPanelOpen}
|
||||
panelContent={panelContent}
|
||||
@ -321,3 +304,21 @@ const App: React.FunctionComponent = () => {
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { applyExplorerBindings } from "../applyExplorerBindings";
|
||||
import { AuthType } from "../AuthType";
|
||||
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 has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
|
||||
// Pleas tread carefully :)
|
||||
let explorer: Explorer;
|
||||
|
||||
export function useKnockoutExplorer(platform: Platform, explorerParams: ExplorerParams): Explorer {
|
||||
explorer = explorer || new Explorer(explorerParams);
|
||||
const [explorer, setExplorer] = useState<Explorer>();
|
||||
|
||||
useEffect(() => {
|
||||
const effect = async () => {
|
||||
if (platform) {
|
||||
if (platform === Platform.Hosted) {
|
||||
await configureHosted();
|
||||
applyExplorerBindings(explorer);
|
||||
const explorer = await configureHosted(explorerParams);
|
||||
setExplorer(explorer);
|
||||
} else if (platform === Platform.Emulator) {
|
||||
configureEmulator();
|
||||
applyExplorerBindings(explorer);
|
||||
const explorer = configureEmulator(explorerParams);
|
||||
setExplorer(explorer);
|
||||
} else if (platform === Platform.Portal) {
|
||||
configurePortal();
|
||||
const explorer = await configurePortal(explorerParams);
|
||||
setExplorer(explorer);
|
||||
}
|
||||
}
|
||||
};
|
||||
effect();
|
||||
}, [platform]);
|
||||
|
||||
useEffect(() => {
|
||||
if (explorer) {
|
||||
applyExplorerBindings(explorer);
|
||||
}
|
||||
}, [explorer]);
|
||||
|
||||
return explorer;
|
||||
}
|
||||
|
||||
async function configureHosted() {
|
||||
async function configureHosted(explorerParams: ExplorerParams): Promise<Explorer> {
|
||||
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||
configureHostedWithEncryptedToken(win.hostedConfig);
|
||||
return configureHostedWithEncryptedToken(win.hostedConfig, explorerParams);
|
||||
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||
configureHostedWithResourceToken(win.hostedConfig);
|
||||
return configureHostedWithResourceToken(win.hostedConfig, explorerParams);
|
||||
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||
configureHostedWithConnectionString(win.hostedConfig);
|
||||
return configureHostedWithConnectionString(win.hostedConfig, explorerParams);
|
||||
} 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 accountResourceId = account.id;
|
||||
const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
|
||||
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||
updateUserContext({
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
authType: AuthType.AAD,
|
||||
authorizationToken: `Bearer ${config.authorizationToken}`,
|
||||
databaseAccount: config.databaseAccount,
|
||||
});
|
||||
const keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.configure({
|
||||
databaseAccount: account,
|
||||
subscriptionId,
|
||||
@ -87,9 +99,10 @@ async function configureHostedWithAAD(config: AAD) {
|
||||
authorizationToken: `Bearer ${config.authorizationToken}`,
|
||||
features: extractFeatures(),
|
||||
});
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configureHostedWithConnectionString(config: ConnectionString) {
|
||||
function configureHostedWithConnectionString(config: ConnectionString, explorerParams: ExplorerParams): Explorer {
|
||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||
const databaseAccount = {
|
||||
id: "",
|
||||
@ -106,14 +119,16 @@ function configureHostedWithConnectionString(config: ConnectionString) {
|
||||
accessToken: encodeURIComponent(config.encryptedToken),
|
||||
databaseAccount,
|
||||
});
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.configure({
|
||||
databaseAccount,
|
||||
masterKey: config.masterKey,
|
||||
features: extractFeatures(),
|
||||
});
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configureHostedWithResourceToken(config: ResourceToken) {
|
||||
function configureHostedWithResourceToken(config: ResourceToken, explorerParams: ExplorerParams): Explorer {
|
||||
const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
|
||||
const databaseAccount = {
|
||||
id: "",
|
||||
@ -131,6 +146,7 @@ function configureHostedWithResourceToken(config: ResourceToken) {
|
||||
resourceToken: parsedResourceToken.resourceToken,
|
||||
endpoint: parsedResourceToken.accountEndpoint,
|
||||
});
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.resourceTokenDatabaseId(parsedResourceToken.databaseId);
|
||||
explorer.resourceTokenCollectionId(parsedResourceToken.collectionId);
|
||||
if (parsedResourceToken.partitionKey) {
|
||||
@ -142,9 +158,10 @@ function configureHostedWithResourceToken(config: ResourceToken) {
|
||||
isAuthWithresourceToken: true,
|
||||
});
|
||||
explorer.isRefreshingExplorer(false);
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configureHostedWithEncryptedToken(config: EncryptedToken) {
|
||||
function configureHostedWithEncryptedToken(config: EncryptedToken, explorerParams: ExplorerParams): Explorer {
|
||||
updateUserContext({
|
||||
authType: AuthType.EncryptedToken,
|
||||
accessToken: encodeURIComponent(config.encryptedToken),
|
||||
@ -152,6 +169,7 @@ function configureHostedWithEncryptedToken(config: EncryptedToken) {
|
||||
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
||||
config.encryptedTokenMetadata.apiKind
|
||||
);
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.configure({
|
||||
databaseAccount: {
|
||||
id: "",
|
||||
@ -162,91 +180,98 @@ function configureHostedWithEncryptedToken(config: EncryptedToken) {
|
||||
},
|
||||
features: extractFeatures(),
|
||||
});
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configureEmulator() {
|
||||
function configureEmulator(explorerParams: ExplorerParams): Explorer {
|
||||
updateUserContext({
|
||||
databaseAccount: emulatorAccount,
|
||||
authType: AuthType.MasterKey,
|
||||
});
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.databaseAccount(emulatorAccount);
|
||||
explorer.isAccountReady(true);
|
||||
return explorer;
|
||||
}
|
||||
|
||||
function configurePortal() {
|
||||
async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer> {
|
||||
updateUserContext({
|
||||
authType: AuthType.AAD,
|
||||
});
|
||||
// In development mode, try to load the iframe message from session storage.
|
||||
// This allows webpack hot reload to function properly in the portal
|
||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
|
||||
if (initMessage) {
|
||||
const message = JSON.parse(initMessage);
|
||||
console.warn(
|
||||
"Loaded cached portal iframe message from session storage. Do a full page refresh to get a new message"
|
||||
);
|
||||
console.dir(message);
|
||||
explorer.configure(message);
|
||||
applyExplorerBindings(explorer);
|
||||
return new Promise((resolve) => {
|
||||
// In development mode, try to load the iframe message from session storage.
|
||||
// This allows webpack hot reload to function properly in the portal
|
||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
|
||||
if (initMessage) {
|
||||
const message = JSON.parse(initMessage);
|
||||
console.warn(
|
||||
"Loaded cached portal iframe message from session storage. Do a full page refresh to get a new message"
|
||||
);
|
||||
console.dir(message);
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.configure(message);
|
||||
resolve(explorer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In the Portal, configuration of Explorer happens via iframe message
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (isInvalidParentFrameOrigin(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldProcessMessage(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for init message
|
||||
const message: PortalMessage = event.data?.data;
|
||||
const inputs = message?.inputs;
|
||||
const openAction = message?.openAction;
|
||||
if (inputs) {
|
||||
if (
|
||||
configContext.BACKEND_ENDPOINT &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
process.env.NODE_ENV === "development"
|
||||
) {
|
||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||
// In the Portal, configuration of Explorer happens via iframe message
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (isInvalidParentFrameOrigin(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authorizationToken = inputs.authorizationToken || "";
|
||||
const masterKey = inputs.masterKey || "";
|
||||
const databaseAccount = inputs.databaseAccount;
|
||||
|
||||
updateConfigContext({
|
||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT,
|
||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
||||
});
|
||||
|
||||
updateUserContext({
|
||||
authorizationToken,
|
||||
masterKey,
|
||||
databaseAccount,
|
||||
resourceGroup: inputs.resourceGroup,
|
||||
subscriptionId: inputs.subscriptionId,
|
||||
subscriptionType: inputs.subscriptionType,
|
||||
quotaId: inputs.quotaId,
|
||||
});
|
||||
|
||||
explorer.configure(inputs);
|
||||
applyExplorerBindings(explorer);
|
||||
if (openAction) {
|
||||
handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer);
|
||||
if (!shouldProcessMessage(event)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
sendMessage("ready");
|
||||
// Check for init message
|
||||
const message: PortalMessage = event.data?.data;
|
||||
const inputs = message?.inputs;
|
||||
const openAction = message?.openAction;
|
||||
if (inputs) {
|
||||
if (
|
||||
configContext.BACKEND_ENDPOINT &&
|
||||
configContext.platform === Platform.Portal &&
|
||||
process.env.NODE_ENV === "development"
|
||||
) {
|
||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||
}
|
||||
|
||||
const authorizationToken = inputs.authorizationToken || "";
|
||||
const masterKey = inputs.masterKey || "";
|
||||
const databaseAccount = inputs.databaseAccount;
|
||||
|
||||
updateConfigContext({
|
||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || configContext.BACKEND_ENDPOINT,
|
||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
||||
});
|
||||
|
||||
updateUserContext({
|
||||
authorizationToken,
|
||||
masterKey,
|
||||
databaseAccount,
|
||||
resourceGroup: inputs.resourceGroup,
|
||||
subscriptionId: inputs.subscriptionId,
|
||||
subscriptionType: inputs.subscriptionType,
|
||||
quotaId: inputs.quotaId,
|
||||
});
|
||||
|
||||
const explorer = new Explorer(explorerParams);
|
||||
explorer.configure(inputs);
|
||||
resolve(explorer);
|
||||
if (openAction) {
|
||||
handleOpenAction(openAction, explorer.nonSystemDatabases(), explorer);
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
sendMessage("ready");
|
||||
});
|
||||
}
|
||||
|
||||
function shouldProcessMessage(event: MessageEvent): boolean {
|
||||
|
@ -1,8 +1,5 @@
|
||||
import "expect-puppeteer";
|
||||
import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
|
||||
import { createDatabase, onClickSaveButton } from "../utils/shared";
|
||||
import { generateUniqueName } from "../utils/shared";
|
||||
import { ApiKind } from "../../src/Contracts/DataModels";
|
||||
import { createDatabase, generateUniqueName, onClickSaveButton } from "../utils/shared";
|
||||
|
||||
const LOADING_STATE_DELAY = 5000;
|
||||
jest.setTimeout(300000);
|
||||
@ -12,7 +9,9 @@ describe("MongoDB Index policy tests", () => {
|
||||
try {
|
||||
const singleFieldId = 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 ";
|
||||
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.waitFor(LOADING_STATE_DELAY);
|
||||
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);
|
||||
// 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.click(`div[data-test="${dbId}"]`);
|
||||
await frame.click(`div[data-test="${databaseId}"]`);
|
||||
await frame.waitFor(LOADING_STATE_DELAY);
|
||||
|
||||
// click on scale & setting
|
||||
const containers = await frame.$$(
|
||||
`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(`div[data-test="${collectionId}"]`), { visible: true };
|
||||
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(LOADING_STATE_DELAY);
|
||||
|
@ -1,19 +1,17 @@
|
||||
import { uploadNotebookIfNotExist } from "./notebookTestUtils";
|
||||
import { ElementHandle, Frame } from "puppeteer";
|
||||
import { getTestExplorerFrame } from "../testExplorer/TestExplorerUtils";
|
||||
|
||||
jest.setTimeout(300000);
|
||||
|
||||
const notebookName = "GettingStarted.ipynb";
|
||||
let frame: Frame;
|
||||
let uploadedNotebookNode: ElementHandle<Element>;
|
||||
|
||||
describe("Notebook UI tests", () => {
|
||||
it("Upload, Open and Delete Notebook", async () => {
|
||||
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");
|
||||
uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
|
||||
const uploadedNotebookNode = await uploadNotebookIfNotExist(frame, notebookName);
|
||||
await uploadedNotebookNode.click();
|
||||
await frame.waitForSelector(".tabNavText");
|
||||
const tabTitle = await frame.$eval(".tabNavText", (element) => element.textContent);
|
||||
|
@ -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);
|
||||
|
||||
let frame: Frame;
|
||||
describe("Self Serve", () => {
|
||||
it("Launch Self Serve Example", async () => {
|
||||
try {
|
||||
frame = await getTestExplorerFrame(
|
||||
ApiKind.SQL,
|
||||
new Map<string, string>([[TestExplorerParams.selfServeType, SelfServeType.example]])
|
||||
);
|
||||
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
||||
const handle = await page.waitForSelector("iframe");
|
||||
const frame = await handle.contentFrame();
|
||||
|
||||
// wait for refresh RP call to end
|
||||
await frame.waitFor(10000);
|
||||
|
@ -1,82 +1,50 @@
|
||||
/* eslint-disable no-console */
|
||||
import { ClientSecretCredential } from "@azure/identity";
|
||||
import "../../less/hostedexplorer.less";
|
||||
import { TestExplorerParams } from "./TestExplorerParams";
|
||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||
import * as msRest from "@azure/ms-rest-js";
|
||||
import * as ViewModels from "../../src/Contracts/ViewModels";
|
||||
import { Capability, DatabaseAccount } from "../../src/Contracts/DataModels";
|
||||
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
||||
import { updateUserContext } from "../../src/UserContext";
|
||||
import { get, listKeys } from "../../src/Utils/arm/generatedClients/2020-04-01/databaseAccounts";
|
||||
|
||||
class CustomSigner implements msRest.ServiceClientCredentials {
|
||||
private token: string;
|
||||
constructor(token: string) {
|
||||
this.token = token;
|
||||
}
|
||||
const resourceGroup = process.env.RESOURCE_GROUP || "";
|
||||
const subscriptionId = process.env.SUBSCRIPTION_ID || "";
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner";
|
||||
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
||||
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
||||
|
||||
async signRequest(webResource: msRest.WebResourceLike): Promise<msRest.WebResourceLike> {
|
||||
webResource.headers.set("authorization", `bearer ${this.token}`);
|
||||
return webResource;
|
||||
}
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
// Azure SDK clients accept the credential as a parameter
|
||||
const credentials = new ClientSecretCredential(
|
||||
process.env.AZURE_TENANT_ID,
|
||||
process.env.AZURE_CLIENT_ID,
|
||||
process.env.AZURE_CLIENT_SECRET,
|
||||
{
|
||||
authorityHost: "https://localhost:1234",
|
||||
}
|
||||
);
|
||||
|
||||
const databaseAccount: DatabaseAccount = {
|
||||
id: databaseAccountGetResponse.id,
|
||||
name: databaseAccountGetResponse.name,
|
||||
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;
|
||||
};
|
||||
console.log("Resource Group:", resourceGroup);
|
||||
console.log("Subcription: ", subscriptionId);
|
||||
console.log("Account Name: ", accountName);
|
||||
|
||||
const initTestExplorer = async (): Promise<void> => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const portalRunnerDatabaseAccount = decodeURIComponent(
|
||||
urlSearchParams.get(TestExplorerParams.portalRunnerDatabaseAccount)
|
||||
);
|
||||
const portalRunnerDatabaseAccountKey = decodeURIComponent(
|
||||
urlSearchParams.get(TestExplorerParams.portalRunnerDatabaseAccountKey)
|
||||
);
|
||||
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 { token } = await credentials.getToken("https://management.core.windows.net/.default");
|
||||
updateUserContext({
|
||||
authorizationToken: `bearer ${token}`,
|
||||
});
|
||||
const databaseAccount = await get(subscriptionId, resourceGroup, accountName);
|
||||
const keys = await listKeys(subscriptionId, resourceGroup, accountName);
|
||||
|
||||
const initTestExplorerContent = {
|
||||
inputs: {
|
||||
databaseAccount: databaseAccount,
|
||||
subscriptionId: portalRunnerSubscripton,
|
||||
resourceGroup: portalRunnerResourceGroup,
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
authorizationToken: `Bearer ${token}`,
|
||||
features: {},
|
||||
hasWriteAccess: true,
|
||||
@ -88,7 +56,7 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
quotaId: "Internal_2014-09-01",
|
||||
addCollectionDefaultFlight: "2",
|
||||
isTryCosmosDBSubscription: false,
|
||||
masterKey: portalRunnerDatabaseAccountKey,
|
||||
masterKey: keys.primaryMasterKey,
|
||||
loadDatabaseAccountTimestamp: 1604663109836,
|
||||
dataExplorerVersion: "1.0.1",
|
||||
sharedThroughputMinimum: 400,
|
||||
@ -101,7 +69,7 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
// add UI test only when feature is not dependent on flights anymore
|
||||
flights: [],
|
||||
selfServeType,
|
||||
} as ViewModels.DataExplorerInputsFrame,
|
||||
} as DataExplorerInputsFrame,
|
||||
};
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
@ -127,16 +95,8 @@ const initTestExplorer = async (): Promise<void> => {
|
||||
iframe.name = "explorer";
|
||||
iframe.classList.add("iframe");
|
||||
iframe.title = "explorer";
|
||||
iframe.src = getIframeSrc(selfServeType);
|
||||
iframe.src = iframeSrc;
|
||||
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();
|
||||
|
@ -1,8 +0,0 @@
|
||||
export enum TestExplorerParams {
|
||||
portalRunnerDatabaseAccount = "portalRunnerDatabaseAccount",
|
||||
portalRunnerDatabaseAccountKey = "portalRunnerDatabaseAccountKey",
|
||||
portalRunnerSubscripton = "portalRunnerSubscripton",
|
||||
portalRunnerResourceGroup = "portalRunnerResourceGroup",
|
||||
selfServeType = "selfServeType",
|
||||
token = "token",
|
||||
}
|
@ -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();
|
||||
};
|
@ -30,8 +30,8 @@ export function generateDatabaseName(baseName = "db", length = 1): string {
|
||||
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
||||
}
|
||||
|
||||
export async function createDatabase(frame: Frame): Promise<string> {
|
||||
const dbId = generateDatabaseName();
|
||||
export async function createDatabase(frame: Frame): Promise<{ databaseId: string; collectionId: string }> {
|
||||
const databaseId = generateDatabaseName();
|
||||
const collectionId = generateUniqueName("col");
|
||||
const shardKey = "partitionKey";
|
||||
// create new collection
|
||||
@ -50,7 +50,7 @@ export async function createDatabase(frame: Frame): Promise<string> {
|
||||
await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
|
||||
const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
|
||||
await dbInput.press("Backspace");
|
||||
await dbInput.type(dbId);
|
||||
await dbInput.type(databaseId);
|
||||
|
||||
// type collection id
|
||||
await frame.waitFor('input[data-test="addCollection-collectionId"]');
|
||||
@ -67,7 +67,7 @@ export async function createDatabase(frame: Frame): Promise<string> {
|
||||
// click submit
|
||||
await frame.waitFor("#submitBtnAddCollection");
|
||||
await frame.click("#submitBtnAddCollection");
|
||||
return dbId;
|
||||
return { databaseId, collectionId };
|
||||
}
|
||||
|
||||
export async function onClickSaveButton(frame: Frame): Promise<void> {
|
||||
|
@ -1,7 +1,6 @@
|
||||
const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
|
||||
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
|
||||
const ms = require("ms");
|
||||
const { time } = require("console");
|
||||
|
||||
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
|
||||
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 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) {
|
||||
try {
|
||||
@ -29,7 +28,7 @@ async function main() {
|
||||
const mongoDatabases = await client.mongoDBResources.listMongoDBDatabases(resourceGroupName, account.name);
|
||||
for (const database of mongoDatabases) {
|
||||
const timestamp = Number(database.name.split("-")[1]);
|
||||
if (timestamp && timestamp < sixtyMinutesAgo) {
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.mongoDBResources.deleteMongoDBDatabase(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
} else {
|
||||
@ -40,7 +39,7 @@ async function main() {
|
||||
const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name);
|
||||
for (const database of sqlDatabases) {
|
||||
const timestamp = Number(database.name.split("-")[1]);
|
||||
if (timestamp && timestamp < sixtyMinutesAgo) {
|
||||
if (timestamp && timestamp < thirtyMinutesAgo) {
|
||||
await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name);
|
||||
console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
|
||||
} else {
|
||||
|
@ -1,3 +1,4 @@
|
||||
require("dotenv/config");
|
||||
const path = require("path");
|
||||
const MonacoWebpackPlugin = require("monaco-editor-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 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 = {
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, "css-loader"],
|
||||
@ -102,6 +113,11 @@ module.exports = function (env = {}, argv = {}) {
|
||||
|
||||
if (mode === "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" };
|
||||
}
|
||||
|
||||
@ -282,6 +298,12 @@ module.exports = function (env = {}, argv = {}) {
|
||||
secure: false,
|
||||
logLevel: "debug",
|
||||
},
|
||||
[`/${AZURE_TENANT_ID}`]: {
|
||||
target: "https://login.microsoftonline.com/",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: "debug",
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: "minimal",
|
||||
|
Loading…
Reference in New Issue
Block a user