mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-23 19:54:08 +00:00
Compare commits
42 Commits
package-up
...
hosted-msa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35213a77e2 | ||
|
|
d1ac8eb077 | ||
|
|
d9156c47d0 | ||
|
|
a844042580 | ||
|
|
1238d30f95 | ||
|
|
684cbfe4a0 | ||
|
|
5b6b4d3583 | ||
|
|
e05a78e96a | ||
|
|
33f7ae1e6d | ||
|
|
2089a8881d | ||
|
|
2e1665f093 | ||
|
|
f6d6222e5c | ||
|
|
2147b10361 | ||
|
|
09ac1d1552 | ||
|
|
b81cef2a03 | ||
|
|
731999c4e8 | ||
|
|
aba583abd8 | ||
|
|
2e10b96678 | ||
|
|
5652f29d03 | ||
|
|
15cb4a8fc4 | ||
|
|
bf30c3190a | ||
|
|
585f75bc91 | ||
|
|
7116f25ce4 | ||
|
|
5f5d9176af | ||
|
|
ac2d645fda | ||
|
|
12a44fdd42 | ||
|
|
13dbcb6453 | ||
|
|
cc63cdc1fd | ||
|
|
e41230e8c4 | ||
|
|
c3058ee5a9 | ||
|
|
b000631a0c | ||
|
|
e8f4c8f93c | ||
|
|
16bde97e47 | ||
|
|
6da43ee27b | ||
|
|
ebae484b8f | ||
|
|
dfb1b50621 | ||
|
|
f54e8eb692 | ||
|
|
ea39c1d092 | ||
|
|
c21f42159f | ||
|
|
31e4b49f11 | ||
|
|
40491ec9c5 | ||
|
|
e133df18dd |
@@ -14,7 +14,6 @@ src/Common/DataAccessUtilityBase.ts
|
|||||||
src/Common/DeleteFeedback.ts
|
src/Common/DeleteFeedback.ts
|
||||||
src/Common/DocumentClientUtilityBase.ts
|
src/Common/DocumentClientUtilityBase.ts
|
||||||
src/Common/EditableUtility.ts
|
src/Common/EditableUtility.ts
|
||||||
src/Common/EnvironmentUtility.ts
|
|
||||||
src/Common/HashMap.test.ts
|
src/Common/HashMap.test.ts
|
||||||
src/Common/HashMap.ts
|
src/Common/HashMap.ts
|
||||||
src/Common/HeadersUtility.test.ts
|
src/Common/HeadersUtility.test.ts
|
||||||
@@ -43,7 +42,6 @@ src/Contracts/ViewModels.ts
|
|||||||
src/Controls/Heatmap/Heatmap.test.ts
|
src/Controls/Heatmap/Heatmap.test.ts
|
||||||
src/Controls/Heatmap/Heatmap.ts
|
src/Controls/Heatmap/Heatmap.ts
|
||||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
src/Controls/Heatmap/HeatmapDatatypes.ts
|
||||||
src/Definitions/adal.d.ts
|
|
||||||
src/Definitions/datatables.d.ts
|
src/Definitions/datatables.d.ts
|
||||||
src/Definitions/gif.d.ts
|
src/Definitions/gif.d.ts
|
||||||
src/Definitions/globals.d.ts
|
src/Definitions/globals.d.ts
|
||||||
@@ -243,9 +241,6 @@ src/Platform/Hosted/Authorization.ts
|
|||||||
src/Platform/Hosted/DataAccessUtility.ts
|
src/Platform/Hosted/DataAccessUtility.ts
|
||||||
src/Platform/Hosted/ExplorerFactory.ts
|
src/Platform/Hosted/ExplorerFactory.ts
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.ts
|
|
||||||
src/Platform/Hosted/HostedUtils.test.ts
|
|
||||||
src/Platform/Hosted/HostedUtils.ts
|
|
||||||
src/Platform/Hosted/Main.ts
|
src/Platform/Hosted/Main.ts
|
||||||
src/Platform/Hosted/Maint.test.ts
|
src/Platform/Hosted/Maint.test.ts
|
||||||
src/Platform/Hosted/NotificationsClient.ts
|
src/Platform/Hosted/NotificationsClient.ts
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ module.exports = {
|
|||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
eqeqeq: "error",
|
eqeqeq: "error",
|
||||||
|
"react/display-name": "off",
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
@@ -101,6 +101,7 @@ jobs:
|
|||||||
PLATFORM: "Emulator"
|
PLATFORM: "Emulator"
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
@@ -159,13 +160,14 @@ 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"
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -189,7 +191,7 @@ jobs:
|
|||||||
nugetmpac:
|
nugetmpac:
|
||||||
name: Publish Nuget MPAC
|
name: Publish Nuget MPAC
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -211,3 +213,28 @@ jobs:
|
|||||||
name: packages
|
name: packages
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
path: "*.nupkg"
|
||||||
|
nugetie:
|
||||||
|
name: Publish Nuget IE
|
||||||
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
|
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
||||||
|
steps:
|
||||||
|
- uses: nuget/setup-nuget@v1
|
||||||
|
with:
|
||||||
|
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
||||||
|
- name: Download Dist Folder
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
- run: cp ./configs/prod.json config.json
|
||||||
|
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.IE/g' DataExplorer.nuspec
|
||||||
|
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
|
||||||
|
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
||||||
|
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
name: packages
|
||||||
|
with:
|
||||||
|
path: "*.nupkg"
|
||||||
|
|||||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
37
README.md
37
README.md
@@ -13,29 +13,18 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
|
|||||||
|
|
||||||
### Watch mode
|
### Watch mode
|
||||||
|
|
||||||
Run `npm run watch` to start the development server and automatically rebuild on changes
|
Run `npm start` to start the development server and automatically rebuild on changes
|
||||||
|
|
||||||
### Specifying Development Platform
|
### Hosted Development (https://cosmos.azure.com)
|
||||||
|
|
||||||
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options:
|
- Visit: `https://localhost:1234/hostedExplorer.html`
|
||||||
|
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
|
||||||
- Hosted
|
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
||||||
- Emulator
|
|
||||||
- Portal
|
|
||||||
|
|
||||||
`PLATFORM=Emulator npm run watch`
|
|
||||||
|
|
||||||
### Hosted Development
|
|
||||||
|
|
||||||
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
|
||||||
|
|
||||||
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
|
|
||||||
|
|
||||||
### Emulator Development
|
### Emulator Development
|
||||||
|
|
||||||
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
- Start the Cosmos Emulator
|
||||||
|
- Visit: https://localhost:1234/index.html
|
||||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
|
||||||
|
|
||||||
#### Setting up a Remote Emulator
|
#### Setting up a Remote Emulator
|
||||||
|
|
||||||
@@ -55,16 +44,8 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
|
|||||||
|
|
||||||
### Portal Development
|
### Portal Development
|
||||||
|
|
||||||
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment
|
- Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
|
||||||
You can however load a local running instance of data explorer in the production portal.
|
|
||||||
|
|
||||||
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
|
||||||
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
|
||||||
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
|
||||||
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
|
||||||
|
|
||||||
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
|
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
|||||||
7
canvas/README.md
Normal file
7
canvas/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Why?
|
||||||
|
|
||||||
|
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
|
||||||
|
|
||||||
|
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
|
||||||
|
|
||||||
|
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved
|
||||||
1
canvas/index.js
Normal file
1
canvas/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = {}
|
||||||
11
canvas/package.json
Normal file
11
canvas/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "canvas",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
1963
externals/adal.js
vendored
1963
externals/adal.js
vendored
File diff suppressed because it is too large
Load Diff
3770
less/documentDB.less
3770
less/documentDB.less
File diff suppressed because it is too large
Load Diff
286
package-lock.json
generated
286
package-lock.json
generated
@@ -275,6 +275,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@azure/msal-browser": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-I6n7EQnwsZXgKPOLlS5X48jhzUNUFwMVm180wDBA/pwEkUy8ei6zWiPMBfWaMSxz9uNx9WHaEhgAyhJy0ze3AQ==",
|
||||||
|
"requires": {
|
||||||
|
"@azure/msal-common": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@azure/msal-common": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-d1RNcJb+P1EGzMHtgbZoVlHLQWjlVfr504jywNk9YEfoq8Hw3BxJ0wepu+1w0hc64D8zG0wljcvHaIH1jTn2SA==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@azure/msal-react": {
|
||||||
|
"version": "1.0.0-alpha.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-1.0.0-alpha.1.tgz",
|
||||||
|
"integrity": "sha512-8BftMP1DyXf7/Fa7mxi14/fmHBdDGDUONmE8sm1T6w7ERJyY1RN7PZgdnUOcYcqj2xMnxfz9++8HsrzMrtMc0Q=="
|
||||||
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.10.4",
|
"version": "7.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||||
@@ -5393,11 +5414,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
|
||||||
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
|
||||||
},
|
},
|
||||||
"abbrev": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
|
||||||
},
|
|
||||||
"abort-controller": {
|
"abort-controller": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
@@ -5448,12 +5464,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
|
||||||
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
|
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
|
||||||
},
|
},
|
||||||
"adal-angular": {
|
|
||||||
"version": "1.0.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.15.tgz",
|
|
||||||
"integrity": "sha1-8qnvgvNYxEToMUKs5l0yJ6RBBDs=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"agent-base": {
|
"agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
@@ -5641,6 +5651,7 @@
|
|||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"delegates": "^1.0.0",
|
"delegates": "^1.0.0",
|
||||||
"readable-stream": "^2.0.6"
|
"readable-stream": "^2.0.6"
|
||||||
@@ -6359,7 +6370,6 @@
|
|||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"base64-js": "^1.3.1",
|
"base64-js": "^1.3.1",
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
@@ -6884,14 +6894,7 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"canvas": {
|
"canvas": {
|
||||||
"version": "2.6.1",
|
"version": "file:canvas"
|
||||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-S98rKsPcuhfTcYbtF53UIJhcbgIAK533d1kJKMwsMwAIFgfd58MOyxRud3kktlzWiEkFliaJtvyZCBtud/XVEA==",
|
|
||||||
"requires": {
|
|
||||||
"nan": "^2.14.0",
|
|
||||||
"node-pre-gyp": "^0.11.0",
|
|
||||||
"simple-get": "^3.0.3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"capture-exit": {
|
"capture-exit": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -7455,7 +7458,8 @@
|
|||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"constants-browserify": {
|
"constants-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -8436,6 +8440,7 @@
|
|||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"mimic-response": "^2.0.0"
|
"mimic-response": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -8461,7 +8466,8 @@
|
|||||||
"deep-extend": {
|
"deep-extend": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"deep-is": {
|
"deep-is": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
@@ -8653,7 +8659,8 @@
|
|||||||
"delegates": {
|
"delegates": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -8689,7 +8696,8 @@
|
|||||||
"detect-libc": {
|
"detect-libc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"detect-newline": {
|
"detect-newline": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -10675,14 +10683,6 @@
|
|||||||
"universalify": "^0.1.0"
|
"universalify": "^0.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs-minipass": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
|
||||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs-observable": {
|
"fs-observable": {
|
||||||
"version": "4.1.14",
|
"version": "4.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/fs-observable/-/fs-observable-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/fs-observable/-/fs-observable-4.1.14.tgz",
|
||||||
@@ -10824,6 +10824,7 @@
|
|||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"aproba": "^1.0.3",
|
"aproba": "^1.0.3",
|
||||||
"console-control-strings": "^1.0.0",
|
"console-control-strings": "^1.0.0",
|
||||||
@@ -10838,12 +10839,14 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -10852,6 +10855,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
@@ -10862,6 +10866,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^2.0.0"
|
"ansi-regex": "^2.0.0"
|
||||||
}
|
}
|
||||||
@@ -11351,7 +11356,8 @@
|
|||||||
"has-unicode": {
|
"has-unicode": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"has-value": {
|
"has-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -11833,14 +11839,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
|
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
|
||||||
},
|
},
|
||||||
"ignore-walk": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
|
||||||
"requires": {
|
|
||||||
"minimatch": "^3.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"image-size": {
|
"image-size": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
@@ -14691,6 +14689,14 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"nan": "2.14.1",
|
"nan": "2.14.1",
|
||||||
"prebuild-install": "5.3.3"
|
"prebuild-install": "5.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"nan": {
|
||||||
|
"version": "2.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||||
|
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"killable": {
|
"killable": {
|
||||||
@@ -15322,6 +15328,15 @@
|
|||||||
"tinyqueue": "^1.1.0"
|
"tinyqueue": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"match-sorter": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-SDRLNlWof9GnAUEyhKP0O5525MMGXUGt+ep4MrrqQ2StAh3zjvICVZseiwg7Zijn3GazpJDiwuRr/mFDHd92NQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"remove-accents": "0.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"matchdep": {
|
"matchdep": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
||||||
@@ -15537,7 +15552,8 @@
|
|||||||
"mimic-response": {
|
"mimic-response": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"min-document": {
|
"min-document": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
@@ -15594,15 +15610,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"minipass": {
|
|
||||||
"version": "2.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
|
||||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minipass-collect": {
|
"minipass-collect": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
|
||||||
@@ -15672,14 +15679,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minizlib": {
|
|
||||||
"version": "1.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
|
||||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mississippi": {
|
"mississippi": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||||
@@ -15816,9 +15815,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"msal": {
|
"msal": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/msal/-/msal-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/msal/-/msal-1.4.4.tgz",
|
||||||
"integrity": "sha512-C90MhgzcBuTSR2BOQ/LQryY1CZVESQLJDdmRDWSsaVde+zwZ2iXD0fWw7zeBd5TzfUCiJEXZVs4lFJ8d/IGbiQ==",
|
"integrity": "sha512-aOBD/L6jAsizDFzKxxvXxH0FEDjp6Inr3Ufi/Y2o7KCFKN+akoE2sLeszEb/0Y3VxHxK0F0ea7xQ/HHTomKivw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
},
|
||||||
@@ -15854,7 +15853,8 @@
|
|||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.14.2",
|
"version": "2.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
|
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
@@ -15904,26 +15904,6 @@
|
|||||||
"semver": "^5.4.1"
|
"semver": "^5.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"needle": {
|
|
||||||
"version": "2.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz",
|
|
||||||
"integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "^3.2.6",
|
|
||||||
"iconv-lite": "^0.4.4",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "3.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
|
||||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "^2.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"negotiator": {
|
"negotiator": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
@@ -16092,41 +16072,6 @@
|
|||||||
"which": "^1.3.0"
|
"which": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-pre-gyp": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==",
|
|
||||||
"requires": {
|
|
||||||
"detect-libc": "^1.0.2",
|
|
||||||
"mkdirp": "^0.5.1",
|
|
||||||
"needle": "^2.2.1",
|
|
||||||
"nopt": "^4.0.1",
|
|
||||||
"npm-packlist": "^1.1.6",
|
|
||||||
"npmlog": "^4.0.2",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"rimraf": "^2.6.1",
|
|
||||||
"semver": "^5.3.0",
|
|
||||||
"tar": "^4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"mkdirp": {
|
|
||||||
"version": "0.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
|
||||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rimraf": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
|
||||||
"requires": {
|
|
||||||
"glob": "^7.1.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "1.1.66",
|
"version": "1.1.66",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz",
|
||||||
@@ -16139,15 +16084,6 @@
|
|||||||
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=",
|
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"nopt": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
|
||||||
"requires": {
|
|
||||||
"abbrev": "1",
|
|
||||||
"osenv": "^0.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"normalize-package-data": {
|
"normalize-package-data": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||||
@@ -16172,29 +16108,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",
|
||||||
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
"integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="
|
||||||
},
|
},
|
||||||
"npm-bundled": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
|
||||||
"requires": {
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-normalize-package-bin": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
|
||||||
},
|
|
||||||
"npm-packlist": {
|
|
||||||
"version": "1.4.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
|
||||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
|
||||||
"requires": {
|
|
||||||
"ignore-walk": "^3.0.1",
|
|
||||||
"npm-bundled": "^1.0.1",
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-run-path": {
|
"npm-run-path": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
@@ -16207,6 +16120,7 @@
|
|||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"are-we-there-yet": "~1.1.2",
|
"are-we-there-yet": "~1.1.2",
|
||||||
"console-control-strings": "~1.1.0",
|
"console-control-strings": "~1.1.0",
|
||||||
@@ -16598,7 +16512,8 @@
|
|||||||
"os-homedir": {
|
"os-homedir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"os-locale": {
|
"os-locale": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
@@ -16617,20 +16532,6 @@
|
|||||||
"windows-release": "^3.1.0"
|
"windows-release": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"os-tmpdir": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
|
||||||
},
|
|
||||||
"osenv": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
|
||||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
|
||||||
"requires": {
|
|
||||||
"os-homedir": "^1.0.0",
|
|
||||||
"os-tmpdir": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-defer": {
|
"p-defer": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
|
||||||
@@ -17683,6 +17584,7 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"deep-extend": "^0.6.0",
|
"deep-extend": "^0.6.0",
|
||||||
"ini": "~1.3.0",
|
"ini": "~1.3.0",
|
||||||
@@ -17882,6 +17784,15 @@
|
|||||||
"warning": "^4.0.2"
|
"warning": "^4.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-query": {
|
||||||
|
"version": "3.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.5.5.tgz",
|
||||||
|
"integrity": "sha512-WYZcHcAs5K5lPGT6CI8fz3lU62S8IfZhvB1K4aZH27wg9T6CWei+y7IRyZwti9X18LX134O4olgEuNth9LEX+w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"match-sorter": "^6.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz",
|
||||||
@@ -18274,6 +18185,11 @@
|
|||||||
"superagent-proxy": "^2.0.0"
|
"superagent-proxy": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"remove-accents": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
|
||||||
|
"integrity": "sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U="
|
||||||
|
},
|
||||||
"remove-trailing-separator": {
|
"remove-trailing-separator": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||||
@@ -19109,12 +19025,14 @@
|
|||||||
"simple-concat": {
|
"simple-concat": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"simple-get": {
|
"simple-get": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
|
||||||
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
|
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"decompress-response": "^4.2.0",
|
"decompress-response": "^4.2.0",
|
||||||
"once": "^1.3.1",
|
"once": "^1.3.1",
|
||||||
@@ -20106,35 +20024,10 @@
|
|||||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tar": {
|
|
||||||
"version": "4.4.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
|
||||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
|
||||||
"requires": {
|
|
||||||
"chownr": "^1.1.1",
|
|
||||||
"fs-minipass": "^1.2.5",
|
|
||||||
"minipass": "^2.8.6",
|
|
||||||
"minizlib": "^1.2.1",
|
|
||||||
"mkdirp": "^0.5.0",
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"mkdirp": {
|
|
||||||
"version": "0.5.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
|
||||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "^1.2.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tar-fs": {
|
"tar-fs": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"chownr": "^1.1.1",
|
"chownr": "^1.1.1",
|
||||||
"mkdirp-classic": "^0.5.2",
|
"mkdirp-classic": "^0.5.2",
|
||||||
@@ -22186,6 +22079,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
||||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"string-width": "^1.0.2 || 2"
|
"string-width": "^1.0.2 || 2"
|
||||||
},
|
},
|
||||||
@@ -22193,12 +22087,14 @@
|
|||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-fullwidth-code-point": "^2.0.0",
|
"is-fullwidth-code-point": "^2.0.0",
|
||||||
"strip-ansi": "^4.0.0"
|
"strip-ansi": "^4.0.0"
|
||||||
@@ -22208,6 +22104,7 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-regex": "^3.0.0"
|
"ansi-regex": "^3.0.0"
|
||||||
}
|
}
|
||||||
@@ -22377,7 +22274,8 @@
|
|||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "13.3.2",
|
"version": "13.3.2",
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "3.9.0",
|
"@azure/cosmos": "3.9.0",
|
||||||
"@azure/identity": "1.1.0",
|
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
|
"@azure/identity": "1.1.0",
|
||||||
|
"@azure/msal-browser": "2.8.0",
|
||||||
|
"@azure/msal-react": "1.0.0-alpha.1",
|
||||||
"@jupyterlab/services": "6.0.0-rc.2",
|
"@jupyterlab/services": "6.0.0-rc.2",
|
||||||
"@jupyterlab/terminal": "3.0.0-rc.2",
|
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||||
"@microsoft/applicationinsights-web": "2.5.9",
|
"@microsoft/applicationinsights-web": "2.5.9",
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
"applicationinsights": "1.8.0",
|
"applicationinsights": "1.8.0",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "2.6.1",
|
"canvas": "file:./canvas",
|
||||||
"clean-webpack-plugin": "0.1.19",
|
"clean-webpack-plugin": "0.1.19",
|
||||||
"copy-webpack-plugin": "6.0.2",
|
"copy-webpack-plugin": "6.0.2",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
@@ -69,6 +71,7 @@
|
|||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.18.1",
|
"monaco-editor": "0.18.1",
|
||||||
|
"msal": "1.4.4",
|
||||||
"object.entries": "1.1.0",
|
"object.entries": "1.1.0",
|
||||||
"office-ui-fabric-react": "7.134.1",
|
"office-ui-fabric-react": "7.134.1",
|
||||||
"p-retry": "4.2.0",
|
"p-retry": "4.2.0",
|
||||||
@@ -83,6 +86,7 @@
|
|||||||
"react-dom": "16.9.0",
|
"react-dom": "16.9.0",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
|
"react-query": "3.5.5",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
@@ -128,7 +132,6 @@
|
|||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||||
"@typescript-eslint/parser": "4.0.1",
|
"@typescript-eslint/parser": "4.0.1",
|
||||||
"adal-angular": "1.0.15",
|
|
||||||
"axe-puppeteer": "1.1.0",
|
"axe-puppeteer": "1.1.0",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ export enum AuthType {
|
|||||||
AAD = "aad",
|
AAD = "aad",
|
||||||
EncryptedToken = "encryptedtoken",
|
EncryptedToken = "encryptedtoken",
|
||||||
MasterKey = "masterkey",
|
MasterKey = "masterkey",
|
||||||
ResourceToken = "resourcetoken"
|
ResourceToken = "resourcetoken",
|
||||||
|
ConnectionString = "connectionstring"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export class Features {
|
|||||||
public static readonly enableTtl = "enablettl";
|
public static readonly enableTtl = "enablettl";
|
||||||
public static readonly enableNotebooks = "enablenotebooks";
|
public static readonly enableNotebooks = "enablenotebooks";
|
||||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||||
public static readonly enableCodeOfConduct = "enablecodeofconduct";
|
|
||||||
public static readonly enableLinkInjection = "enablelinkinjection";
|
public static readonly enableLinkInjection = "enablelinkinjection";
|
||||||
public static readonly enableSpark = "enablespark";
|
public static readonly enableSpark = "enablespark";
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
public static readonly livyEndpoint = "livyendpoint";
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
import { ConflictDefinition, FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import Q from "q";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
|
||||||
import * as Constants from "./Constants";
|
|
||||||
import { client } from "./CosmosClient";
|
|
||||||
|
|
||||||
export function getCommonQueryOptions(options: FeedOptions): any {
|
|
||||||
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
|
||||||
options = options || {};
|
|
||||||
options.populateQueryMetrics = true;
|
|
||||||
options.enableScanInQuery = options.enableScanInQuery || true;
|
|
||||||
if (!options.partitionKey) {
|
|
||||||
options.forceQueryPlan = true;
|
|
||||||
}
|
|
||||||
options.maxItemCount =
|
|
||||||
options.maxItemCount ||
|
|
||||||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
|
||||||
Constants.Queries.itemsPerPage;
|
|
||||||
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryDocuments(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
|
||||||
options = getCommonQueryOptions(options);
|
|
||||||
const documentsIterator = client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(containerId)
|
|
||||||
.items.query(query, options);
|
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
|
||||||
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
|
||||||
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
|
||||||
|
|
||||||
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
|
||||||
if (!partitionKeyDefinition) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partitionKeyValue === undefined) {
|
|
||||||
return [{}];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [partitionKeyValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.replace(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: StoredProcedure,
|
|
||||||
partitionKeyValue: any,
|
|
||||||
params: any[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
|
||||||
const deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id())
|
|
||||||
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
|
||||||
.then(response =>
|
|
||||||
deferred.resolve({
|
|
||||||
result: response.resource,
|
|
||||||
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch(error => deferred.reject(error));
|
|
||||||
|
|
||||||
return deferred.promise.timeout(
|
|
||||||
Constants.ClientDefaults.requestTimeoutMs,
|
|
||||||
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.items.create(newDocument)
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.read()
|
|
||||||
.then(response => response.resource)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
const partitionKey = documentId.partitionKeyValue;
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), partitionKey)
|
|
||||||
.delete()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteConflict(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
conflictId: ConflictId,
|
|
||||||
options: any = {}
|
|
||||||
): Q.Promise<any> {
|
|
||||||
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
|
||||||
|
|
||||||
return Q(
|
|
||||||
client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.conflict(conflictId.id())
|
|
||||||
.delete(options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
const documentsIterator = client()
|
|
||||||
.database(databaseId)
|
|
||||||
.container(containerId)
|
|
||||||
.conflicts.query(query, options);
|
|
||||||
return Q(documentsIterator);
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import Q from "q";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
|
||||||
import * as Constants from "./Constants";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
|
||||||
export function queryDocuments(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
|
||||||
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEntityName() {
|
|
||||||
const defaultExperience =
|
|
||||||
window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
|
|
||||||
if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
|
|
||||||
return "document";
|
|
||||||
}
|
|
||||||
return "item";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeStoredProcedure(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
storedProcedure: StoredProcedure,
|
|
||||||
partitionKeyValue: any,
|
|
||||||
params: any[]
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
|
||||||
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
deferred.resolve(response);
|
|
||||||
logConsoleInfo(
|
|
||||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"ExecuteStoredProcedure",
|
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryDocumentsPage(
|
|
||||||
resourceName: string,
|
|
||||||
documentsIterator: MinimalQueryIterator,
|
|
||||||
firstItemIndex: number,
|
|
||||||
options: any
|
|
||||||
): Q.Promise<ViewModels.QueryResults> {
|
|
||||||
var deferred = Q.defer<ViewModels.QueryResults>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
|
||||||
Q(nextPage(documentsIterator, firstItemIndex))
|
|
||||||
.then(
|
|
||||||
(result: ViewModels.QueryResults) => {
|
|
||||||
const itemCount = (result.documents && result.documents.length) || 0;
|
|
||||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
|
||||||
deferred.resolve(result);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.readDocument(collection, documentId)
|
|
||||||
.then(
|
|
||||||
(document: any) => {
|
|
||||||
deferred.resolve(document);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
|
||||||
.then(
|
|
||||||
(updatedDocument: any) => {
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.resolve(updatedDocument);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
DataAccessUtilityBase.createDocument(collection, newDocument)
|
|
||||||
.then(
|
|
||||||
(savedDocument: any) => {
|
|
||||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
|
||||||
deferred.resolve(savedDocument);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
|
||||||
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteConflict(
|
|
||||||
collection: ViewModels.CollectionBase,
|
|
||||||
conflictId: ConflictId,
|
|
||||||
options?: any
|
|
||||||
): Q.Promise<any> {
|
|
||||||
var deferred = Q.defer<any>();
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
|
||||||
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
|
||||||
.then(
|
|
||||||
(response: any) => {
|
|
||||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
|
||||||
deferred.resolve(response);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
clearMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
10
src/Common/DocumentUtility.ts
Normal file
10
src/Common/DocumentUtility.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
|
export const getEntityName = (): string => {
|
||||||
|
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
||||||
|
return "document";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "item";
|
||||||
|
};
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export default class EnvironmentUtility {
|
export function normalizeArmEndpoint(uri: string): string {
|
||||||
public static normalizeArmEndpointUri(uri: string): string {
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
return `${uri}/`;
|
||||||
return `${uri}/`;
|
|
||||||
}
|
|
||||||
return uri;
|
|
||||||
}
|
}
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition
|
offerDefinition: mockOfferDefinition,
|
||||||
|
offerReplacePending: false
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
@@ -54,7 +55,8 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
autoscaleMaxThroughput: 5000,
|
autoscaleMaxThroughput: 5000,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition
|
offerDefinition: mockOfferDefinition,
|
||||||
|
offerReplacePending: false
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||||
import { OfferResponse } from "@azure/cosmos";
|
import { OfferResponse } from "@azure/cosmos";
|
||||||
|
import { HttpHeaders } from "./Constants";
|
||||||
|
|
||||||
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
||||||
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
|
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
|
||||||
@@ -18,7 +19,7 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
|||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
headers: offerResponse.headers
|
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +29,6 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
|||||||
manualThroughput: offerContent.offerThroughput,
|
manualThroughput: offerContent.offerThroughput,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
headers: offerResponse.headers
|
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ import * as _ from "underscore";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { QueryUtils } from "../Utils/QueryUtils";
|
import { QueryUtils } from "../Utils/QueryUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "./dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "./dataAccess/deleteDocument";
|
||||||
|
import { queryDocuments } from "./dataAccess/queryDocuments";
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -31,10 +33,7 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queriesCollection.rawDataModel);
|
return Promise.resolve(queriesCollection.rawDataModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
"Setting up account for saving queries"
|
|
||||||
);
|
|
||||||
return createCollection({
|
return createCollection({
|
||||||
collectionId: SavedQueries.CollectionName,
|
collectionId: SavedQueries.CollectionName,
|
||||||
createNewDatabase: true,
|
createNewDatabase: true,
|
||||||
@@ -45,10 +44,7 @@ export class QueriesClient {
|
|||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
|
||||||
ConsoleDataType.Info,
|
|
||||||
"Successfully set up account for saving queries"
|
|
||||||
);
|
|
||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -56,17 +52,14 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveQuery(query: DataModels.Query): Promise<void> {
|
public async saveQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +67,16 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Saving query ${query.queryName}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Saving query ${query.queryName}`
|
|
||||||
);
|
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
return createDocument(queriesCollection, query)
|
return createDocument(queriesCollection, query)
|
||||||
.then(
|
.then(
|
||||||
(savedQuery: DataModels.Query) => {
|
(savedQuery: DataModels.Query) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully saved query ${query.queryName}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully saved query ${query.queryName}`
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -103,74 +87,65 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getQueries(): Promise<DataModels.Query[]> {
|
public async getQueries(): Promise<DataModels.Query[]> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: any = { enableCrossPartitionQuery: true };
|
const options: any = { enableCrossPartitionQuery: true };
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
||||||
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
|
SavedQueries.DatabaseName,
|
||||||
|
SavedQueries.CollectionName,
|
||||||
|
this.fetchQueriesQuery(),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
||||||
|
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
||||||
|
return QueryUtils.queryAllPages(fetchQueries)
|
||||||
.then(
|
.then(
|
||||||
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
(results: ViewModels.QueryResults) => {
|
||||||
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
||||||
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
if (!document) {
|
||||||
return QueryUtils.queryAllPages(fetchQueries).then(
|
return undefined;
|
||||||
(results: ViewModels.QueryResults) => {
|
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
|
||||||
if (!document) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const { id, resourceId, query, queryName } = document;
|
|
||||||
const parsedQuery: DataModels.Query = {
|
|
||||||
resourceId: resourceId,
|
|
||||||
queryName: queryName,
|
|
||||||
query: query,
|
|
||||||
id: id
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
this.validateQuery(parsedQuery);
|
|
||||||
return parsedQuery;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries");
|
|
||||||
return Promise.resolve(queries);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
);
|
const { id, resourceId, query, queryName } = document;
|
||||||
|
const parsedQuery: DataModels.Query = {
|
||||||
|
resourceId: resourceId,
|
||||||
|
queryName: queryName,
|
||||||
|
query: query,
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
this.validateQuery(parsedQuery);
|
||||||
|
return parsedQuery;
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
||||||
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
// should never get into this state but we handle this regardless
|
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${errorMessage}`
|
|
||||||
);
|
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,16 +153,10 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleError(`Failed to delete query ${query.queryName}: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting query ${query.queryName}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Deleting query ${query.queryName}`
|
|
||||||
);
|
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
const documentId = new DocumentId(
|
const documentId = new DocumentId(
|
||||||
{
|
{
|
||||||
@@ -201,10 +170,7 @@ export class QueriesClient {
|
|||||||
return deleteDocument(queriesCollection, documentId)
|
return deleteDocument(queriesCollection, documentId)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted query ${query.queryName}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully deleted query ${query.queryName}`
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -212,7 +178,7 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => clearMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getResourceId(): string {
|
public getResourceId(): string {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
jest.mock("../../Utils/arm/request");
|
jest.mock("../../Utils/arm/request");
|
||||||
jest.mock("../CosmosClient");
|
jest.mock("../CosmosClient");
|
||||||
jest.mock("../DataAccessUtilityBase");
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|||||||
25
src/Common/dataAccess/createDocument.ts
Normal file
25
src/Common/dataAccess/createDocument.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
|
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.items.create(newDocument);
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
36
src/Common/dataAccess/deleteConflict.ts
Normal file
36
src/Common/dataAccess/deleteConflict.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import ConflictId from "../../Explorer/Tree/ConflictId";
|
||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { RequestOptions } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
|
export const deleteConflict = async (collection: CollectionBase, conflictId: ConflictId): Promise<void> => {
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
partitionKey: getPartitionKeyHeaderForConflict(conflictId)
|
||||||
|
};
|
||||||
|
|
||||||
|
await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.conflict(conflictId.id())
|
||||||
|
.delete(options as RequestOptions);
|
||||||
|
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPartitionKeyHeaderForConflict = (conflictId: ConflictId): unknown => {
|
||||||
|
if (!conflictId.partitionKey) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflictId.partitionKeyValue === undefined ? [{}] : [conflictId.partitionKeyValue];
|
||||||
|
};
|
||||||
25
src/Common/dataAccess/deleteDocument.ts
Normal file
25
src/Common/dataAccess/deleteDocument.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
||||||
|
const entityName: string = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.delete();
|
||||||
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
48
src/Common/dataAccess/executeStoredProcedure.ts
Normal file
48
src/Common/dataAccess/executeStoredProcedure.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Collection } from "../../Contracts/ViewModels";
|
||||||
|
import { ClientDefaults, HttpHeaders } from "../Constants";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
|
||||||
|
|
||||||
|
export interface ExecuteSprocResult {
|
||||||
|
result: StoredProcedure;
|
||||||
|
scriptLogs: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const executeStoredProcedure = async (
|
||||||
|
collection: Collection,
|
||||||
|
storedProcedure: StoredProcedure,
|
||||||
|
partitionKeyValue: string,
|
||||||
|
params: string[]
|
||||||
|
): Promise<ExecuteSprocResult> => {
|
||||||
|
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
throw Error(`Request timed out while executing stored procedure ${storedProcedure.id()}`);
|
||||||
|
}, ClientDefaults.requestTimeoutMs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id())
|
||||||
|
.execute(partitionKeyValue, params, { enableScriptLogging: true });
|
||||||
|
clearTimeout(timeout);
|
||||||
|
logConsoleInfo(
|
||||||
|
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
result: response.resource,
|
||||||
|
scriptLogs: response.headers[HttpHeaders.scriptLogResults] as string
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
handleError(
|
||||||
|
error,
|
||||||
|
"ExecuteStoredProcedure",
|
||||||
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
import { armRequest } from "../../Utils/arm/request";
|
import { armRequest } from "../../Utils/arm/request";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
@@ -40,6 +41,10 @@ interface MetricsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
|
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const resourceGroup = userContext.resourceGroup;
|
const resourceGroup = userContext.resourceGroup;
|
||||||
const accountName = userContext.databaseAccount.name;
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|||||||
14
src/Common/dataAccess/queryConflicts.ts
Normal file
14
src/Common/dataAccess/queryConflicts.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
|
||||||
|
export const queryConflicts = (
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: FeedOptions
|
||||||
|
): QueryIterator<ConflictDefinition & Resource> => {
|
||||||
|
return client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.conflicts.query(query, options);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getCommonQueryOptions } from "./DataAccessUtilityBase";
|
import { getCommonQueryOptions } from "./queryDocuments";
|
||||||
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
|
||||||
describe("getCommonQueryOptions", () => {
|
describe("getCommonQueryOptions", () => {
|
||||||
it("builds the correct default options objects", () => {
|
it("builds the correct default options objects", () => {
|
||||||
34
src/Common/dataAccess/queryDocuments.ts
Normal file
34
src/Common/dataAccess/queryDocuments.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Queries } from "../Constants";
|
||||||
|
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
|
||||||
|
export const queryDocuments = (
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: FeedOptions
|
||||||
|
): QueryIterator<ItemDefinition & Resource> => {
|
||||||
|
options = getCommonQueryOptions(options);
|
||||||
|
return client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(containerId)
|
||||||
|
.items.query(query, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
|
||||||
|
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
||||||
|
options = options || {};
|
||||||
|
options.populateQueryMetrics = true;
|
||||||
|
options.enableScanInQuery = options.enableScanInQuery || true;
|
||||||
|
if (!options.partitionKey) {
|
||||||
|
options.forceQueryPlan = true;
|
||||||
|
}
|
||||||
|
options.maxItemCount =
|
||||||
|
options.maxItemCount ||
|
||||||
|
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
||||||
|
Queries.itemsPerPage;
|
||||||
|
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
26
src/Common/dataAccess/queryDocumentsPage.ts
Normal file
26
src/Common/dataAccess/queryDocumentsPage.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { QueryResults } from "../../Contracts/ViewModels";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
|
||||||
|
export const queryDocumentsPage = async (
|
||||||
|
resourceName: string,
|
||||||
|
documentsIterator: MinimalQueryIterator,
|
||||||
|
firstItemIndex: number
|
||||||
|
): Promise<QueryResults> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex);
|
||||||
|
const itemCount = (result.documents && result.documents.length) || 0;
|
||||||
|
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -105,7 +105,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput
|
minimumThroughput,
|
||||||
|
offerReplacePending: resource.offerReplacePending === "true"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +114,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput
|
minimumThroughput,
|
||||||
|
offerReplacePending: resource.offerReplacePending === "true"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput
|
minimumThroughput,
|
||||||
|
offerReplacePending: resource.offerReplacePending === "true"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +86,8 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
id: offerId,
|
id: offerId,
|
||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput
|
minimumThroughput,
|
||||||
|
offerReplacePending: resource.offerReplacePending === "true"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
src/Common/dataAccess/readDocument.ts
Normal file
27
src/Common/dataAccess/readDocument.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Item } from "@azure/cosmos";
|
||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.read();
|
||||||
|
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
32
src/Common/dataAccess/updateDocument.ts
Normal file
32
src/Common/dataAccess/updateDocument.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { Item } from "@azure/cosmos";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { getEntityName } from "../DocumentUtility";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import DocumentId from "../../Explorer/Tree/DocumentId";
|
||||||
|
|
||||||
|
export const updateDocument = async (
|
||||||
|
collection: CollectionBase,
|
||||||
|
documentId: DocumentId,
|
||||||
|
newDocument: Item
|
||||||
|
): Promise<Item> => {
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), documentId.partitionKeyValue)
|
||||||
|
.replace(newDocument);
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
|
return response?.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -214,7 +214,7 @@ export interface Offer {
|
|||||||
manualThroughput: number;
|
manualThroughput: number;
|
||||||
minimumThroughput: number;
|
minimumThroughput: number;
|
||||||
offerDefinition?: SDKOfferDefinition;
|
offerDefinition?: SDKOfferDefinition;
|
||||||
headers?: any;
|
offerReplacePending: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDKOfferDefinition extends Resource {
|
export interface SDKOfferDefinition extends Resource {
|
||||||
@@ -587,11 +587,3 @@ export interface MemoryUsageInfo {
|
|||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface resourceTokenConnectionStringProperties {
|
|
||||||
accountEndpoint: string;
|
|
||||||
collectionId: string;
|
|
||||||
databaseId: string;
|
|
||||||
partitionKey?: string;
|
|
||||||
resourceToken: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ export enum CollectionTabKind {
|
|||||||
Gallery = 17,
|
Gallery = 17,
|
||||||
NotebookViewer = 18,
|
NotebookViewer = 18,
|
||||||
Schema = 19,
|
Schema = 19,
|
||||||
SettingsV2 = 19
|
SettingsV2 = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
|
|||||||
383
src/Definitions/adal.d.ts
vendored
383
src/Definitions/adal.d.ts
vendored
@@ -1,383 +0,0 @@
|
|||||||
// Type definitions for adal-angular 1.0.1.1
|
|
||||||
// Project: https://github.com/AzureAD/azure-activedirectory-library-for-js#readme
|
|
||||||
// Definitions by: Daniel Perez Alvarez <https://github.com/unindented>
|
|
||||||
// Anthony Ciccarello <https://github.com/aciccarello>
|
|
||||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
||||||
|
|
||||||
//This is a customized version of adal on top of version 1.0.1 which does not support multi tenant
|
|
||||||
// Customized version add tenantId to stored tokens so when tenant change, adal will refetch instead of read from sessionStorage
|
|
||||||
|
|
||||||
// In module contexts the class constructor function is the exported object
|
|
||||||
// export = AuthenticationContext;
|
|
||||||
|
|
||||||
// This class is defined globally in not in a module context
|
|
||||||
declare class AuthenticationContext {
|
|
||||||
instance: string;
|
|
||||||
config: AuthenticationContext.Options;
|
|
||||||
callback: AuthenticationContext.TokenCallback;
|
|
||||||
popUp: boolean;
|
|
||||||
isAngular: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for request type
|
|
||||||
*/
|
|
||||||
REQUEST_TYPE: AuthenticationContext.RequestType;
|
|
||||||
RESPONSE_TYPE: AuthenticationContext.ResponseType;
|
|
||||||
CONSTANTS: AuthenticationContext.Constants;
|
|
||||||
|
|
||||||
constructor(options: AuthenticationContext.Options);
|
|
||||||
/**
|
|
||||||
* Initiates the login process by redirecting the user to Azure AD authorization endpoint.
|
|
||||||
*/
|
|
||||||
login(): void;
|
|
||||||
/**
|
|
||||||
* Returns whether a login is in progress.
|
|
||||||
*/
|
|
||||||
loginInProgress(): boolean;
|
|
||||||
/**
|
|
||||||
* Gets token for the specified resource from the cache.
|
|
||||||
* @param resource A URI that identifies the resource for which the token is requested.
|
|
||||||
* @param tenantId tenant Id.
|
|
||||||
*/
|
|
||||||
getCachedToken(resource: string, tenantId: string): string;
|
|
||||||
/**
|
|
||||||
* If user object exists, returns it. Else creates a new user object by decoding `id_token` from the cache.
|
|
||||||
*/
|
|
||||||
getCachedUser(): AuthenticationContext.UserInfo;
|
|
||||||
/**
|
|
||||||
* Adds the passed callback to the array of callbacks for the specified resource.
|
|
||||||
* @param resource A URI that identifies the resource for which the token is requested.
|
|
||||||
* @param expectedState A unique identifier (guid).
|
|
||||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
|
||||||
*/
|
|
||||||
registerCallback(
|
|
||||||
expectedState: string,
|
|
||||||
resource: string,
|
|
||||||
callback: AuthenticationContext.TokenCallback,
|
|
||||||
tenantId: string
|
|
||||||
): void;
|
|
||||||
/**
|
|
||||||
* Acquires token from the cache if it is not expired. Otherwise sends request to AAD to obtain a new token.
|
|
||||||
* @param resource Resource URI identifying the target resource.
|
|
||||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
|
||||||
*/
|
|
||||||
acquireToken(resource: string, tenantId: string, callback: AuthenticationContext.TokenCallback): void;
|
|
||||||
/**
|
|
||||||
* Acquires token (interactive flow using a popup window) by sending request to AAD to obtain a new token.
|
|
||||||
* @param resource Resource URI identifying the target resource.
|
|
||||||
* @param extraQueryParameters Query parameters to add to the authentication request.
|
|
||||||
* @param claims Claims to add to the authentication request.
|
|
||||||
* @param callback The callback provided by the caller. It will be called with token or error.
|
|
||||||
*/
|
|
||||||
acquireTokenPopup(
|
|
||||||
resource: string,
|
|
||||||
tenantId: string,
|
|
||||||
extraQueryParameters: string | null | undefined,
|
|
||||||
claims: string | null | undefined,
|
|
||||||
callback: AuthenticationContext.TokenCallback
|
|
||||||
): void;
|
|
||||||
/**
|
|
||||||
* Acquires token (interactive flow using a redirect) by sending request to AAD to obtain a new token. In this case the callback passed in the authentication request constructor will be called.
|
|
||||||
* @param resource Resource URI identifying the target resource.
|
|
||||||
* @param extraQueryParameters Query parameters to add to the authentication request.
|
|
||||||
* @param claims Claims to add to the authentication request.
|
|
||||||
*/
|
|
||||||
acquireTokenRedirect(
|
|
||||||
resource: string,
|
|
||||||
tenantId: string,
|
|
||||||
extraQueryParameters?: string | null,
|
|
||||||
claims?: string | null
|
|
||||||
): void;
|
|
||||||
/**
|
|
||||||
* Redirects the browser to Azure AD authorization endpoint.
|
|
||||||
* @param urlNavigate URL of the authorization endpoint.
|
|
||||||
*/
|
|
||||||
promptUser(urlNavigate: string): void;
|
|
||||||
/**
|
|
||||||
* Clears cache items.
|
|
||||||
*/
|
|
||||||
clearCache(): void;
|
|
||||||
/**
|
|
||||||
* Clears cache items for a given resource.
|
|
||||||
* @param resource Resource URI identifying the target resource.
|
|
||||||
*/
|
|
||||||
clearCacheForResource(resource: string): void;
|
|
||||||
/**
|
|
||||||
* Redirects user to logout endpoint. After logout, it will redirect to `postLogoutRedirectUri` if added as a property on the config object.
|
|
||||||
*/
|
|
||||||
logOut(): void;
|
|
||||||
/**
|
|
||||||
* Calls the passed in callback with the user object or error message related to the user.
|
|
||||||
* @param callback The callback provided by the caller. It will be called with user or error.
|
|
||||||
*/
|
|
||||||
getUser(callback: AuthenticationContext.UserCallback): void;
|
|
||||||
/**
|
|
||||||
* Checks if the URL fragment contains access token, id token or error description.
|
|
||||||
* @param hash Hash passed from redirect page.
|
|
||||||
*/
|
|
||||||
isCallback(hash: string): boolean;
|
|
||||||
/**
|
|
||||||
* Gets login error.
|
|
||||||
*/
|
|
||||||
getLoginError(): string;
|
|
||||||
/**
|
|
||||||
* Creates a request info object from the URL fragment and returns it.
|
|
||||||
*/
|
|
||||||
getRequestInfo(hash: string): AuthenticationContext.RequestInfo;
|
|
||||||
/**
|
|
||||||
* Saves token or error received in the response from AAD in the cache. In case of `id_token`, it also creates the user object.
|
|
||||||
*/
|
|
||||||
saveTokenFromHash(requestInfo: AuthenticationContext.RequestInfo): void;
|
|
||||||
/**
|
|
||||||
* Gets resource for given endpoint if mapping is provided with config.
|
|
||||||
* @param endpoint Resource URI identifying the target resource.
|
|
||||||
*/
|
|
||||||
getResourceForEndpoint(resource: string): string;
|
|
||||||
/**
|
|
||||||
* This method must be called for processing the response received from AAD. It extracts the hash, processes the token or error, saves it in the cache and calls the callbacks with the result.
|
|
||||||
* @param hash Hash fragment of URL. Defaults to `window.location.hash`.
|
|
||||||
*/
|
|
||||||
handleWindowCallback(hash?: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the logging Level, constructs the log message and logs it. Users need to implement/override this method to turn on logging.
|
|
||||||
* @param level Level can be set 0, 1, 2 and 3 which turns on 'error', 'warning', 'info' or 'verbose' level logging respectively.
|
|
||||||
* @param message Message to log.
|
|
||||||
* @param error Error to log.
|
|
||||||
*/
|
|
||||||
log(level: AuthenticationContext.LoggingLevel, message: string, error: any): void;
|
|
||||||
/**
|
|
||||||
* Logs messages when logging level is set to 0.
|
|
||||||
* @param message Message to log.
|
|
||||||
* @param error Error to log.
|
|
||||||
*/
|
|
||||||
error(message: string, error: any): void;
|
|
||||||
/**
|
|
||||||
* Logs messages when logging level is set to 1.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
warn(message: string): void;
|
|
||||||
/**
|
|
||||||
* Logs messages when logging level is set to 2.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
info(message: string): void;
|
|
||||||
/**
|
|
||||||
* Logs messages when logging level is set to 3.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
verbose(message: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs Pii messages when Logging Level is set to 0 and window.piiLoggingEnabled is set to true.
|
|
||||||
* @param message Message to log.
|
|
||||||
* @param error Error to log.
|
|
||||||
*/
|
|
||||||
errorPii(message: string, error: any): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs Pii messages when Logging Level is set to 1 and window.piiLoggingEnabled is set to true.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
warnPii(message: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs messages when Logging Level is set to 2 and window.piiLoggingEnabled is set to true.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
infoPii(message: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs messages when Logging Level is set to 3 and window.piiLoggingEnabled is set to true.
|
|
||||||
* @param message Message to log.
|
|
||||||
*/
|
|
||||||
verbosePii(message: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare namespace AuthenticationContext {
|
|
||||||
function inject(config: Options): AuthenticationContext;
|
|
||||||
|
|
||||||
type LoggingLevel = 0 | 1 | 2 | 3;
|
|
||||||
|
|
||||||
type RequestType = "LOGIN" | "RENEW_TOKEN" | "UNKNOWN";
|
|
||||||
|
|
||||||
type ResponseType = "id_token token" | "token";
|
|
||||||
|
|
||||||
interface RequestInfo {
|
|
||||||
/**
|
|
||||||
* Object comprising of fields such as id_token/error, session_state, state, e.t.c.
|
|
||||||
*/
|
|
||||||
parameters: any;
|
|
||||||
/**
|
|
||||||
* Request type.
|
|
||||||
*/
|
|
||||||
requestType: RequestType;
|
|
||||||
/**
|
|
||||||
* Whether state is valid.
|
|
||||||
*/
|
|
||||||
stateMatch: boolean;
|
|
||||||
/**
|
|
||||||
* Unique guid used to match the response with the request.
|
|
||||||
*/
|
|
||||||
stateResponse: string;
|
|
||||||
/**
|
|
||||||
* Whether `requestType` contains `id_token`, `access_token` or error.
|
|
||||||
*/
|
|
||||||
valid: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserInfo {
|
|
||||||
/**
|
|
||||||
* Username assigned from UPN or email.
|
|
||||||
*/
|
|
||||||
userName: string;
|
|
||||||
/**
|
|
||||||
* Properties parsed from `id_token`.
|
|
||||||
*/
|
|
||||||
profile: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenCallback = (errorDesc: string | null, token: string | null, error: any) => void;
|
|
||||||
|
|
||||||
type UserCallback = (errorDesc: string | null, user: UserInfo | null) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration options for Authentication Context
|
|
||||||
*/
|
|
||||||
interface Options {
|
|
||||||
/**
|
|
||||||
* Client ID assigned to your app by Azure Active Directory.
|
|
||||||
*/
|
|
||||||
clientId: string;
|
|
||||||
/**
|
|
||||||
* Endpoint at which you expect to receive tokens.Defaults to `window.location.href`.
|
|
||||||
*/
|
|
||||||
redirectUri?: string;
|
|
||||||
/**
|
|
||||||
* Azure Active Directory instance. Defaults to `https://login.microsoftonline.com/`.
|
|
||||||
*/
|
|
||||||
instance?: string;
|
|
||||||
/**
|
|
||||||
* Your target tenant. Defaults to `common`.
|
|
||||||
*/
|
|
||||||
tenant?: string;
|
|
||||||
/**
|
|
||||||
* Query parameters to add to the authentication request.
|
|
||||||
*/
|
|
||||||
extraQueryParameter?: string;
|
|
||||||
/**
|
|
||||||
* Unique identifier used to map the request with the response. Defaults to RFC4122 version 4 guid (128 bits).
|
|
||||||
*/
|
|
||||||
correlationId?: string;
|
|
||||||
/**
|
|
||||||
* User defined function of handling the navigation to Azure AD authorization endpoint in case of login.
|
|
||||||
*/
|
|
||||||
displayCall?: (url: string) => void;
|
|
||||||
/**
|
|
||||||
* Set this to true to enable login in a popup winodow instead of a full redirect. Defaults to `false`.
|
|
||||||
*/
|
|
||||||
popUp?: boolean;
|
|
||||||
/**
|
|
||||||
* Set this to the resource to request on login. Defaults to `clientId`.
|
|
||||||
*/
|
|
||||||
loginResource?: string;
|
|
||||||
/**
|
|
||||||
* Set this to redirect the user to a custom login page.
|
|
||||||
*/
|
|
||||||
localLoginUrl?: string;
|
|
||||||
/**
|
|
||||||
* Redirects to start page after login. Defaults to `true`.
|
|
||||||
*/
|
|
||||||
navigateToLoginRequestUrl?: boolean;
|
|
||||||
/**
|
|
||||||
* Set this to redirect the user to a custom logout page.
|
|
||||||
*/
|
|
||||||
logOutUri?: string;
|
|
||||||
/**
|
|
||||||
* Redirects the user to postLogoutRedirectUri after logout. Defaults to `redirectUri`.
|
|
||||||
*/
|
|
||||||
postLogoutRedirectUri?: string;
|
|
||||||
/**
|
|
||||||
* Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to `sessionStorage`.
|
|
||||||
*/
|
|
||||||
cacheLocation?: "localStorage" | "sessionStorage";
|
|
||||||
/**
|
|
||||||
* Array of keywords or URIs. Adal will attach a token to outgoing requests that have these keywords or URIs.
|
|
||||||
*/
|
|
||||||
endpoints?: { [resource: string]: string };
|
|
||||||
/**
|
|
||||||
* Array of keywords or URIs. Adal will not attach a token to outgoing requests that have these keywords or URIs.
|
|
||||||
*/
|
|
||||||
anonymousEndpoints?: string[];
|
|
||||||
/**
|
|
||||||
* If the cached token is about to be expired in the expireOffsetSeconds (in seconds), Adal will renew the token instead of using the cached token. Defaults to 300 seconds.
|
|
||||||
*/
|
|
||||||
expireOffsetSeconds?: number;
|
|
||||||
/**
|
|
||||||
* The number of milliseconds of inactivity before a token renewal response from AAD should be considered timed out. Defaults to 6 seconds.
|
|
||||||
*/
|
|
||||||
loadFrameTimeout?: number;
|
|
||||||
/**
|
|
||||||
* Callback to be invoked when a token is acquired.
|
|
||||||
*/
|
|
||||||
callback?: TokenCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoggingConfig {
|
|
||||||
level: LoggingLevel;
|
|
||||||
log: (message: string) => void;
|
|
||||||
piiLoggingEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for storage constants
|
|
||||||
*/
|
|
||||||
interface Constants {
|
|
||||||
ACCESS_TOKEN: "access_token";
|
|
||||||
EXPIRES_IN: "expires_in";
|
|
||||||
ID_TOKEN: "id_token";
|
|
||||||
ERROR_DESCRIPTION: "error_description";
|
|
||||||
SESSION_STATE: "session_state";
|
|
||||||
STORAGE: {
|
|
||||||
TOKEN_KEYS: "adal.token.keys";
|
|
||||||
ACCESS_TOKEN_KEY: "adal.access.token.key";
|
|
||||||
EXPIRATION_KEY: "adal.expiration.key";
|
|
||||||
STATE_LOGIN: "adal.state.login";
|
|
||||||
STATE_RENEW: "adal.state.renew";
|
|
||||||
NONCE_IDTOKEN: "adal.nonce.idtoken";
|
|
||||||
SESSION_STATE: "adal.session.state";
|
|
||||||
USERNAME: "adal.username";
|
|
||||||
IDTOKEN: "adal.idtoken";
|
|
||||||
ERROR: "adal.error";
|
|
||||||
ERROR_DESCRIPTION: "adal.error.description";
|
|
||||||
LOGIN_REQUEST: "adal.login.request";
|
|
||||||
LOGIN_ERROR: "adal.login.error";
|
|
||||||
RENEW_STATUS: "adal.token.renew.status";
|
|
||||||
};
|
|
||||||
RESOURCE_DELIMETER: "|";
|
|
||||||
LOADFRAME_TIMEOUT: "6000";
|
|
||||||
TOKEN_RENEW_STATUS_CANCELED: "Canceled";
|
|
||||||
TOKEN_RENEW_STATUS_COMPLETED: "Completed";
|
|
||||||
TOKEN_RENEW_STATUS_IN_PROGRESS: "In Progress";
|
|
||||||
LOGGING_LEVEL: {
|
|
||||||
ERROR: 0;
|
|
||||||
WARN: 1;
|
|
||||||
INFO: 2;
|
|
||||||
VERBOSE: 3;
|
|
||||||
};
|
|
||||||
LEVEL_STRING_MAP: {
|
|
||||||
0: "ERROR:";
|
|
||||||
1: "WARNING:";
|
|
||||||
2: "INFO:";
|
|
||||||
3: "VERBOSE:";
|
|
||||||
};
|
|
||||||
POPUP_WIDTH: 483;
|
|
||||||
POPUP_HEIGHT: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// declare global {
|
|
||||||
// interface Window {
|
|
||||||
// Logging: AuthenticationContext.LoggingConfig;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow, mount } from "enzyme";
|
|
||||||
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
|
||||||
import { AuthType } from "../../../AuthType";
|
|
||||||
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
|
||||||
import { AccountKind } from "../../../Common/Constants";
|
|
||||||
|
|
||||||
const createBlankProps = (): AccountSwitchComponentProps => {
|
|
||||||
return {
|
|
||||||
authType: null,
|
|
||||||
displayText: "",
|
|
||||||
accounts: [],
|
|
||||||
selectedAccountName: null,
|
|
||||||
isLoadingAccounts: false,
|
|
||||||
onAccountChange: jest.fn(),
|
|
||||||
subscriptions: [],
|
|
||||||
selectedSubscriptionId: null,
|
|
||||||
isLoadingSubscriptions: false,
|
|
||||||
onSubscriptionChange: jest.fn()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createBlankAccount = (): DatabaseAccount => {
|
|
||||||
return {
|
|
||||||
id: "",
|
|
||||||
kind: AccountKind.Default,
|
|
||||||
name: "",
|
|
||||||
properties: null,
|
|
||||||
location: "",
|
|
||||||
tags: null,
|
|
||||||
type: ""
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createBlankSubscription = (): Subscription => {
|
|
||||||
return {
|
|
||||||
subscriptionId: "",
|
|
||||||
displayName: "",
|
|
||||||
authorizationSource: "",
|
|
||||||
state: "",
|
|
||||||
subscriptionPolicies: null,
|
|
||||||
tenantId: "",
|
|
||||||
uniqueDisplayName: ""
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createFullProps = (): AccountSwitchComponentProps => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
props.authType = AuthType.AAD;
|
|
||||||
const account1 = createBlankAccount();
|
|
||||||
account1.name = "account1";
|
|
||||||
const account2 = createBlankAccount();
|
|
||||||
account2.name = "account2";
|
|
||||||
const account3 = createBlankAccount();
|
|
||||||
account3.name = "superlongaccountnamestringtest";
|
|
||||||
props.accounts = [account1, account2, account3];
|
|
||||||
props.selectedAccountName = "account2";
|
|
||||||
|
|
||||||
const sub1 = createBlankSubscription();
|
|
||||||
sub1.displayName = "sub1";
|
|
||||||
sub1.subscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
|
||||||
const sub2 = createBlankSubscription();
|
|
||||||
sub2.displayName = "subsubsubsubsubsubsub2";
|
|
||||||
sub2.subscriptionId = "b20b3e93-0185-4326-8a9c-d44bac276b6b";
|
|
||||||
props.subscriptions = [sub1, sub2];
|
|
||||||
props.selectedSubscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
|
||||||
|
|
||||||
return props;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("test render", () => {
|
|
||||||
it("renders no auth type -> handle error in code", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
|
|
||||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Encrypted Token
|
|
||||||
it("renders auth security token, with selected account name", () => {
|
|
||||||
const props = createBlankProps();
|
|
||||||
props.authType = AuthType.EncryptedToken;
|
|
||||||
props.selectedAccountName = "testaccount";
|
|
||||||
|
|
||||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
// AAD
|
|
||||||
it("renders auth aad, with all information", () => {
|
|
||||||
const props = createFullProps();
|
|
||||||
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders auth aad all dropdown menus", () => {
|
|
||||||
const props = createFullProps();
|
|
||||||
const wrapper = mount(<AccountSwitchComponent {...props} />);
|
|
||||||
|
|
||||||
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
|
||||||
wrapper.find("button.accountSwitchButton").simulate("click");
|
|
||||||
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(true);
|
|
||||||
|
|
||||||
expect(wrapper.exists("div.accountSwitchSubscriptionDropdown")).toBe(true);
|
|
||||||
wrapper.find("DropdownBase.accountSwitchSubscriptionDropdown").simulate("click");
|
|
||||||
// Click will dismiss the first contextual menu in enzyme. Need to dig deeper to achieve below test
|
|
||||||
|
|
||||||
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(true);
|
|
||||||
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(2);
|
|
||||||
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
|
||||||
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(false);
|
|
||||||
|
|
||||||
// expect(wrapper.exists("div.accountSwitchAccountDropdown")).toBe(true);
|
|
||||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
|
||||||
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(true);
|
|
||||||
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(3);
|
|
||||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
|
||||||
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(false);
|
|
||||||
|
|
||||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
|
||||||
// expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
|
||||||
|
|
||||||
wrapper.unmount();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// describe("test function", () => {
|
|
||||||
// it("switch subscription function", () => {
|
|
||||||
// const props = createFullProps();
|
|
||||||
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
|
||||||
|
|
||||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
|
||||||
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
|
||||||
// wrapper
|
|
||||||
// .find("button.ms-Dropdown-item")
|
|
||||||
// .at(1)
|
|
||||||
// .simulate("click");
|
|
||||||
// expect(props.onSubscriptionChange).toBeCalled();
|
|
||||||
// expect(props.onSubscriptionChange).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// wrapper.unmount();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it("switch account", () => {
|
|
||||||
// const props = createFullProps();
|
|
||||||
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
|
||||||
|
|
||||||
// wrapper.find("button.accountSwitchButton").simulate("click");
|
|
||||||
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
|
||||||
// wrapper
|
|
||||||
// .find("button.ms-Dropdown-item")
|
|
||||||
// .at(0)
|
|
||||||
// .simulate("click");
|
|
||||||
// expect(props.onAccountChange).toBeCalled();
|
|
||||||
// expect(props.onAccountChange).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// wrapper.unmount();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
import { AuthType } from "../../../AuthType";
|
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
|
||||||
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { DefaultButton, IButtonStyles, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
|
||||||
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
|
||||||
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
|
||||||
|
|
||||||
export interface AccountSwitchComponentProps {
|
|
||||||
authType: AuthType;
|
|
||||||
selectedAccountName: string;
|
|
||||||
accounts: DatabaseAccount[];
|
|
||||||
isLoadingAccounts: boolean;
|
|
||||||
onAccountChange: (newAccount: DatabaseAccount) => void;
|
|
||||||
selectedSubscriptionId: string;
|
|
||||||
subscriptions: Subscription[];
|
|
||||||
isLoadingSubscriptions: boolean;
|
|
||||||
onSubscriptionChange: (newSubscription: Subscription) => void;
|
|
||||||
displayText?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AccountSwitchComponent extends React.Component<AccountSwitchComponentProps> {
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return this.props.authType === AuthType.AAD ? this._renderSwitchDropDown() : this._renderAccountName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderSwitchDropDown(): JSX.Element {
|
|
||||||
const { displayText, selectedAccountName } = this.props;
|
|
||||||
|
|
||||||
const menuProps: IContextualMenuProps = {
|
|
||||||
directionalHintFixed: true,
|
|
||||||
className: "accountSwitchContextualMenu",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: "switchSubscription",
|
|
||||||
onRender: this._renderSubscriptionDropdown.bind(this)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "switchAccount",
|
|
||||||
onRender: this._renderAccountDropDown.bind(this)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonStyles: IButtonStyles = {
|
|
||||||
root: {
|
|
||||||
fontSize: StyleConstants.DefaultFontSize,
|
|
||||||
height: 40,
|
|
||||||
padding: 0,
|
|
||||||
paddingLeft: 10,
|
|
||||||
marginRight: 5,
|
|
||||||
backgroundColor: StyleConstants.BaseDark,
|
|
||||||
color: StyleConstants.BaseLight
|
|
||||||
},
|
|
||||||
rootHovered: {
|
|
||||||
backgroundColor: StyleConstants.BaseHigh,
|
|
||||||
color: StyleConstants.BaseLight
|
|
||||||
},
|
|
||||||
rootFocused: {
|
|
||||||
backgroundColor: StyleConstants.BaseHigh,
|
|
||||||
color: StyleConstants.BaseLight
|
|
||||||
},
|
|
||||||
rootPressed: {
|
|
||||||
backgroundColor: StyleConstants.BaseHigh,
|
|
||||||
color: StyleConstants.BaseLight
|
|
||||||
},
|
|
||||||
rootExpanded: {
|
|
||||||
backgroundColor: StyleConstants.BaseHigh,
|
|
||||||
color: StyleConstants.BaseLight
|
|
||||||
},
|
|
||||||
textContainer: {
|
|
||||||
flexGrow: "initial"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonProps: IButtonProps = {
|
|
||||||
text: displayText || selectedAccountName,
|
|
||||||
menuProps: menuProps,
|
|
||||||
styles: buttonStyles,
|
|
||||||
className: "accountSwitchButton",
|
|
||||||
id: "accountSwitchButton"
|
|
||||||
};
|
|
||||||
|
|
||||||
return <DefaultButton {...buttonProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderSubscriptionDropdown(): JSX.Element {
|
|
||||||
const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props;
|
|
||||||
const options: IDropdownOption[] = subscriptions.map(sub => {
|
|
||||||
return {
|
|
||||||
key: sub.subscriptionId,
|
|
||||||
text: sub.displayName,
|
|
||||||
data: sub
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const placeHolderText = isLoadingSubscriptions
|
|
||||||
? "Loading subscriptions"
|
|
||||||
: !options || !options.length
|
|
||||||
? "No subscriptions found in current directory"
|
|
||||||
: "Select subscription from list";
|
|
||||||
|
|
||||||
const dropdownProps: IDropdownProps = {
|
|
||||||
label: "Subscription",
|
|
||||||
className: "accountSwitchSubscriptionDropdown",
|
|
||||||
options: options,
|
|
||||||
onChange: this._onSubscriptionDropdownChange,
|
|
||||||
defaultSelectedKey: selectedSubscriptionId,
|
|
||||||
placeholder: placeHolderText,
|
|
||||||
styles: {
|
|
||||||
callout: "accountSwitchSubscriptionDropdownMenu"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dropdown {...dropdownProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onSubscriptionDropdownChange = (e: React.FormEvent<HTMLDivElement>, option: IDropdownOption): void => {
|
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onSubscriptionChange(option.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _renderAccountDropDown(): JSX.Element {
|
|
||||||
const { accounts, selectedAccountName, isLoadingAccounts } = this.props;
|
|
||||||
const options: IDropdownOption[] = accounts.map(account => {
|
|
||||||
return {
|
|
||||||
key: account.name,
|
|
||||||
text: account.name,
|
|
||||||
data: account
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// Fabric UI will also try to select the first non-disabled option from dropdown.
|
|
||||||
// Add a option to prevent pop the message when user click on dropdown on first time.
|
|
||||||
options.unshift({
|
|
||||||
key: "select from list",
|
|
||||||
text: "Select Cosmos DB account from list",
|
|
||||||
data: undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
const placeHolderText = isLoadingAccounts
|
|
||||||
? "Loading Cosmos DB accounts"
|
|
||||||
: !options || !options.length
|
|
||||||
? "No Cosmos DB accounts found"
|
|
||||||
: "Select Cosmos DB account from list";
|
|
||||||
|
|
||||||
const dropdownProps: IDropdownProps = {
|
|
||||||
label: "Cosmos DB Account Name",
|
|
||||||
className: "accountSwitchAccountDropdown",
|
|
||||||
options: options,
|
|
||||||
onChange: this._onAccountDropdownChange,
|
|
||||||
defaultSelectedKey: selectedAccountName,
|
|
||||||
placeholder: placeHolderText,
|
|
||||||
styles: {
|
|
||||||
callout: "accountSwitchAccountDropdownMenu"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Dropdown {...dropdownProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onAccountDropdownChange = (e: React.FormEvent<HTMLDivElement>, option: IDropdownOption): void => {
|
|
||||||
if (!option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.onAccountChange(option.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
private _renderAccountName(): JSX.Element {
|
|
||||||
const { displayText, selectedAccountName } = this.props;
|
|
||||||
return <span className="accountNameHeader">{displayText || selectedAccountName}</span>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|
||||||
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
|
||||||
|
|
||||||
export class AccountSwitchComponentAdapter implements ReactAdapter {
|
|
||||||
public parameters: ko.Observable<AccountSwitchComponentProps>;
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
return <AccountSwitchComponent {...this.parameters()} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`test render renders auth aad, with all information 1`] = `
|
|
||||||
<CustomizedDefaultButton
|
|
||||||
className="accountSwitchButton"
|
|
||||||
id="accountSwitchButton"
|
|
||||||
menuProps={
|
|
||||||
Object {
|
|
||||||
"className": "accountSwitchContextualMenu",
|
|
||||||
"directionalHintFixed": true,
|
|
||||||
"items": Array [
|
|
||||||
Object {
|
|
||||||
"key": "switchSubscription",
|
|
||||||
"onRender": [Function],
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "switchAccount",
|
|
||||||
"onRender": [Function],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"color": undefined,
|
|
||||||
"fontSize": undefined,
|
|
||||||
"height": 40,
|
|
||||||
"marginRight": 5,
|
|
||||||
"padding": 0,
|
|
||||||
"paddingLeft": 10,
|
|
||||||
},
|
|
||||||
"rootExpanded": Object {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"color": undefined,
|
|
||||||
},
|
|
||||||
"rootFocused": Object {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"color": undefined,
|
|
||||||
},
|
|
||||||
"rootHovered": Object {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"color": undefined,
|
|
||||||
},
|
|
||||||
"rootPressed": Object {
|
|
||||||
"backgroundColor": undefined,
|
|
||||||
"color": undefined,
|
|
||||||
},
|
|
||||||
"textContainer": Object {
|
|
||||||
"flexGrow": "initial",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text="account2"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`test render renders auth security token, with selected account name 1`] = `
|
|
||||||
<span
|
|
||||||
className="accountNameHeader"
|
|
||||||
>
|
|
||||||
testaccount
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`test render renders no auth type -> handle error in code 1`] = `
|
|
||||||
<span
|
|
||||||
className="accountNameHeader"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
@@ -38,7 +38,7 @@ export interface CommandButtonComponentProps {
|
|||||||
/**
|
/**
|
||||||
* Label for the button
|
* Label for the button
|
||||||
*/
|
*/
|
||||||
commandButtonLabel: string;
|
commandButtonLabel?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this button opens a tab or pane, false otherwise.
|
* True if this button opens a tab or pane, false otherwise.
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
|||||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||||
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
||||||
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
|
|
||||||
{
|
{
|
||||||
key: "feature.enableLinkInjection",
|
key: "feature.enableLinkInjection",
|
||||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||||
|
|||||||
@@ -157,14 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablecodeofconduct"
|
key="feature.enableLinkInjection"
|
||||||
label="Enable Code Of Conduct Acknowledgement"
|
label="Enable Injecting Notebook Viewer Link into the first cell"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enableLinkInjection"
|
key="feature.canexceedmaximumvalue"
|
||||||
label="Enable Injecting Notebook Viewer Link into the first cell"
|
label="Can exceed max value"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -172,12 +172,6 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
className="checkboxRow"
|
className="checkboxRow"
|
||||||
horizontalAlign="space-between"
|
horizontalAlign="space-between"
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
|
||||||
checked={false}
|
|
||||||
key="feature.canexceedmaximumvalue"
|
|
||||||
label="Can exceed max value"
|
|
||||||
onChange={[Function]}
|
|
||||||
/>
|
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.enablefixedcollectionwithsharedthroughput"
|
key="feature.enablefixedcollectionwithsharedthroughput"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontIcon,
|
||||||
FontWeights,
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
Text
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
@@ -136,7 +137,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
||||||
|
|
||||||
if (this.props.container?.isGalleryPublishEnabled()) {
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
@@ -146,7 +147,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
this.state.isCodeOfConductAccepted
|
this.state.isCodeOfConductAccepted
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
||||||
|
|
||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
@@ -183,6 +184,27 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isEmptyData = (data: IGalleryItem[]): boolean => {
|
||||||
|
return !data || data.length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private createEmptyTabContent = (iconName: string, line1: string, line2: string): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack horizontalAlign="center" tokens={{ childrenGap: 10 }}>
|
||||||
|
<FontIcon iconName={iconName} style={{ fontSize: 100, color: "lightgray", marginTop: 20 }} />
|
||||||
|
<Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{line1}</Text>
|
||||||
|
<Text>{line2}</Text>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
private createPublicGalleryTab(
|
private createPublicGalleryTab(
|
||||||
tab: GalleryTab,
|
tab: GalleryTab,
|
||||||
data: IGalleryItem[],
|
data: IGalleryItem[],
|
||||||
@@ -194,17 +216,29 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createFavoritesTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
content: this.isEmptyData(data)
|
||||||
|
? this.createEmptyTabContent(
|
||||||
|
"ContactHeart",
|
||||||
|
"You have not liked anything",
|
||||||
|
"Like any notebook from Official Samples or Public gallery"
|
||||||
|
)
|
||||||
|
: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createPublishedNotebooksTabContent(data)
|
content: this.isEmptyData(data)
|
||||||
|
? this.createEmptyTabContent(
|
||||||
|
"Contact",
|
||||||
|
"You have not published anything",
|
||||||
|
"Publish your sample notebooks to share your published work with others"
|
||||||
|
)
|
||||||
|
: this.createPublishedNotebooksTabContent(data)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -364,9 +398,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
||||||
if (!offline) {
|
if (!offline) {
|
||||||
try {
|
try {
|
||||||
let response: IJunoResponse<IPublicGalleryData> | IJunoResponse<IGalleryItem[]>;
|
let response: IJunoResponse<IGalleryItem[]> | IJunoResponse<IPublicGalleryData>;
|
||||||
if (this.props.container.isCodeOfConductEnabled()) {
|
if (this.props.container) {
|
||||||
response = await this.props.junoClient.fetchPublicNotebooks();
|
response = await this.props.junoClient.getPublicGalleryData();
|
||||||
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
||||||
this.publicNotebooks = response.data?.notebooksData;
|
this.publicNotebooks = response.data?.notebooksData;
|
||||||
} else {
|
} else {
|
||||||
@@ -568,7 +602,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
||||||
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
|
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
|
||||||
this.publishedNotebooks = this.publishedNotebooks.filter(notebook => item.id !== notebook.id);
|
this.publishedNotebooks = this.publishedNotebooks?.filter(notebook => item.id !== notebook.id);
|
||||||
this.refreshSelectedTab(item);
|
this.refreshSelectedTab(item);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ describe("SettingsComponent", () => {
|
|||||||
autoscaleMaxThroughput: 10000,
|
autoscaleMaxThroughput: 10000,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test"
|
id: "test",
|
||||||
|
offerReplacePending: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = { ...baseProps };
|
const props = { ...baseProps };
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
!!this.collection.conflictResolutionPolicy();
|
!!this.collection.conflictResolutionPolicy();
|
||||||
|
|
||||||
public isOfferReplacePending = (): boolean => {
|
public isOfferReplacePending = (): boolean => {
|
||||||
return !!this.collection?.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending];
|
return this.collection?.offer()?.offerReplacePending;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<void> => {
|
public onSaveClick = async (): Promise<void> => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { IColumn, Text } from "office-ui-fabric-react";
|
||||||
import {
|
import {
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
getEstimatedSpendElement,
|
getEstimatedSpendingElement,
|
||||||
getEstimatedAutoscaleSpendElement,
|
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
@@ -19,11 +19,37 @@ import {
|
|||||||
mongoIndexingPolicyDisclaimer,
|
mongoIndexingPolicyDisclaimer,
|
||||||
mongoIndexingPolicyAADError,
|
mongoIndexingPolicyAADError,
|
||||||
mongoIndexTransformationRefreshingMessage,
|
mongoIndexTransformationRefreshingMessage,
|
||||||
renderMongoIndexTransformationRefreshMessage
|
renderMongoIndexTransformationRefreshMessage,
|
||||||
|
ManualEstimatedSpendingDisplayProps,
|
||||||
|
PriceBreakdown,
|
||||||
|
getRuPriceBreakdown
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{ key: "costType", name: "", fieldName: "costType", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "hourly", name: "Hourly", fieldName: "hourly", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "daily", name: "Daily", fieldName: "daily", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true }
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
hourly: <Text>$ 1.02</Text>,
|
||||||
|
daily: <Text>$ 24.48</Text>,
|
||||||
|
monthly: <Text>$ 744.6</Text>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const priceBreakdown: PriceBreakdown = {
|
||||||
|
hourlyPrice: 1.02,
|
||||||
|
dailyPrice: 24.48,
|
||||||
|
monthlyPrice: 744.6,
|
||||||
|
pricePerRu: 0.00051,
|
||||||
|
currency: "RMB",
|
||||||
|
currencySign: "¥"
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getAutoPilotV3SpendElement(1000, false)}
|
{getAutoPilotV3SpendElement(1000, false)}
|
||||||
@@ -31,9 +57,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{getAutoPilotV3SpendElement(1000, true)}
|
{getAutoPilotV3SpendElement(1000, true)}
|
||||||
{getAutoPilotV3SpendElement(undefined, true)}
|
{getAutoPilotV3SpendElement(undefined, true)}
|
||||||
|
|
||||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
{getEstimatedSpendingElement(estimatedSpendingColumns, estimatedSpendingItems, 1000, 2, priceBreakdown, false)}
|
||||||
|
|
||||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
|
||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
@@ -69,4 +93,14 @@ describe("SettingsUtils functions", () => {
|
|||||||
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return correct price breakdown for a manual RU setting of 500, 1 region, multimaster disabled", () => {
|
||||||
|
const prices = getRuPriceBreakdown(500, "", 1, false, false);
|
||||||
|
expect(prices.hourlyPrice).toBe(0.04);
|
||||||
|
expect(prices.dailyPrice).toBe(0.96);
|
||||||
|
expect(prices.monthlyPrice).toBe(29.2);
|
||||||
|
expect(prices.pricePerRu).toBe(0.00008);
|
||||||
|
expect(prices.currency).toBe("USD");
|
||||||
|
expect(prices.currencySign).toBe("$");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||||
import {
|
import {
|
||||||
computeAutoscaleUsagePriceHourly,
|
|
||||||
getPriceCurrency,
|
getPriceCurrency,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
getPricePerRu,
|
getPricePerRu,
|
||||||
calculateEstimateNumber
|
estimatedCostDisclaimer
|
||||||
} from "../../../Utils/PricingUtils";
|
} from "../../../Utils/PricingUtils";
|
||||||
import {
|
import {
|
||||||
ITextFieldStyles,
|
ITextFieldStyles,
|
||||||
@@ -32,10 +31,41 @@ import {
|
|||||||
MessageBarType,
|
MessageBarType,
|
||||||
Stack,
|
Stack,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize
|
SpinnerSize,
|
||||||
|
DetailsList,
|
||||||
|
IColumn,
|
||||||
|
SelectionMode,
|
||||||
|
DetailsListLayoutMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
|
IDetailsColumnStyles
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
|
export interface EstimatedSpendingDisplayProps {
|
||||||
|
costType: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ManualEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||||
|
hourly: JSX.Element;
|
||||||
|
daily: JSX.Element;
|
||||||
|
monthly: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AutoscaleEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
||||||
|
minPerMonth: JSX.Element;
|
||||||
|
maxPerMonth: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriceBreakdown {
|
||||||
|
hourlyPrice: number;
|
||||||
|
dailyPrice: number;
|
||||||
|
monthlyPrice: number;
|
||||||
|
pricePerRu: number;
|
||||||
|
currency: string;
|
||||||
|
currencySign: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
@@ -104,6 +134,16 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
background: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
root: {
|
root: {
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -130,6 +170,10 @@ export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop:
|
|||||||
|
|
||||||
export const throughputUnit = "RU/s";
|
export const throughputUnit = "RU/s";
|
||||||
|
|
||||||
|
export function onRenderRow(props: IDetailsRowProps): JSX.Element {
|
||||||
|
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||||
|
}
|
||||||
|
|
||||||
export const getAutoPilotV3SpendElement = (
|
export const getAutoPilotV3SpendElement = (
|
||||||
maxAutoPilotThroughputSet: number,
|
maxAutoPilotThroughputSet: number,
|
||||||
isDatabaseThroughput: boolean,
|
isDatabaseThroughput: boolean,
|
||||||
@@ -165,63 +209,61 @@ export const getAutoPilotV3SpendElement = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEstimatedAutoscaleSpendElement = (
|
export const getRuPriceBreakdown = (
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
regions: number,
|
numberOfRegions: number,
|
||||||
multimaster: boolean
|
isMultimaster: boolean,
|
||||||
): JSX.Element => {
|
isAutoscale: boolean
|
||||||
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
): PriceBreakdown => {
|
||||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
const hourlyPrice: number = computeRUUsagePriceHourly({
|
||||||
const currency: string = getPriceCurrency(serverId);
|
serverId: serverId,
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
requestUnits: throughput,
|
||||||
const pricePerRu =
|
numberOfRegions: numberOfRegions,
|
||||||
getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
|
multimasterEnabled: isMultimaster,
|
||||||
getMultimasterMultiplier(regions, multimaster);
|
isAutoscale: isAutoscale
|
||||||
|
});
|
||||||
return (
|
const basePricePerRu: number = isAutoscale
|
||||||
<Text id="autoscaleSpendElement">
|
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
||||||
Estimated monthly cost ({currency}) is{" "}
|
: getPricePerRu(serverId);
|
||||||
<b>
|
return {
|
||||||
{currencySign}
|
hourlyPrice: hourlyPrice,
|
||||||
{calculateEstimateNumber(monthlyPrice / 10)}
|
dailyPrice: hourlyPrice * 24,
|
||||||
{` - `}
|
monthlyPrice: hourlyPrice * hoursInAMonth,
|
||||||
{currencySign}
|
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
||||||
{calculateEstimateNumber(monthlyPrice)}{" "}
|
currency: getPriceCurrency(serverId),
|
||||||
</b>
|
currencySign: getCurrencySign(serverId)
|
||||||
({"regions: "} {regions}, {throughput / 10} - {throughput} RU/s, {currencySign}
|
};
|
||||||
{pricePerRu}/RU)
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEstimatedSpendElement = (
|
export const getEstimatedSpendingElement = (
|
||||||
|
estimatedSpendingColumns: IColumn[],
|
||||||
|
estimatedSpendingItems: EstimatedSpendingDisplayProps[],
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
numberOfRegions: number,
|
||||||
regions: number,
|
priceBreakdown: PriceBreakdown,
|
||||||
multimaster: boolean
|
isAutoscale: boolean
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
||||||
const dailyPrice: number = hourlyPrice * 24;
|
|
||||||
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
|
||||||
const currency: string = getPriceCurrency(serverId);
|
|
||||||
const currencySign: string = getCurrencySign(serverId);
|
|
||||||
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text id="throughputSpendElement">
|
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
Estimated cost ({currency}):{" "}
|
<DetailsList
|
||||||
<b>
|
disableSelectionZone
|
||||||
{currencySign}
|
items={estimatedSpendingItems}
|
||||||
{calculateEstimateNumber(hourlyPrice)} hourly {` / `}
|
columns={estimatedSpendingColumns}
|
||||||
{currencySign}
|
selectionMode={SelectionMode.none}
|
||||||
{calculateEstimateNumber(dailyPrice)} daily {` / `}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
{currencySign}
|
onRenderRow={onRenderRow}
|
||||||
{calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
/>
|
||||||
</b>
|
<Text id="throughputSpendElement">
|
||||||
({"regions: "} {regions}, {throughput}RU/s, {currencySign}
|
({"regions: "} {numberOfRegions}, {ruRange}
|
||||||
{pricePerRu}/RU)
|
{throughput} RU/s, {priceBreakdown.currencySign}
|
||||||
</Text>
|
{priceBreakdown.pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>{estimatedCostDisclaimer}</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,6 +307,13 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const saveThroughputWarningMessage: JSX.Element = (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
||||||
|
before saving your changes
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
const getCurrentThroughput = (
|
const getCurrentThroughput = (
|
||||||
isAutoscale: boolean,
|
isAutoscale: boolean,
|
||||||
throughput: number,
|
throughput: number,
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Text,
|
Text,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
IDetailsRowProps,
|
|
||||||
DetailsRow,
|
|
||||||
IColumn,
|
IColumn,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
@@ -21,11 +19,11 @@ import {
|
|||||||
mongoIndexingPolicyDisclaimer,
|
mongoIndexingPolicyDisclaimer,
|
||||||
mediumWidthStackStyles,
|
mediumWidthStackStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
transparentDetailsRowStyles,
|
|
||||||
createAndAddMongoIndexStackProps,
|
createAndAddMongoIndexStackProps,
|
||||||
separatorStyles,
|
separatorStyles,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
infoAndToolTipTextStyle
|
infoAndToolTipTextStyle,
|
||||||
|
onRenderRow
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import {
|
import {
|
||||||
@@ -140,10 +138,6 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
|
||||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||||
return isCurrentIndex ? (
|
return isCurrentIndex ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -253,7 +247,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={initialIndexes}
|
items={initialIndexes}
|
||||||
columns={this.initialIndexesColumns}
|
columns={this.initialIndexesColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={this.onRenderRow}
|
onRenderRow={onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
{this.renderIndexesToBeAdded()}
|
{this.renderIndexesToBeAdded()}
|
||||||
@@ -279,7 +273,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={indexesToBeDropped}
|
items={indexesToBeDropped}
|
||||||
columns={this.indexesToBeDroppedColumns}
|
columns={this.indexesToBeDroppedColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={this.onRenderRow}
|
onRenderRow={onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe("ScaleComponent", () => {
|
|||||||
autoscaleMaxThroughput: maxThroughput,
|
autoscaleMaxThroughput: maxThroughput,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "offer",
|
id: "offer",
|
||||||
headers: { "x-ms-offer-replace-pending": true }
|
offerReplacePending: true
|
||||||
});
|
});
|
||||||
const newProps = {
|
const newProps = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.props.collection?.offer();
|
const offer = this.props.collection?.offer();
|
||||||
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) {
|
if (offer?.offerReplacePending) {
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
||||||
return getThroughputApplyShortDelayMessage(
|
return getThroughputApplyShortDelayMessage(
|
||||||
this.props.isAutoPilotSelected,
|
this.props.isAutoPilotSelected,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
||||||
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("autopilot input visible", () => {
|
it("autopilot input visible", () => {
|
||||||
@@ -72,8 +71,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
|
|
||||||
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(true);
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("spendAck checkbox visible", () => {
|
it("spendAck checkbox visible", () => {
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import {
|
|||||||
checkBoxAndInputStackProps,
|
checkBoxAndInputStackProps,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
messageBarStyles,
|
messageBarStyles,
|
||||||
getEstimatedSpendElement,
|
getEstimatedSpendingElement,
|
||||||
getEstimatedAutoscaleSpendElement,
|
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
manualToAutoscaleDisclaimerElement
|
manualToAutoscaleDisclaimerElement,
|
||||||
|
saveThroughputWarningMessage,
|
||||||
|
ManualEstimatedSpendingDisplayProps,
|
||||||
|
AutoscaleEstimatedSpendingDisplayProps,
|
||||||
|
PriceBreakdown,
|
||||||
|
getRuPriceBreakdown,
|
||||||
|
transparentDetailsHeaderStyle
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
@@ -23,7 +28,9 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType
|
MessageBarType,
|
||||||
|
FontIcon,
|
||||||
|
IColumn
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||||
@@ -32,7 +39,7 @@ import * as DataModels from "../../../../../Contracts/DataModels";
|
|||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||||
import { usageInGB } from "../../../../../Utils/PricingUtils";
|
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
||||||
import { Features } from "../../../../../Common/Constants";
|
import { Features } from "../../../../../Common/Constants";
|
||||||
|
|
||||||
export interface ThroughputInputAutoPilotV3Props {
|
export interface ThroughputInputAutoPilotV3Props {
|
||||||
@@ -165,33 +172,243 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
|
||||||
const serverId: string = this.props.serverId;
|
const serverId: string = this.props.serverId;
|
||||||
const offerThroughput: number = this.props.throughput;
|
|
||||||
|
|
||||||
const regions = account?.properties?.readLocations?.length || 1;
|
const regions = account?.properties?.readLocations?.length || 1;
|
||||||
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
||||||
|
|
||||||
let estimatedSpend: JSX.Element;
|
let estimatedSpend: JSX.Element;
|
||||||
|
|
||||||
if (!this.props.isAutoPilotSelected) {
|
if (!this.props.isAutoPilotSelected) {
|
||||||
estimatedSpend = getEstimatedSpendElement(
|
estimatedSpend = this.getEstimatedManualSpendElement(
|
||||||
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
||||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
isDirty ? this.props.throughput : undefined
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
|
||||||
this.props.maxAutoPilotThroughput,
|
this.props.maxAutoPilotThroughputBaseline,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster
|
multimaster,
|
||||||
|
isDirty ? this.props.maxAutoPilotThroughput : undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return estimatedSpend;
|
return estimatedSpend;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getEstimatedAutoscaleSpendElement = (
|
||||||
|
throughput: number,
|
||||||
|
serverId: string,
|
||||||
|
numberOfRegions: number,
|
||||||
|
isMultimaster: boolean,
|
||||||
|
newThroughput?: number
|
||||||
|
): JSX.Element => {
|
||||||
|
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{
|
||||||
|
key: "costType",
|
||||||
|
name: "",
|
||||||
|
fieldName: "costType",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "minPerMonth",
|
||||||
|
name: "Min Per Month",
|
||||||
|
fieldName: "minPerMonth",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "maxPerMonth",
|
||||||
|
name: "Max Per Month",
|
||||||
|
fieldName: "maxPerMonth",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
minPerMonth: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
maxPerMonth: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (newThroughput) {
|
||||||
|
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||||
|
newThroughput,
|
||||||
|
serverId,
|
||||||
|
numberOfRegions,
|
||||||
|
isMultimaster,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
estimatedSpendingItems.unshift({
|
||||||
|
costType: (
|
||||||
|
<Text>
|
||||||
|
<b>Updated Cost</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
minPerMonth: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
maxPerMonth: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEstimatedSpendingElement(
|
||||||
|
estimatedSpendingColumns,
|
||||||
|
estimatedSpendingItems,
|
||||||
|
newThroughput ?? throughput,
|
||||||
|
numberOfRegions,
|
||||||
|
prices,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private getEstimatedManualSpendElement = (
|
||||||
|
throughput: number,
|
||||||
|
serverId: string,
|
||||||
|
numberOfRegions: number,
|
||||||
|
isMultimaster: boolean,
|
||||||
|
newThroughput?: number
|
||||||
|
): JSX.Element => {
|
||||||
|
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
|
||||||
|
const estimatedSpendingColumns: IColumn[] = [
|
||||||
|
{
|
||||||
|
key: "costType",
|
||||||
|
name: "",
|
||||||
|
fieldName: "costType",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "hourly",
|
||||||
|
name: "Hourly",
|
||||||
|
fieldName: "hourly",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "daily",
|
||||||
|
name: "Daily",
|
||||||
|
fieldName: "daily",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "monthly",
|
||||||
|
name: "Monthly",
|
||||||
|
fieldName: "monthly",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true,
|
||||||
|
styles: transparentDetailsHeaderStyle
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
||||||
|
{
|
||||||
|
costType: <Text>Current Cost</Text>,
|
||||||
|
hourly: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.hourlyPrice)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
daily: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.dailyPrice)}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
monthly: (
|
||||||
|
<Text>
|
||||||
|
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (newThroughput) {
|
||||||
|
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
||||||
|
newThroughput,
|
||||||
|
serverId,
|
||||||
|
numberOfRegions,
|
||||||
|
isMultimaster,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
estimatedSpendingItems.unshift({
|
||||||
|
costType: (
|
||||||
|
<Text>
|
||||||
|
<b>Updated Cost</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
hourly: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.hourlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
daily: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.dailyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
monthly: (
|
||||||
|
<Text>
|
||||||
|
<b>
|
||||||
|
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEstimatedSpendingElement(
|
||||||
|
estimatedSpendingColumns,
|
||||||
|
estimatedSpendingItems,
|
||||||
|
newThroughput ?? throughput,
|
||||||
|
numberOfRegions,
|
||||||
|
prices,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private getAutoPilotUsageCost = (): JSX.Element => {
|
private getAutoPilotUsageCost = (): JSX.Element => {
|
||||||
if (!this.props.maxAutoPilotThroughput) {
|
if (!this.props.maxAutoPilotThroughput) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -318,6 +535,12 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
|
|
||||||
private renderThroughputInput = (): JSX.Element => (
|
private renderThroughputInput = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
||||||
|
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
type="number"
|
type="number"
|
||||||
@@ -349,13 +572,24 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<br />
|
||||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private renderWarningMessage = (): JSX.Element => {
|
||||||
|
let warningMessage: JSX.Element;
|
||||||
|
if (this.IsComponentDirty().isDiscardable) {
|
||||||
|
warningMessage = saveThroughputWarningMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}</>;
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...checkBoxAndInputStackProps}>
|
<Stack {...checkBoxAndInputStackProps}>
|
||||||
|
{this.renderWarningMessage()}
|
||||||
{this.renderThroughputModeChoices()}
|
{this.renderThroughputModeChoices()}
|
||||||
|
|
||||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={5}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes
|
||||||
|
</Text>
|
||||||
|
</StyledMessageBarBase>
|
||||||
<Stack>
|
<Stack>
|
||||||
<StyledLabelBase
|
<StyledLabelBase
|
||||||
id="settingsV2RadioButtonLabelId"
|
id="settingsV2RadioButtonLabelId"
|
||||||
@@ -214,6 +229,19 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
capacity calculator
|
||||||
|
|
||||||
|
<Component
|
||||||
|
iconName="NavigateExternalInline"
|
||||||
|
/>
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -239,38 +267,142 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
USD
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
<b>
|
0.19
|
||||||
$
|
</Text>,
|
||||||
0.0080
|
"hourly": <Text>
|
||||||
hourly
|
$
|
||||||
/
|
|
||||||
$
|
|
||||||
0.19
|
|
||||||
daily
|
|
||||||
/
|
|
||||||
$
|
|
||||||
5.84
|
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
0.0080
|
||||||
(
|
</Text>,
|
||||||
regions:
|
"monthly": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
1
|
5.84
|
||||||
,
|
</Text>,
|
||||||
100
|
},
|
||||||
RU/s,
|
]
|
||||||
$
|
}
|
||||||
0.00008
|
layoutMode={1}
|
||||||
/RU)
|
onRenderRow={[Function]}
|
||||||
</Text>
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>
|
||||||
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
|
</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
@@ -288,6 +420,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<br />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
@@ -369,6 +502,19 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Text>
|
||||||
|
Estimate your required throughput with
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://cosmos.azure.com/capacitycalculator/"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
capacity calculator
|
||||||
|
|
||||||
|
<Component
|
||||||
|
iconName="NavigateExternalInline"
|
||||||
|
/>
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -394,38 +540,143 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
USD
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
"styles": Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
":hover": Object {
|
||||||
|
"background": "transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
<b>
|
0.19
|
||||||
$
|
</Text>,
|
||||||
0.0080
|
"hourly": <Text>
|
||||||
hourly
|
$
|
||||||
/
|
|
||||||
$
|
|
||||||
0.19
|
|
||||||
daily
|
|
||||||
/
|
|
||||||
$
|
|
||||||
5.84
|
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
0.0080
|
||||||
(
|
</Text>,
|
||||||
regions:
|
"monthly": <Text>
|
||||||
|
$
|
||||||
|
|
||||||
1
|
5.84
|
||||||
,
|
</Text>,
|
||||||
100
|
},
|
||||||
RU/s,
|
]
|
||||||
$
|
}
|
||||||
0.00008
|
layoutMode={1}
|
||||||
/RU)
|
onRenderRow={[Function]}
|
||||||
</Text>
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
<em>
|
||||||
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
|
</em>
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<br />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ export const collection = ({
|
|||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: 10000,
|
manualThroughput: 10000,
|
||||||
minimumThroughput: 6000,
|
minimumThroughput: 6000,
|
||||||
id: "offer"
|
id: "offer",
|
||||||
|
offerReplacePending: false
|
||||||
}),
|
}),
|
||||||
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
||||||
{} as DataModels.ConflictResolutionPolicy
|
{} as DataModels.ConflictResolutionPolicy
|
||||||
|
|||||||
@@ -731,7 +731,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -943,7 +942,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -1024,7 +1022,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -1050,7 +1047,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -2006,7 +2002,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2218,7 +2213,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -2299,7 +2293,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2325,7 +2318,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -3294,7 +3286,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3506,7 +3497,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -3587,7 +3577,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3613,7 +3602,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -4569,7 +4557,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"armEndpoint": [Function],
|
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4781,7 +4768,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isCodeOfConductEnabled": [Function],
|
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -4862,7 +4848,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
"parentFrameDataExplorerVersion": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4888,7 +4873,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"quotaId": [Function],
|
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
|
|||||||
@@ -60,66 +60,100 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Stack
|
||||||
id="throughputSpendElement"
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Estimated cost (
|
<StyledWithViewportComponent
|
||||||
RMB
|
columns={
|
||||||
):
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "costType",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "costType",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "hourly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "hourly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Hourly",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "daily",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "daily",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Daily",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "monthly",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "monthly",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Monthly",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"costType": <Text>
|
||||||
|
Current Cost
|
||||||
|
</Text>,
|
||||||
|
"daily": <Text>
|
||||||
|
$ 24.48
|
||||||
|
</Text>,
|
||||||
|
"hourly": <Text>
|
||||||
|
$ 1.02
|
||||||
|
</Text>,
|
||||||
|
"monthly": <Text>
|
||||||
|
$ 744.6
|
||||||
|
</Text>,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
layoutMode={1}
|
||||||
|
onRenderRow={[Function]}
|
||||||
|
selectionMode={0}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
id="throughputSpendElement"
|
||||||
|
>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
<b>
|
2
|
||||||
|
,
|
||||||
|
1000
|
||||||
|
RU/s,
|
||||||
¥
|
¥
|
||||||
1.02
|
0.00051
|
||||||
hourly
|
/RU)
|
||||||
/
|
</Text>
|
||||||
¥
|
<Text>
|
||||||
24.48
|
<em>
|
||||||
daily
|
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
||||||
/
|
</em>
|
||||||
¥
|
</Text>
|
||||||
744.60
|
</Stack>
|
||||||
monthly
|
|
||||||
|
|
||||||
</b>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
2
|
|
||||||
,
|
|
||||||
1000
|
|
||||||
RU/s,
|
|
||||||
¥
|
|
||||||
0.00051
|
|
||||||
/RU)
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
id="autoscaleSpendElement"
|
|
||||||
>
|
|
||||||
Estimated monthly cost (
|
|
||||||
RMB
|
|
||||||
) is
|
|
||||||
|
|
||||||
<b>
|
|
||||||
¥
|
|
||||||
111.69
|
|
||||||
-
|
|
||||||
¥
|
|
||||||
1116.90
|
|
||||||
|
|
||||||
</b>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
2
|
|
||||||
,
|
|
||||||
100
|
|
||||||
-
|
|
||||||
1000
|
|
||||||
RU/s,
|
|
||||||
¥
|
|
||||||
0.000765
|
|
||||||
/RU)
|
|
||||||
</Text>
|
|
||||||
<Text
|
<Text
|
||||||
id="manualToAutoscaleDisclaimerElement"
|
id="manualToAutoscaleDisclaimerElement"
|
||||||
styles={
|
styles={
|
||||||
|
|||||||
@@ -126,6 +126,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
<div data-bind="visible: !isAutoPilotSelected()">
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>Estimate your required throughput with
|
||||||
|
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
<div data-bind="setTemplateReady: true">
|
<div data-bind="setTemplateReady: true">
|
||||||
<input
|
<input
|
||||||
data-bind="
|
data-bind="
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
jest.mock("../../Common/DocumentClientUtilityBase");
|
|
||||||
jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||||
jest.mock("../../Common/dataAccess/createCollection");
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
|
jest.mock("../../Common/dataAccess/createDocument");
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import GraphTab from ".././Tabs/GraphTab";
|
|||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
@@ -95,12 +95,15 @@ export class ContainerSampleGenerator {
|
|||||||
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
||||||
} else {
|
} else {
|
||||||
// For SQL all queries are executed at the same time
|
// For SQL all queries are executed at the same time
|
||||||
this.sampleDataFile.data.map(doc => {
|
await Promise.all(
|
||||||
const subPromise = createDocument(collection, doc);
|
this.sampleDataFile.data.map(async doc => {
|
||||||
subPromise.catch(reason => NotificationConsoleUtils.logConsoleError(reason));
|
try {
|
||||||
promises.push(subPromise);
|
await createDocument(collection, doc);
|
||||||
});
|
} catch (error) {
|
||||||
await Promise.all(promises);
|
NotificationConsoleUtils.logConsoleError(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPa
|
|||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
import EnvironmentUtility from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import hasher from "hasher";
|
import hasher from "hasher";
|
||||||
import NewVertexPane from "./Panes/NewVertexPane";
|
import NewVertexPane from "./Panes/NewVertexPane";
|
||||||
@@ -121,7 +121,6 @@ export default class Explorer {
|
|||||||
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
||||||
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
||||||
public subscriptionType: ko.Observable<SubscriptionType>;
|
public subscriptionType: ko.Observable<SubscriptionType>;
|
||||||
public quotaId: ko.Observable<string>;
|
|
||||||
public defaultExperience: ko.Observable<string>;
|
public defaultExperience: ko.Observable<string>;
|
||||||
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
||||||
public isPreferredApiCassandra: ko.Computed<boolean>;
|
public isPreferredApiCassandra: ko.Computed<boolean>;
|
||||||
@@ -135,12 +134,10 @@ export default class Explorer {
|
|||||||
public canSaveQueries: ko.Computed<boolean>;
|
public canSaveQueries: ko.Computed<boolean>;
|
||||||
public features: ko.Observable<any>;
|
public features: ko.Observable<any>;
|
||||||
public serverId: ko.Observable<string>;
|
public serverId: ko.Observable<string>;
|
||||||
public armEndpoint: ko.Observable<string>;
|
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
|
|
||||||
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
||||||
|
|
||||||
// Notification Console
|
// Notification Console
|
||||||
@@ -204,7 +201,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
// features
|
// features
|
||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
|
||||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
@@ -279,7 +275,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
||||||
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
||||||
this.quotaId = ko.observable<string>("");
|
|
||||||
let firstInitialization = true;
|
let firstInitialization = true;
|
||||||
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
||||||
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
||||||
@@ -319,9 +314,9 @@ export default class Explorer {
|
|||||||
if (isAccountReady) {
|
if (isAccountReady) {
|
||||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
RouteHandler.getInstance().initHandler();
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
|
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
||||||
this.arcadiaWorkspaces = ko.observableArray();
|
this.arcadiaWorkspaces = ko.observableArray();
|
||||||
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
|
this._arcadiaManager = new ArcadiaResourceManager();
|
||||||
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
|
||||||
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
||||||
);
|
);
|
||||||
@@ -371,7 +366,6 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
this.serverId = ko.observable<string>();
|
this.serverId = ko.observable<string>();
|
||||||
this.armEndpoint = ko.observable<string>(undefined);
|
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
||||||
|
|
||||||
@@ -404,9 +398,6 @@ export default class Explorer {
|
|||||||
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
||||||
);
|
);
|
||||||
this.isCodeOfConductEnabled = ko.computed<boolean>(() =>
|
|
||||||
this.isFeatureEnabled(Constants.Features.enableCodeOfConduct)
|
|
||||||
);
|
|
||||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||||
);
|
);
|
||||||
@@ -1016,9 +1007,7 @@ export default class Explorer {
|
|||||||
this.isSynapseLinkUpdating(true);
|
this.isSynapseLinkUpdating(true);
|
||||||
this._closeSynapseLinkModalDialog();
|
this._closeSynapseLinkModalDialog();
|
||||||
|
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(this.databaseAccount().id);
|
||||||
this.databaseAccount().id
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
||||||
@@ -1271,16 +1260,6 @@ export default class Explorer {
|
|||||||
$("#contextSwitchPrompt").dialog("open");
|
$("#contextSwitchPrompt").dialog("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
public displayConnectExplorerForm(): void {
|
|
||||||
$("#divExplorer").hide();
|
|
||||||
$("#connectExplorer").css("display", "flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
public hideConnectExplorerForm(): void {
|
|
||||||
$("#connectExplorer").hide();
|
|
||||||
$("#divExplorer").show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public isReadWriteToggled: () => boolean = (): boolean => {
|
public isReadWriteToggled: () => boolean = (): boolean => {
|
||||||
return this.shareAccessToggleState() === ShareAccessToggleState.ReadWrite;
|
return this.shareAccessToggleState() === ShareAccessToggleState.ReadWrite;
|
||||||
};
|
};
|
||||||
@@ -1758,61 +1737,59 @@ export default class Explorer {
|
|||||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
this.initDataExplorerWithFrameInputs(inputs);
|
||||||
|
|
||||||
initPromise.then(() => {
|
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
if (!!openAction) {
|
||||||
if (!!openAction) {
|
if (this.isRefreshingExplorer()) {
|
||||||
if (this.isRefreshingExplorer()) {
|
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||||
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
|
||||||
subscription.dispose();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
}
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
}
|
}
|
||||||
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
}
|
||||||
handleCachedDataMessage(message);
|
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
||||||
return;
|
handleCachedDataMessage(message);
|
||||||
}
|
return;
|
||||||
if (message.type) {
|
}
|
||||||
switch (message.type) {
|
if (message.type) {
|
||||||
case MessageTypes.UpdateLocationHash:
|
switch (message.type) {
|
||||||
if (!message.locationHash) {
|
case MessageTypes.UpdateLocationHash:
|
||||||
break;
|
if (!message.locationHash) {
|
||||||
}
|
break;
|
||||||
hasher.replaceHash(message.locationHash);
|
}
|
||||||
RouteHandler.getInstance().parseHash(message.locationHash);
|
hasher.replaceHash(message.locationHash);
|
||||||
break;
|
RouteHandler.getInstance().parseHash(message.locationHash);
|
||||||
case MessageTypes.SendNotification:
|
break;
|
||||||
if (!message.message) {
|
case MessageTypes.SendNotification:
|
||||||
break;
|
if (!message.message) {
|
||||||
}
|
break;
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
}
|
||||||
message.consoleDataType || ConsoleDataType.Info,
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
message.message,
|
message.consoleDataType || ConsoleDataType.Info,
|
||||||
message.id
|
message.message,
|
||||||
);
|
message.id
|
||||||
break;
|
);
|
||||||
case MessageTypes.ClearNotification:
|
break;
|
||||||
if (!message.id) {
|
case MessageTypes.ClearNotification:
|
||||||
break;
|
if (!message.id) {
|
||||||
}
|
break;
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
}
|
||||||
break;
|
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
||||||
case MessageTypes.LoadingStatus:
|
break;
|
||||||
if (!message.text) {
|
case MessageTypes.LoadingStatus:
|
||||||
break;
|
if (!message.text) {
|
||||||
}
|
break;
|
||||||
this._setLoadingStatusText(message.text, message.title);
|
}
|
||||||
break;
|
this._setLoadingStatusText(message.text, message.title);
|
||||||
}
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.splashScreenAdapter.forceRender();
|
this.splashScreenAdapter.forceRender();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
public findSelectedDatabase(): ViewModels.Database {
|
||||||
@@ -1852,8 +1829,14 @@ export default class Explorer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
|
||||||
if (inputs != null) {
|
if (inputs != null) {
|
||||||
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
|
// This allows webpack hot reload to funciton properly
|
||||||
|
if (process.env.NODE_ENV === "development" && configContext.platform === Platform.Portal) {
|
||||||
|
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
const authorizationToken = inputs.authorizationToken || "";
|
const authorizationToken = inputs.authorizationToken || "";
|
||||||
const masterKey = inputs.masterKey || "";
|
const masterKey = inputs.masterKey || "";
|
||||||
const databaseAccount = inputs.databaseAccount || null;
|
const databaseAccount = inputs.databaseAccount || null;
|
||||||
@@ -1862,25 +1845,18 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
this.features(inputs.features);
|
this.features(inputs.features);
|
||||||
this.serverId(inputs.serverId);
|
this.serverId(inputs.serverId);
|
||||||
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
this.quotaId(inputs.quotaId);
|
|
||||||
this.hasWriteAccess(inputs.hasWriteAccess);
|
this.hasWriteAccess(inputs.hasWriteAccess);
|
||||||
this.flight(inputs.addCollectionDefaultFlight);
|
this.flight(inputs.addCollectionDefaultFlight);
|
||||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||||
this.setFeatureFlagsFromFlights(inputs.flights);
|
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||||
|
|
||||||
if (!!inputs.dataExplorerVersion) {
|
|
||||||
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
||||||
ARM_ENDPOINT: this.armEndpoint()
|
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT)
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -1889,21 +1865,11 @@ export default class Explorer {
|
|||||||
databaseAccount,
|
databaseAccount,
|
||||||
resourceGroup: inputs.resourceGroup,
|
resourceGroup: inputs.resourceGroup,
|
||||||
subscriptionId: inputs.subscriptionId,
|
subscriptionId: inputs.subscriptionId,
|
||||||
subscriptionType: inputs.subscriptionType
|
subscriptionType: inputs.subscriptionType,
|
||||||
|
quotaId: inputs.quotaId
|
||||||
});
|
});
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.LoadDatabaseAccount,
|
|
||||||
{
|
|
||||||
resourceId: this.databaseAccount && this.databaseAccount().id,
|
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
|
||||||
databaseAccount: this.databaseAccount && this.databaseAccount()
|
|
||||||
},
|
|
||||||
inputs.loadDatabaseAccountTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
this.isAccountReady(true);
|
this.isAccountReady(true);
|
||||||
}
|
}
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||||
@@ -2276,7 +2242,6 @@ export default class Explorer {
|
|||||||
name,
|
name,
|
||||||
content,
|
content,
|
||||||
parentDomElement,
|
parentDomElement,
|
||||||
this.isCodeOfConductEnabled(),
|
|
||||||
this.isLinkInjectionEnabled()
|
this.isLinkInjectionEnabled()
|
||||||
);
|
);
|
||||||
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
||||||
@@ -2567,7 +2532,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = this.armEndpoint();
|
const armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2576,7 +2541,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -2596,7 +2561,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = this.armEndpoint();
|
const armEndpoint = configContext.ARM_ENDPOINT;
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2604,7 +2569,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
jest.mock("../../../Common/DocumentClientUtilityBase");
|
jest.mock("../../../Common/dataAccess/queryDocuments");
|
||||||
|
jest.mock("../../../Common/dataAccess/queryDocumentsPage");
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
@@ -12,7 +13,8 @@ import * as DataModels from "../../../Contracts/DataModels";
|
|||||||
import * as StorageUtility from "../../../Shared/StorageUtility";
|
import * as StorageUtility from "../../../Shared/StorageUtility";
|
||||||
import GraphTab from "../../Tabs/GraphTab";
|
import GraphTab from "../../Tabs/GraphTab";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||||
|
|
||||||
describe("Check whether query result is vertex array", () => {
|
describe("Check whether query result is vertex array", () => {
|
||||||
it("should reject null as vertex array", () => {
|
it("should reject null as vertex array", () => {
|
||||||
@@ -299,12 +301,12 @@ describe("GraphExplorer", () => {
|
|||||||
ignoreD3Update: boolean
|
ignoreD3Update: boolean
|
||||||
): GraphExplorer => {
|
): GraphExplorer => {
|
||||||
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
||||||
return Q.resolve({
|
return {
|
||||||
_query: query,
|
_query: query,
|
||||||
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
||||||
hasMoreResults: () => false,
|
hasMoreResults: () => false,
|
||||||
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
|
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
|
||||||
});
|
};
|
||||||
});
|
});
|
||||||
(queryDocumentsPage as jest.Mock).mockImplementation(
|
(queryDocumentsPage as jest.Mock).mockImplementation(
|
||||||
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
|
|
||||||
export interface GraphAccessor {
|
export interface GraphAccessor {
|
||||||
applyFilter: () => void;
|
applyFilter: () => void;
|
||||||
@@ -725,26 +727,32 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Execute DocDB query and get all results
|
* Execute DocDB query and get all results
|
||||||
*/
|
*/
|
||||||
public executeNonPagedDocDbQuery(query: string): Q.Promise<DataModels.DocumentId[]> {
|
public async executeNonPagedDocDbQuery(query: string): Promise<DataModels.DocumentId[]> {
|
||||||
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
try {
|
||||||
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
||||||
maxItemCount: GraphExplorer.PAGE_ALL,
|
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
enableCrossPartitionQuery:
|
this.props.databaseId,
|
||||||
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.IsCrossPartitionQueryEnabled) ===
|
this.props.collectionId,
|
||||||
"true"
|
query,
|
||||||
}).then(
|
{
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
maxItemCount: GraphExplorer.PAGE_ALL,
|
||||||
return iterator.fetchNext().then(response => response.resources);
|
enableCrossPartitionQuery:
|
||||||
},
|
StorageUtility.LocalStorageUtility.getEntryString(
|
||||||
(reason: any) => {
|
StorageUtility.StorageKey.IsCrossPartitionQueryEnabled
|
||||||
GraphExplorer.reportToConsole(
|
) === "true"
|
||||||
ConsoleDataType.Error,
|
} as FeedOptions
|
||||||
`Failed to execute non-paged query ${query}. Reason:${reason}`,
|
);
|
||||||
reason
|
const response = await iterator.fetchNext();
|
||||||
);
|
|
||||||
return null;
|
return response?.resources;
|
||||||
}
|
} catch (error) {
|
||||||
);
|
GraphExplorer.reportToConsole(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to execute non-paged query ${query}. Reason:${error}`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -864,7 +872,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* User executes query
|
* User executes query
|
||||||
*/
|
*/
|
||||||
public submitQuery(query: string): void {
|
public async submitQuery(query: string): Promise<void> {
|
||||||
// Clear any progress indicator
|
// Clear any progress indicator
|
||||||
this.executeCounter = 0;
|
this.executeCounter = 0;
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -882,24 +890,22 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// Remember query
|
// Remember query
|
||||||
this.pushToLatestQueryFragments(query);
|
this.pushToLatestQueryFragments(query);
|
||||||
|
|
||||||
let backendPromise;
|
try {
|
||||||
|
let result: UserQueryResult;
|
||||||
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
||||||
backendPromise = this.executeDocDbGVQuery();
|
result = await this.executeDocDbGVQuery();
|
||||||
} else {
|
} else {
|
||||||
backendPromise = this.executeGremlinQuery(query);
|
result = await this.executeGremlinQuery(query);
|
||||||
}
|
|
||||||
|
|
||||||
backendPromise.then(
|
|
||||||
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
|
|
||||||
(error: any) => {
|
|
||||||
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
|
||||||
this.setState({
|
|
||||||
filterQueryError: errorMsg
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
this.queryTotalRequestCharge = result.requestCharge;
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
||||||
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
|
this.setState({
|
||||||
|
filterQueryError: errorMsg
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1390,7 +1396,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Update possible vertices to display in UI
|
* Update possible vertices to display in UI
|
||||||
*/
|
*/
|
||||||
private updatePossibleVertices(): Q.Promise<PossibleVertex[]> {
|
private updatePossibleVertices(): Promise<PossibleVertex[]> {
|
||||||
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
||||||
|
|
||||||
const q = `SELECT c.id, c["${this.props.graphConfigUiData.nodeCaptionChoice() ||
|
const q = `SELECT c.id, c["${this.props.graphConfigUiData.nodeCaptionChoice() ||
|
||||||
@@ -1721,85 +1727,81 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeDocDbGVQuery(): Q.Promise<UserQueryResult> {
|
private async executeDocDbGVQuery(): Promise<UserQueryResult> {
|
||||||
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
||||||
if (this.props.collectionPartitionKeyProperty) {
|
if (this.props.collectionPartitionKeyProperty) {
|
||||||
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
try {
|
||||||
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
||||||
enableCrossPartitionQuery: LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
this.props.databaseId,
|
||||||
})
|
this.props.collectionId,
|
||||||
.then(
|
query,
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
{
|
||||||
this.currentDocDBQueryInfo = {
|
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
||||||
iterator: iterator,
|
enableCrossPartitionQuery:
|
||||||
index: 0,
|
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
||||||
query: query
|
} as FeedOptions
|
||||||
};
|
);
|
||||||
},
|
this.currentDocDBQueryInfo = {
|
||||||
(reason: any) => {
|
iterator: iterator,
|
||||||
GraphExplorer.reportToConsole(
|
index: 0,
|
||||||
ConsoleDataType.Error,
|
query: query
|
||||||
`Failed to execute CosmosDB query: ${query} reason:${reason}`
|
};
|
||||||
);
|
return await this.loadMoreRootNodes();
|
||||||
}
|
} catch (error) {
|
||||||
)
|
GraphExplorer.reportToConsole(
|
||||||
.then(() => this.loadMoreRootNodes());
|
ConsoleDataType.Error,
|
||||||
|
`Failed to execute CosmosDB query: ${query} reason:${error}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadMoreRootNodes(): Q.Promise<UserQueryResult> {
|
private async loadMoreRootNodes(): Promise<UserQueryResult> {
|
||||||
if (!this.currentDocDBQueryInfo) {
|
if (!this.currentDocDBQueryInfo) {
|
||||||
return Q.resolve(null);
|
return undefined;
|
||||||
}
|
}
|
||||||
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
|
||||||
|
|
||||||
|
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
||||||
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${this
|
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${this
|
||||||
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
|
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
|
||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
||||||
|
|
||||||
return queryDocumentsPage(
|
try {
|
||||||
this.props.collectionId,
|
const results: ViewModels.QueryResults = await queryDocumentsPage(
|
||||||
this.currentDocDBQueryInfo.iterator,
|
this.props.collectionId,
|
||||||
this.currentDocDBQueryInfo.index,
|
this.currentDocDBQueryInfo.iterator,
|
||||||
{
|
this.currentDocDBQueryInfo.index
|
||||||
enableCrossPartitionQuery:
|
);
|
||||||
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
|
|
||||||
}
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
)
|
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
||||||
.then((results: ViewModels.QueryResults) => {
|
this.setState({ hasMoreRoots: results.hasMoreResults });
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
RU = results.requestCharge.toString();
|
||||||
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
GraphExplorer.reportToConsole(
|
||||||
this.setState({ hasMoreRoots: results.hasMoreResults });
|
ConsoleDataType.Info,
|
||||||
RU = results.requestCharge.toString();
|
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
||||||
GraphExplorer.reportToConsole(
|
);
|
||||||
ConsoleDataType.Info,
|
const pkIds: string[] = (results.documents || []).map((item: DataModels.DocumentId) =>
|
||||||
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty)
|
||||||
);
|
);
|
||||||
const documents = results.documents || [];
|
|
||||||
return documents.map(
|
const arg = pkIds.join(",");
|
||||||
(item: DataModels.DocumentId) => {
|
await this.executeGremlinQuery(`g.V(${arg})`);
|
||||||
return GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty);
|
|
||||||
},
|
return { requestCharge: RU };
|
||||||
(reason: any) => {
|
} catch (error) {
|
||||||
// Failure
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
|
||||||
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${reason}`;
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
this.setState({
|
||||||
this.setState({
|
filterQueryError: errorMsg
|
||||||
filterQueryError: errorMsg
|
});
|
||||||
});
|
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
||||||
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
throw error;
|
||||||
throw reason;
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.then((pkIds: string[]) => {
|
|
||||||
const arg = pkIds.join(",");
|
|
||||||
return this.executeGremlinQuery(`g.V(${arg})`);
|
|
||||||
})
|
|
||||||
.then(() => ({ requestCharge: RU }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "./MeControlComponent.less";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { DefaultButton, BaseButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
import { DefaultButton, BaseButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||||
import { DirectionalHint, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
import { DirectionalHint, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
|
|||||||
@@ -128,17 +128,9 @@ export default class NotebookManager {
|
|||||||
name: string,
|
name: string,
|
||||||
content: string | ImmutableNotebook,
|
content: string | ImmutableNotebook,
|
||||||
parentDomElement: HTMLElement,
|
parentDomElement: HTMLElement,
|
||||||
isCodeOfConductEnabled: boolean,
|
|
||||||
isLinkInjectionEnabled: boolean
|
isLinkInjectionEnabled: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.publishNotebookPaneAdapter.open(
|
await this.publishNotebookPaneAdapter.open(name, getFullName(), content, parentDomElement, isLinkInjectionEnabled);
|
||||||
name,
|
|
||||||
getFullName(),
|
|
||||||
content,
|
|
||||||
parentDomElement,
|
|
||||||
isCodeOfConductEnabled,
|
|
||||||
isLinkInjectionEnabled
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openCopyNotebookPane(name: string, content: string): void {
|
public openCopyNotebookPane(name: string, content: string): void {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
|
|||||||
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
|
||||||
isPreferredApiTable: ko.Computed<boolean>;
|
isPreferredApiTable: ko.Computed<boolean>;
|
||||||
@@ -668,7 +669,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
databaseId: this.databaseId()
|
databaseId: this.databaseId()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: this._getThroughput(),
|
throughput: this._getThroughput(),
|
||||||
@@ -770,7 +771,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -844,7 +845,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
@@ -878,7 +879,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||||
},
|
},
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
storage: this.storage() === Constants.BackendDefaults.singlePartitionStorageInGb ? "f" : "u",
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { createDatabase } from "../../Common/dataAccess/createDatabase";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export default class AddDatabasePane extends ContextualPaneBase {
|
export default class AddDatabasePane extends ContextualPaneBase {
|
||||||
public defaultExperience: ko.Computed<string>;
|
public defaultExperience: ko.Computed<string>;
|
||||||
@@ -250,7 +251,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
@@ -278,7 +279,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput,
|
offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -342,7 +343,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
@@ -366,7 +367,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
offerThroughput: offerThroughput,
|
offerThroughput: offerThroughput,
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
flight: this.container.flight()
|
flight: this.container.flight()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { HashMap } from "../../Common/HashMap";
|
|||||||
import { configContext, Platform } from "../../ConfigContext";
|
import { configContext, Platform } from "../../ConfigContext";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../Contracts/SubscriptionType";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||||
public createTableQuery: ko.Observable<string>;
|
public createTableQuery: ko.Observable<string>;
|
||||||
@@ -299,7 +300,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
databaseId: this.keyspaceId()
|
databaseId: this.keyspaceId()
|
||||||
}),
|
}),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -353,7 +354,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -399,7 +400,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
}),
|
}),
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
@@ -429,7 +430,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
|||||||
},
|
},
|
||||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||||
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
subscriptionType: SubscriptionType[this.container.subscriptionType()],
|
||||||
subscriptionQuotaId: this.container.quotaId(),
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
storage: "u",
|
storage: "u",
|
||||||
throughput: this.throughput(),
|
throughput: this.throughput(),
|
||||||
|
|||||||
@@ -98,26 +98,21 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
|
|||||||
author: string,
|
author: string,
|
||||||
notebookContent: string | ImmutableNotebook,
|
notebookContent: string | ImmutableNotebook,
|
||||||
parentDomElement: HTMLElement,
|
parentDomElement: HTMLElement,
|
||||||
isCodeOfConductEnabled: boolean,
|
|
||||||
isLinkInjectionEnabled: boolean
|
isLinkInjectionEnabled: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (isCodeOfConductEnabled) {
|
try {
|
||||||
try {
|
const response = await this.junoClient.isCodeOfConductAccepted();
|
||||||
const response = await this.junoClient.isCodeOfConductAccepted();
|
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
||||||
throw new Error(`Received HTTP ${response.status} when accepting code of conduct`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isCodeOfConductAccepted = response.data;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
|
||||||
"Failed to check if code of conduct was accepted"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.isCodeOfConductAccepted = true;
|
this.isCodeOfConductAccepted = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(
|
||||||
|
error,
|
||||||
|
"PublishNotebookPaneAdapter/isCodeOfConductAccepted",
|
||||||
|
"Failed to check if code of conduct was accepted"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as ko from "knockout";
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { ConnectionStringParser } from "../../Platform/Hosted/Helpers/ConnectionStringParser";
|
import { parseConnectionString } from "../../Platform/Hosted/Helpers/ConnectionStringParser";
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
import { ContextualPaneBase } from "./ContextualPaneBase";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
@@ -48,9 +48,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _shouldShowContextSwitchPrompt(): boolean {
|
private _shouldShowContextSwitchPrompt(): boolean {
|
||||||
const inputMetadata: DataModels.AccessInputMetadata = ConnectionStringParser.parseConnectionString(
|
const inputMetadata: DataModels.AccessInputMetadata = parseConnectionString(this.accessKey());
|
||||||
this.accessKey()
|
|
||||||
);
|
|
||||||
const apiKind: DataModels.ApiKind =
|
const apiKind: DataModels.ApiKind =
|
||||||
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
|
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
|
||||||
const hasOpenedTabs: boolean =
|
const hasOpenedTabs: boolean =
|
||||||
|
|||||||
@@ -421,53 +421,47 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
* Note that this also means that we can get less entities than the requested download size in a successful call.
|
* Note that this also means that we can get less entities than the requested download size in a successful call.
|
||||||
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
|
||||||
*/
|
*/
|
||||||
private prefetchData(
|
private async prefetchData(
|
||||||
tableQuery: Entities.ITableQuery,
|
tableQuery: Entities.ITableQuery,
|
||||||
downloadSize: number,
|
downloadSize: number,
|
||||||
currentRetry: number = 0
|
currentRetry: number = 0
|
||||||
): Q.Promise<any> {
|
): Promise<IListTableEntitiesSegmentedResult> {
|
||||||
if (!this.cache.serverCallInProgress) {
|
if (!this.cache.serverCallInProgress) {
|
||||||
this.cache.serverCallInProgress = true;
|
this.cache.serverCallInProgress = true;
|
||||||
this.allDownloaded = false;
|
this.allDownloaded = false;
|
||||||
this.lastPrefetchTime = new Date().getTime();
|
this.lastPrefetchTime = new Date().getTime();
|
||||||
var time = this.lastPrefetchTime;
|
const time = this.lastPrefetchTime;
|
||||||
|
|
||||||
var promise: Q.Promise<IListTableEntitiesSegmentedResult>;
|
|
||||||
if (this._documentIterator && this.continuationToken) {
|
if (this._documentIterator && this.continuationToken) {
|
||||||
// TODO handle Cassandra case
|
// TODO handle Cassandra case
|
||||||
|
const response = await this._documentIterator.fetchNext();
|
||||||
|
const entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(response?.resources);
|
||||||
|
|
||||||
promise = Q(this._documentIterator.fetchNext().then(response => response.resources)).then(
|
return {
|
||||||
(documents: any[]) => {
|
Results: entities,
|
||||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
ContinuationToken: this._documentIterator.hasMoreResults()
|
||||||
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
|
};
|
||||||
Results: entities,
|
|
||||||
ContinuationToken: this._documentIterator.hasMoreResults()
|
|
||||||
};
|
|
||||||
return Q.resolve(finalEntities);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
|
|
||||||
promise = this.queryTablesTab.container.tableDataClient.queryDocuments(
|
|
||||||
this.queryTablesTab.collection,
|
|
||||||
this.cqlQuery(),
|
|
||||||
true,
|
|
||||||
this.continuationToken
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let query = this.sqlQuery();
|
|
||||||
if (this.queryTablesTab.container.isPreferredApiCassandra()) {
|
|
||||||
query = this.cqlQuery();
|
|
||||||
}
|
|
||||||
promise = this.queryTablesTab.container.tableDataClient.queryDocuments(
|
|
||||||
this.queryTablesTab.collection,
|
|
||||||
query,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return promise
|
|
||||||
.then((result: IListTableEntitiesSegmentedResult) => {
|
try {
|
||||||
|
let documents: IListTableEntitiesSegmentedResult;
|
||||||
|
if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
|
||||||
|
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||||
|
this.queryTablesTab.collection,
|
||||||
|
this.cqlQuery(),
|
||||||
|
true,
|
||||||
|
this.continuationToken
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const query = this.queryTablesTab.container.isPreferredApiCassandra() ? this.cqlQuery() : this.sqlQuery();
|
||||||
|
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
|
||||||
|
this.queryTablesTab.collection,
|
||||||
|
query,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
if (!this._documentIterator) {
|
if (!this._documentIterator) {
|
||||||
this._documentIterator = result.iterator;
|
this._documentIterator = documents.iterator;
|
||||||
}
|
}
|
||||||
var actualDownloadSize: number = 0;
|
var actualDownloadSize: number = 0;
|
||||||
|
|
||||||
@@ -478,11 +472,11 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
return Q.resolve(null);
|
return Q.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = result.Results;
|
var entities = documents.Results;
|
||||||
actualDownloadSize = entities.length;
|
actualDownloadSize = entities.length;
|
||||||
|
|
||||||
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
|
||||||
this.continuationToken = this.isCancelled ? null : result.ContinuationToken;
|
this.continuationToken = this.isCancelled ? null : documents.ContinuationToken;
|
||||||
|
|
||||||
if (!this.continuationToken) {
|
if (!this.continuationToken) {
|
||||||
this.allDownloaded = true;
|
this.allDownloaded = true;
|
||||||
@@ -514,20 +508,22 @@ export default class TableEntityListViewModel extends DataTableViewModel {
|
|||||||
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
|
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
|
||||||
// For #2.2, go to next round prefetch.
|
// For #2.2, go to next round prefetch.
|
||||||
if (this.allDownloaded || nextDownloadSize === 0) {
|
if (this.allDownloaded || nextDownloadSize === 0) {
|
||||||
return Q.resolve(result);
|
return documents;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
|
||||||
result.ExceedMaximumRetries = true;
|
documents.ExceedMaximumRetries = true;
|
||||||
return Q.resolve(result);
|
return documents;
|
||||||
}
|
}
|
||||||
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
|
||||||
})
|
return await this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
|
||||||
.catch((error: Error) => {
|
}
|
||||||
this.cache.serverCallInProgress = false;
|
} catch (error) {
|
||||||
return Q.reject(error);
|
this.cache.serverCallInProgress = false;
|
||||||
});
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Q from "q";
|
|||||||
import { displayTokenRenewalPromptForStatus, getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { displayTokenRenewalPromptForStatus, getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as Entities from "./Entities";
|
import * as Entities from "./Entities";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
@@ -12,9 +13,12 @@ import * as TableConstants from "./Constants";
|
|||||||
import * as TableEntityProcessor from "./TableEntityProcessor";
|
import * as TableEntityProcessor from "./TableEntityProcessor";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
|
||||||
export interface CassandraTableKeys {
|
export interface CassandraTableKeys {
|
||||||
partitionKeys: CassandraTableKey[];
|
partitionKeys: CassandraTableKey[];
|
||||||
@@ -38,19 +42,19 @@ export abstract class TableDataClient {
|
|||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
newEntity: Entities.ITableEntity
|
newEntity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity>;
|
): Promise<Entities.ITableEntity>;
|
||||||
|
|
||||||
public abstract queryDocuments(
|
public abstract queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string,
|
query: string,
|
||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string
|
paginationToken?: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult>;
|
): Promise<Entities.IListTableEntitiesResult>;
|
||||||
|
|
||||||
public abstract deleteDocuments(
|
public abstract deleteDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
entitiesToDelete: Entities.ITableEntity[]
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
): Q.Promise<any>;
|
): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TablesAPIDataClient extends TableDataClient {
|
export class TablesAPIDataClient extends TableDataClient {
|
||||||
@@ -74,77 +78,63 @@ export class TablesAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDocument(
|
public async updateDocument(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
entity: Entities.ITableEntity
|
entity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity> {
|
): Promise<Entities.ITableEntity> {
|
||||||
const deferred = Q.defer<Entities.ITableEntity>();
|
try {
|
||||||
|
const newDocument = await updateDocument(
|
||||||
updateDocument(
|
collection,
|
||||||
collection,
|
originalDocument,
|
||||||
originalDocument,
|
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
|
||||||
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
|
);
|
||||||
).then(
|
return TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
|
||||||
(newDocument: any) => {
|
} catch (error) {
|
||||||
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
|
handleError(error, "TablesAPIDataClient/updateDocument");
|
||||||
deferred.resolve(newEntity);
|
throw error;
|
||||||
},
|
}
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public queryDocuments(
|
public async queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string
|
query: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
|
try {
|
||||||
|
const options = {
|
||||||
|
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey()
|
||||||
|
} as FeedOptions;
|
||||||
|
const iterator = queryDocuments(collection.databaseId, collection.id(), query, options);
|
||||||
|
const response = await iterator.fetchNext();
|
||||||
|
const documents = response?.resources;
|
||||||
|
const entities = TableEntityProcessor.convertDocumentsToEntities(documents);
|
||||||
|
|
||||||
let options: any = {};
|
return {
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
Results: entities,
|
||||||
queryDocuments(collection.databaseId, collection.id(), query, options).then(
|
ContinuationToken: iterator.hasMoreResults(),
|
||||||
iterator => {
|
iterator: iterator
|
||||||
iterator
|
};
|
||||||
.fetchNext()
|
} catch (error) {
|
||||||
.then(response => response.resources)
|
handleError(error, "TablesAPIDataClient/queryDocuments", "Query documents failed");
|
||||||
.then(
|
throw error;
|
||||||
(documents: any[] = []) => {
|
}
|
||||||
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
|
|
||||||
let finalEntities: Entities.IListTableEntitiesResult = <Entities.IListTableEntitiesResult>{
|
|
||||||
Results: entities,
|
|
||||||
ContinuationToken: iterator.hasMoreResults(),
|
|
||||||
iterator: iterator
|
|
||||||
};
|
|
||||||
deferred.resolve(finalEntities);
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
reason => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
|
public async deleteDocuments(
|
||||||
let documentsToDelete: any[] = TableEntityProcessor.convertEntitiesToDocuments(
|
collection: ViewModels.Collection,
|
||||||
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
|
): Promise<any> {
|
||||||
|
const documentsToDelete: any[] = TableEntityProcessor.convertEntitiesToDocuments(
|
||||||
<Entities.ITableEntityForTablesAPI[]>entitiesToDelete,
|
<Entities.ITableEntityForTablesAPI[]>entitiesToDelete,
|
||||||
collection
|
collection
|
||||||
);
|
);
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
|
||||||
documentsToDelete &&
|
await Promise.all(
|
||||||
documentsToDelete.forEach(document => {
|
documentsToDelete?.map(async document => {
|
||||||
document.id = ko.observable<string>(document.id);
|
document.id = ko.observable<string>(document.id);
|
||||||
let promise: Q.Promise<any> = deleteDocument(collection, document);
|
await deleteDocument(collection, document);
|
||||||
promiseArray.push(promise);
|
})
|
||||||
});
|
);
|
||||||
return Q.all(promiseArray);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,10 +170,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
(data: any) => {
|
(data: any) => {
|
||||||
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
|
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
|
||||||
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
|
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
NotificationConsoleUtils.logConsoleInfo(`Successfully added new row to table ${collection.id()}`);
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully added new row to table ${collection.id()}`
|
|
||||||
);
|
|
||||||
deferred.resolve(entity);
|
deferred.resolve(entity);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
@@ -197,181 +184,149 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateDocument(
|
public async updateDocument(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
originalDocument: any,
|
originalDocument: any,
|
||||||
newEntity: Entities.ITableEntity
|
newEntity: Entities.ITableEntity
|
||||||
): Q.Promise<Entities.ITableEntity> {
|
): Promise<Entities.ITableEntity> {
|
||||||
const notificationId = NotificationConsoleUtils.logConsoleMessage(
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Updating row ${originalDocument.RowKey._}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Updating row ${originalDocument.RowKey._}`
|
try {
|
||||||
);
|
let whereSegment = " WHERE";
|
||||||
const deferred = Q.defer<Entities.ITableEntity>();
|
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
collection.cassandraKeys.clusteringKeys
|
||||||
let query = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
);
|
||||||
let isChange: boolean = false;
|
for (let keyIndex in keys) {
|
||||||
for (let property in newEntity) {
|
const key = keys[keyIndex].property;
|
||||||
if (!originalDocument[property] || newEntity[property]._.toString() !== originalDocument[property]._.toString()) {
|
const keyType = keys[keyIndex].type;
|
||||||
if (this.isStringType(newEntity[property].$)) {
|
whereSegment += this.isStringType(keyType)
|
||||||
query = `${query} SET ${property} = '${newEntity[property]._}',`;
|
? ` ${key} = '${newEntity[key]._}' AND`
|
||||||
} else {
|
: ` ${key} = ${newEntity[key]._} AND`;
|
||||||
query = `${query} SET ${property} = ${newEntity[property]._},`;
|
}
|
||||||
|
whereSegment = whereSegment.slice(0, whereSegment.length - 4);
|
||||||
|
|
||||||
|
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
|
||||||
|
let isPropertyUpdated = false;
|
||||||
|
for (let property in newEntity) {
|
||||||
|
if (
|
||||||
|
!originalDocument[property] ||
|
||||||
|
newEntity[property]._.toString() !== originalDocument[property]._.toString()
|
||||||
|
) {
|
||||||
|
updateQuery += this.isStringType(newEntity[property].$)
|
||||||
|
? ` SET ${property} = '${newEntity[property]._}',`
|
||||||
|
: ` SET ${property} = ${newEntity[property]._},`;
|
||||||
|
isPropertyUpdated = true;
|
||||||
}
|
}
|
||||||
isChange = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
query = query.slice(0, query.length - 1);
|
if (isPropertyUpdated) {
|
||||||
let whereSegment = " WHERE";
|
updateQuery = updateQuery.slice(0, updateQuery.length - 1);
|
||||||
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
|
updateQuery += whereSegment;
|
||||||
collection.cassandraKeys.clusteringKeys
|
await this.queryDocuments(collection, updateQuery);
|
||||||
);
|
|
||||||
for (let keyIndex in keys) {
|
|
||||||
const key = keys[keyIndex].property;
|
|
||||||
const keyType = keys[keyIndex].type;
|
|
||||||
if (this.isStringType(keyType)) {
|
|
||||||
whereSegment = `${whereSegment} ${key} = '${newEntity[key]._}' AND`;
|
|
||||||
} else {
|
|
||||||
whereSegment = `${whereSegment} ${key} = ${newEntity[key]._} AND`;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
whereSegment = whereSegment.slice(0, whereSegment.length - 4);
|
let deleteQuery = `DELETE `;
|
||||||
query = query + whereSegment;
|
let isPropertyDeleted = false;
|
||||||
if (isChange) {
|
for (let property in originalDocument) {
|
||||||
promiseArray.push(this.queryDocuments(collection, query));
|
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
|
||||||
}
|
deleteQuery += ` ${property},`;
|
||||||
query = `DELETE `;
|
isPropertyDeleted = true;
|
||||||
for (let property in originalDocument) {
|
|
||||||
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
|
|
||||||
query = `${query} ${property},`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (query.length > 7) {
|
|
||||||
query = query.slice(0, query.length - 1);
|
|
||||||
query = `${query} FROM ${collection.databaseId}.${collection.id()}${whereSegment}`;
|
|
||||||
promiseArray.push(this.queryDocuments(collection, query));
|
|
||||||
}
|
|
||||||
Q.all(promiseArray)
|
|
||||||
.then(
|
|
||||||
(data: any) => {
|
|
||||||
newEntity[TableConstants.EntityKeyNames.RowKey] = originalDocument[TableConstants.EntityKeyNames.RowKey];
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully updated row ${newEntity.RowKey._}`
|
|
||||||
);
|
|
||||||
deferred.resolve(newEntity);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
handleError(error, "UpdateRowCassandra", `Failed to update row ${newEntity.RowKey._}`);
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
if (isPropertyDeleted) {
|
||||||
});
|
deleteQuery = deleteQuery.slice(0, deleteQuery.length - 1);
|
||||||
return deferred.promise;
|
deleteQuery += ` FROM ${collection.databaseId}.${collection.id()}${whereSegment}`;
|
||||||
|
await this.queryDocuments(collection, deleteQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntity[TableConstants.EntityKeyNames.RowKey] = originalDocument[TableConstants.EntityKeyNames.RowKey];
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(`Successfully updated row ${newEntity.RowKey._}`);
|
||||||
|
return newEntity;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "UpdateRowCassandra", "Failed to update row ${newEntity.RowKey._}");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public queryDocuments(
|
public async queryDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
query: string,
|
query: string,
|
||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string
|
paginationToken?: string
|
||||||
): Q.Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
let notificationId: string;
|
const clearMessage =
|
||||||
if (shouldNotify) {
|
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
||||||
notificationId = NotificationConsoleUtils.logConsoleMessage(
|
try {
|
||||||
ConsoleDataType.InProgress,
|
const authType = window.authType;
|
||||||
`Querying rows for table ${collection.id()}`
|
const apiEndpoint: string =
|
||||||
);
|
authType === AuthType.EncryptedToken
|
||||||
}
|
? Constants.CassandraBackend.guestQueryApi
|
||||||
const deferred = Q.defer<Entities.IListTableEntitiesResult>();
|
: Constants.CassandraBackend.queryApi;
|
||||||
const authType = window.authType;
|
const data: any = await $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
||||||
const apiEndpoint: string =
|
type: "POST",
|
||||||
authType === AuthType.EncryptedToken
|
data: {
|
||||||
? Constants.CassandraBackend.guestQueryApi
|
accountName:
|
||||||
: Constants.CassandraBackend.queryApi;
|
collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
||||||
$.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
cassandraEndpoint: this.trimCassandraEndpoint(
|
||||||
type: "POST",
|
collection.container.databaseAccount().properties.cassandraEndpoint
|
||||||
data: {
|
),
|
||||||
accountName: collection && collection.container.databaseAccount && collection.container.databaseAccount().name,
|
resourceId: collection.container.databaseAccount().id,
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(
|
keyspaceId: collection.databaseId,
|
||||||
collection.container.databaseAccount().properties.cassandraEndpoint
|
tableId: collection.id(),
|
||||||
),
|
query,
|
||||||
resourceId: collection.container.databaseAccount().id,
|
paginationToken
|
||||||
keyspaceId: collection.databaseId,
|
|
||||||
tableId: collection.id(),
|
|
||||||
query: query,
|
|
||||||
paginationToken: paginationToken
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader,
|
|
||||||
error: this.handleAjaxError,
|
|
||||||
cache: false
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(data: any) => {
|
|
||||||
if (shouldNotify) {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Info,
|
|
||||||
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
deferred.resolve({
|
|
||||||
Results: data.result,
|
|
||||||
ContinuationToken: data.paginationToken
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
beforeSend: this.setAuthorizationHeader,
|
||||||
if (shouldNotify) {
|
error: this.handleAjaxError,
|
||||||
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
cache: false
|
||||||
}
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.done(() => {
|
|
||||||
if (shouldNotify) {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return deferred.promise;
|
shouldNotify &&
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(
|
||||||
|
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
Results: data.result,
|
||||||
|
ContinuationToken: data.paginationToken
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
shouldNotify &&
|
||||||
|
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage?.();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteDocuments(collection: ViewModels.Collection, entitiesToDelete: Entities.ITableEntity[]): Q.Promise<any> {
|
public async deleteDocuments(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
entitiesToDelete: Entities.ITableEntity[]
|
||||||
|
): Promise<any> {
|
||||||
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
|
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
|
||||||
let promiseArray: Q.Promise<any>[] = [];
|
const partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
|
||||||
let partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
|
|
||||||
for (let i = 0, len = entitiesToDelete.length; i < len; i++) {
|
await Promise.all(
|
||||||
let currEntityToDelete: Entities.ITableEntity = entitiesToDelete[i];
|
entitiesToDelete.map(async (currEntityToDelete: Entities.ITableEntity) => {
|
||||||
let currQuery = query;
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting row ${currEntityToDelete.RowKey._}`);
|
||||||
let partitionKeyValue = currEntityToDelete[partitionKeyProperty];
|
const partitionKeyValue = currEntityToDelete[partitionKeyProperty];
|
||||||
if (partitionKeyValue._ != null && this.isStringType(partitionKeyValue.$)) {
|
const currQuery =
|
||||||
currQuery = `${currQuery}${partitionKeyProperty} = '${partitionKeyValue._}' AND `;
|
query + this.isStringType(partitionKeyValue.$)
|
||||||
} else {
|
? `${partitionKeyProperty} = '${partitionKeyValue._}'`
|
||||||
currQuery = `${currQuery}${partitionKeyProperty} = ${partitionKeyValue._} AND `;
|
: `${partitionKeyProperty} = ${partitionKeyValue._}`;
|
||||||
}
|
|
||||||
currQuery = currQuery.slice(0, currQuery.length - 5);
|
try {
|
||||||
const notificationId = NotificationConsoleUtils.logConsoleMessage(
|
await this.queryDocuments(collection, currQuery);
|
||||||
ConsoleDataType.InProgress,
|
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted row ${currEntityToDelete.RowKey._}`);
|
||||||
`Deleting row ${currEntityToDelete.RowKey._}`
|
} catch (error) {
|
||||||
);
|
handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
|
||||||
promiseArray.push(
|
throw error;
|
||||||
this.queryDocuments(collection, currQuery)
|
} finally {
|
||||||
.then(
|
clearMessage();
|
||||||
() => {
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
})
|
||||||
ConsoleDataType.Info,
|
);
|
||||||
`Successfully deleted row ${currEntityToDelete.RowKey._}`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Q.all(promiseArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public createKeyspace(
|
public createKeyspace(
|
||||||
|
|||||||
@@ -16,18 +16,16 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import DeleteIcon from "../../../images/delete.svg";
|
import DeleteIcon from "../../../images/delete.svg";
|
||||||
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
|
import { QueryIterator, Resource, ConflictDefinition, FeedOptions } from "@azure/cosmos";
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import {
|
|
||||||
queryConflicts,
|
|
||||||
deleteConflict,
|
|
||||||
deleteDocument,
|
|
||||||
createDocument,
|
|
||||||
updateDocument
|
|
||||||
} from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
import { deleteConflict } from "../../Common/dataAccess/deleteConflict";
|
||||||
|
import { queryConflicts } from "../../Common/dataAccess/queryConflicts";
|
||||||
|
|
||||||
export default class ConflictsTab extends TabsBase {
|
export default class ConflictsTab extends TabsBase {
|
||||||
public selectedConflictId: ko.Observable<ConflictId>;
|
public selectedConflictId: ko.Observable<ConflictId>;
|
||||||
@@ -225,25 +223,15 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
public async refreshDocumentsGrid(): Promise<void> {
|
||||||
// clear documents grid
|
try {
|
||||||
this.conflictIds([]);
|
// clear documents grid
|
||||||
return this.createIterator()
|
this.conflictIds([]);
|
||||||
.then(
|
this._documentsIterator = this.createIterator();
|
||||||
// reset iterator
|
await this.loadNextPage();
|
||||||
iterator => {
|
} catch (error) {
|
||||||
this._documentsIterator = iterator;
|
window.alert(getErrorMessage(error));
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.then(
|
|
||||||
// load documents
|
|
||||||
() => {
|
|
||||||
return this.loadNextPage();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch(error => {
|
|
||||||
window.alert(getErrorMessage(error));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@@ -265,9 +253,9 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAcceptChangesClick = (): Q.Promise<any> => {
|
public onAcceptChangesClick = async (): Promise<void> => {
|
||||||
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
|
||||||
return Q();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
@@ -285,81 +273,79 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
conflictResourceId: selectedConflict.resourceId
|
conflictResourceId: selectedConflict.resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
let operationPromise: Q.Promise<any> = Q();
|
try {
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
|
if (selectedConflict.operationType === Constants.ConflictOperationType.Replace) {
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = updateDocument(
|
await updateDocument(
|
||||||
this.collection,
|
this.collection,
|
||||||
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty]),
|
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty]),
|
||||||
documentContent
|
documentContent
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Create) {
|
if (selectedConflict.operationType === Constants.ConflictOperationType.Create) {
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = createDocument(this.collection, documentContent);
|
await createDocument(this.collection, documentContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedConflict.operationType === Constants.ConflictOperationType.Delete && !!this.selectedConflictContent()) {
|
if (
|
||||||
const documentContent = JSON.parse(this.selectedConflictContent());
|
selectedConflict.operationType === Constants.ConflictOperationType.Delete &&
|
||||||
|
!!this.selectedConflictContent()
|
||||||
|
) {
|
||||||
|
const documentContent = JSON.parse(this.selectedConflictContent());
|
||||||
|
|
||||||
operationPromise = deleteDocument(
|
await deleteDocument(
|
||||||
this.collection,
|
this.collection,
|
||||||
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty])
|
selectedConflict.buildDocumentIdFromConflict(documentContent[selectedConflict.partitionKeyProperty])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return operationPromise
|
await deleteConflict(this.collection, selectedConflict);
|
||||||
.then(
|
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
||||||
() => {
|
this.selectedConflictContent("");
|
||||||
return deleteConflict(this.collection, selectedConflict).then(() => {
|
this.selectedConflictCurrent("");
|
||||||
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
this.selectedConflictId(null);
|
||||||
this.selectedConflictContent("");
|
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||||
this.selectedConflictCurrent("");
|
TelemetryProcessor.traceSuccess(
|
||||||
this.selectedConflictId(null);
|
Action.ResolveConflict,
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
{
|
||||||
TelemetryProcessor.traceSuccess(
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
Action.ResolveConflict,
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
{
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
tabTitle: this.tabTitle(),
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
tabTitle: this.tabTitle(),
|
conflictResourceId: selectedConflict.resourceId
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
|
||||||
conflictOperationType: selectedConflict.operationType,
|
|
||||||
conflictResourceId: selectedConflict.resourceId
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
error => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
window.alert(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.ResolveConflict,
|
window.alert(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.ResolveConflict,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
tabTitle: this.tabTitle(),
|
||||||
conflictResourceId: selectedConflict.resourceId,
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
error: errorMessage,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
errorStack: getErrorStack(error)
|
conflictResourceId: selectedConflict.resourceId,
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
}
|
startKey
|
||||||
)
|
);
|
||||||
.finally(() => this.isExecuting(false));
|
} finally {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDeleteClick = (): Q.Promise<any> => {
|
public onDeleteClick = async (): Promise<void> => {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
|
|
||||||
@@ -375,50 +361,48 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
conflictResourceId: selectedConflict.resourceId
|
conflictResourceId: selectedConflict.resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return deleteConflict(this.collection, selectedConflict)
|
try {
|
||||||
.then(
|
await deleteConflict(this.collection, selectedConflict);
|
||||||
() => {
|
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
||||||
this.conflictIds.remove((conflictId: ConflictId) => conflictId.rid === selectedConflict.rid);
|
this.selectedConflictContent("");
|
||||||
this.selectedConflictContent("");
|
this.selectedConflictCurrent("");
|
||||||
this.selectedConflictCurrent("");
|
this.selectedConflictId(null);
|
||||||
this.selectedConflictId(null);
|
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
TelemetryProcessor.traceSuccess(
|
||||||
TelemetryProcessor.traceSuccess(
|
Action.DeleteConflict,
|
||||||
Action.DeleteConflict,
|
{
|
||||||
{
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
conflictResourceId: selectedConflict.resourceId
|
||||||
conflictResourceId: selectedConflict.resourceId
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
error => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
window.alert(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.DeleteConflict,
|
window.alert(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.DeleteConflict,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
conflictResourceType: selectedConflict.resourceType,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
conflictOperationType: selectedConflict.operationType,
|
tabTitle: this.tabTitle(),
|
||||||
conflictResourceId: selectedConflict.resourceId,
|
conflictResourceType: selectedConflict.resourceType,
|
||||||
error: errorMessage,
|
conflictOperationType: selectedConflict.operationType,
|
||||||
errorStack: getErrorStack(error)
|
conflictResourceId: selectedConflict.resourceId,
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
}
|
startKey
|
||||||
)
|
);
|
||||||
.finally(() => this.isExecuting(false));
|
} finally {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDiscardClick = (): Q.Promise<any> => {
|
public onDiscardClick = (): Q.Promise<any> => {
|
||||||
@@ -445,60 +429,47 @@ export default class ConflictsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
if (this._documentsIterator) {
|
|
||||||
return Q.resolve(this._documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createIterator().then(
|
if (!this._documentsIterator) {
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
try {
|
||||||
this._documentsIterator = iterator;
|
this._documentsIterator = await this.createIterator();
|
||||||
return this.loadNextPage();
|
await this.loadNextPage();
|
||||||
},
|
} catch (error) {
|
||||||
error => {
|
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
TelemetryProcessor.traceFailure(
|
||||||
TelemetryProcessor.traceFailure(
|
Action.Tab,
|
||||||
Action.Tab,
|
{
|
||||||
{
|
databaseAccountName: this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection.container.databaseAccount().name,
|
databaseName: this.collection.databaseId,
|
||||||
databaseName: this.collection.databaseId,
|
collectionName: this.collection.id(),
|
||||||
collectionName: this.collection.id(),
|
defaultExperience: this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
error: getErrorMessage(error),
|
||||||
error: getErrorMessage(error),
|
errorStack: getErrorStack(error)
|
||||||
errorStack: getErrorStack(error)
|
},
|
||||||
},
|
this.onLoadStartKey
|
||||||
this.onLoadStartKey
|
);
|
||||||
);
|
this.onLoadStartKey = null;
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshClick(): Q.Promise<any> {
|
public createIterator(): QueryIterator<ConflictDefinition & Resource> {
|
||||||
return this.refreshDocumentsGrid().then(() => {
|
|
||||||
this.selectedConflictContent("");
|
|
||||||
this.selectedConflictId(null);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public createIterator(): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
|
||||||
// TODO: Conflict Feed does not allow filtering atm
|
// TODO: Conflict Feed does not allow filtering atm
|
||||||
const query: string = undefined;
|
const query: string = undefined;
|
||||||
let options: any = {};
|
const options = {
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
enableCrossPartitionQuery: HeadersUtility.shouldEnableCrossPartitionKey()
|
||||||
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options);
|
};
|
||||||
|
return queryConflicts(this.collection.databaseId, this.collection.id(), query, options as FeedOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
|
|||||||
@@ -230,9 +230,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
return this.throughputTitle() + this.requestUnitsUsageCost();
|
return this.throughputTitle() + this.requestUnitsUsageCost();
|
||||||
});
|
});
|
||||||
this.pendingNotification = ko.observable<DataModels.Notification>();
|
this.pendingNotification = ko.observable<DataModels.Notification>();
|
||||||
this._offerReplacePending = ko.observable<boolean>(
|
this._offerReplacePending = ko.observable<boolean>(!!this.database.offer()?.offerReplacePending);
|
||||||
!!this.database.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending]
|
|
||||||
);
|
|
||||||
this.notificationStatusInfo = ko.observable<string>("");
|
this.notificationStatusInfo = ko.observable<string>("");
|
||||||
this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0);
|
this.shouldShowNotificationStatusPrompt = ko.computed<boolean>(() => this.notificationStatusInfo().length > 0);
|
||||||
this.warningMessage = ko.computed<string>(() => {
|
this.warningMessage = ko.computed<string>(() => {
|
||||||
@@ -241,7 +239,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.database.offer();
|
const offer = this.database.offer();
|
||||||
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) {
|
if (offer?.offerReplacePending) {
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
||||||
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
|
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
|
||||||
}
|
}
|
||||||
@@ -431,11 +429,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(async () => {
|
super.onActivate();
|
||||||
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||||
await this.database.loadOffer();
|
await this.database.loadOffer();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setBaseline() {
|
private _setBaseline() {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<button
|
<button
|
||||||
class="filterbtnstyle queryButton"
|
class="filterbtnstyle queryButton"
|
||||||
data-bind="
|
data-bind="
|
||||||
click: onApplyFilterClick,
|
click: refreshDocumentsGrid,
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
@@ -19,19 +19,24 @@ import SaveIcon from "../../../images/save-cosmos.svg";
|
|||||||
import DiscardIcon from "../../../images/discard.svg";
|
import DiscardIcon from "../../../images/discard.svg";
|
||||||
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
import DeleteDocumentIcon from "../../../images/DeleteDocument.svg";
|
||||||
import UploadIcon from "../../../images/Upload_16x16.svg";
|
import UploadIcon from "../../../images/Upload_16x16.svg";
|
||||||
import { extractPartitionKey, PartitionKeyDefinition, QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import {
|
||||||
|
extractPartitionKey,
|
||||||
|
PartitionKeyDefinition,
|
||||||
|
QueryIterator,
|
||||||
|
ItemDefinition,
|
||||||
|
Resource,
|
||||||
|
Item
|
||||||
|
} from "@azure/cosmos";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import {
|
|
||||||
readDocument,
|
|
||||||
queryDocuments,
|
|
||||||
deleteDocument,
|
|
||||||
updateDocument,
|
|
||||||
createDocument
|
|
||||||
} from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { readDocument } from "../../Common/dataAccess/readDocument";
|
||||||
|
import { deleteDocument } from "../../Common/dataAccess/deleteDocument";
|
||||||
|
import { updateDocument } from "../../Common/dataAccess/updateDocument";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
|
||||||
export default class DocumentsTab extends TabsBase {
|
export default class DocumentsTab extends TabsBase {
|
||||||
public selectedDocumentId: ko.Observable<DocumentId>;
|
public selectedDocumentId: ko.Observable<DocumentId>;
|
||||||
@@ -369,36 +374,22 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onApplyFilterClick(): Q.Promise<any> {
|
public async refreshDocumentsGrid(): Promise<void> {
|
||||||
// clear documents grid
|
// clear documents grid
|
||||||
this.documentIds([]);
|
this.documentIds([]);
|
||||||
return this.createIterator()
|
|
||||||
.then(
|
|
||||||
// reset iterator
|
|
||||||
iterator => {
|
|
||||||
this._documentsIterator = iterator;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
// load documents
|
|
||||||
() => {
|
|
||||||
return this.loadNextPage();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
// collapse filter
|
|
||||||
this.appliedFilter(this.filterContent());
|
|
||||||
this.isFilterExpanded(false);
|
|
||||||
const focusElement = document.getElementById("errorStatusIcon");
|
|
||||||
focusElement && focusElement.focus();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
window.alert(getErrorMessage(error));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public refreshDocumentsGrid(): Q.Promise<any> {
|
try {
|
||||||
return this.onApplyFilterClick();
|
// reset iterator
|
||||||
|
this._documentsIterator = this.createIterator();
|
||||||
|
// load documents
|
||||||
|
await this.loadNextPage();
|
||||||
|
// collapse filter
|
||||||
|
this.appliedFilter(this.filterContent());
|
||||||
|
this.isFilterExpanded(false);
|
||||||
|
document.getElementById("errorStatusIcon")?.focus();
|
||||||
|
} catch (error) {
|
||||||
|
window.alert(getErrorMessage(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
public onRefreshButtonKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||||
@@ -434,7 +425,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveNewDocumentClick = (): Q.Promise<any> => {
|
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
@@ -502,7 +493,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveExisitingDocumentClick = (): Q.Promise<any> => {
|
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||||
|
|
||||||
@@ -571,17 +562,15 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDeleteExisitingDocumentClick = (): Q.Promise<any> => {
|
public onDeleteExisitingDocumentClick = async (): Promise<void> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const msg = !this.isPreferredApiMongoDB
|
const msg = !this.isPreferredApiMongoDB
|
||||||
? "Are you sure you want to delete the selected item ?"
|
? "Are you sure you want to delete the selected item ?"
|
||||||
: "Are you sure you want to delete the selected document ?";
|
: "Are you sure you want to delete the selected document ?";
|
||||||
|
|
||||||
if (window.confirm(msg)) {
|
if (window.confirm(msg)) {
|
||||||
return this._deleteDocument(selectedDocumentId);
|
await this._deleteDocument(selectedDocumentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public onValidDocumentEdit(): Q.Promise<any> {
|
public onValidDocumentEdit(): Q.Promise<any> {
|
||||||
@@ -617,63 +606,50 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public async onActivate(): Promise<void> {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
if (this._documentsIterator) {
|
|
||||||
return Q.resolve(this._documentsIterator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createIterator().then(
|
if (!this._documentsIterator) {
|
||||||
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
try {
|
||||||
this._documentsIterator = iterator;
|
this._documentsIterator = this.createIterator();
|
||||||
return this.loadNextPage();
|
await this.loadNextPage();
|
||||||
},
|
} catch (error) {
|
||||||
error => {
|
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
TelemetryProcessor.traceFailure(
|
||||||
TelemetryProcessor.traceFailure(
|
Action.Tab,
|
||||||
Action.Tab,
|
{
|
||||||
{
|
databaseAccountName: this.collection.container.databaseAccount().name,
|
||||||
databaseAccountName: this.collection.container.databaseAccount().name,
|
databaseName: this.collection.databaseId,
|
||||||
databaseName: this.collection.databaseId,
|
collectionName: this.collection.id(),
|
||||||
collectionName: this.collection.id(),
|
defaultExperience: this.collection.container.defaultExperience(),
|
||||||
defaultExperience: this.collection.container.defaultExperience(),
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
tabTitle: this.tabTitle(),
|
||||||
tabTitle: this.tabTitle(),
|
error: getErrorMessage(error),
|
||||||
error: getErrorMessage(error),
|
errorStack: getErrorStack(error)
|
||||||
errorStack: getErrorStack(error)
|
},
|
||||||
},
|
this.onLoadStartKey
|
||||||
this.onLoadStartKey
|
);
|
||||||
);
|
this.onLoadStartKey = null;
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRefreshClick(): Q.Promise<any> {
|
|
||||||
return this.refreshDocumentsGrid().then(() => {
|
|
||||||
this.selectedDocumentContent("");
|
|
||||||
this.selectedDocumentId(null);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private _isIgnoreDirtyEditor = (): boolean => {
|
private _isIgnoreDirtyEditor = (): boolean => {
|
||||||
var msg: string = "Changes will be lost. Do you want to continue?";
|
var msg: string = "Changes will be lost. Do you want to continue?";
|
||||||
return window.confirm(msg);
|
return window.confirm(msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> {
|
protected __deleteDocument(documentId: DocumentId): Promise<void> {
|
||||||
return deleteDocument(this.collection, documentId);
|
return deleteDocument(this.collection, documentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deleteDocument(selectedDocumentId: DocumentId): Q.Promise<any> {
|
private _deleteDocument(selectedDocumentId: DocumentId): Promise<void> {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDocument, {
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
@@ -684,7 +660,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return this.__deleteDocument(selectedDocumentId)
|
return this.__deleteDocument(selectedDocumentId)
|
||||||
.then(
|
.then(
|
||||||
(result: any) => {
|
() => {
|
||||||
this.documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
|
this.documentIds.remove((documentId: DocumentId) => documentId.rid === selectedDocumentId.rid);
|
||||||
this.selectedDocumentContent("");
|
this.selectedDocumentContent("");
|
||||||
this.selectedDocumentId(null);
|
this.selectedDocumentId(null);
|
||||||
@@ -720,7 +696,7 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
.finally(() => this.isExecuting(false));
|
.finally(() => this.isExecuting(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public createIterator(): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
public createIterator(): QueryIterator<ItemDefinition & Resource> {
|
||||||
let filters = this.lastFilterContents();
|
let filters = this.lastFilterContents();
|
||||||
const filter: string = this.filterContent().trim();
|
const filter: string = this.filterContent().trim();
|
||||||
const query: string = this.buildQuery(filter);
|
const query: string = this.buildQuery(filter);
|
||||||
@@ -734,11 +710,10 @@ export default class DocumentsTab extends TabsBase {
|
|||||||
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
|
return queryDocuments(this.collection.databaseId, this.collection.id(), query, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectDocument(documentId: DocumentId): Q.Promise<any> {
|
public async selectDocument(documentId: DocumentId): Promise<void> {
|
||||||
this.selectedDocumentId(documentId);
|
this.selectedDocumentId(documentId);
|
||||||
return readDocument(this.collection, documentId).then((content: any) => {
|
const content = await readDocument(this.collection, documentId);
|
||||||
this.initDocumentEditor(documentId, content);
|
this.initDocumentEditor(documentId, content);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
|
|||||||
@@ -114,10 +114,9 @@ export default class GraphTab extends TabsBase {
|
|||||||
: `${account.name}.graphs.azure.com:443/`;
|
: `${account.name}.graphs.azure.com:443/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -289,7 +289,7 @@
|
|||||||
<button
|
<button
|
||||||
class="filterbtnstyle queryButton"
|
class="filterbtnstyle queryButton"
|
||||||
data-bind="
|
data-bind="
|
||||||
click: onApplyFilterClick,
|
click: refreshDocumentsGrid,
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
>
|
>
|
||||||
Apply Filter
|
Apply Filter
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
super.buildCommandBarOptions();
|
super.buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSaveNewDocumentClick = (): Q.Promise<any> => {
|
public onSaveNewDocumentClick = (): Promise<any> => {
|
||||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
const documentContent = JSON.parse(this.selectedDocumentContent());
|
||||||
this.displayedError("");
|
this.displayedError("");
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
||||||
@@ -78,12 +78,12 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab");
|
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab");
|
||||||
return Q.reject("Document without shard key");
|
throw new Error("Document without shard key");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return Q(createDocument(this.collection.databaseId, this.collection, this.partitionKeyProperty, documentContent))
|
return createDocument(this.collection.databaseId, this.collection, this.partitionKeyProperty, documentContent)
|
||||||
.then(
|
.then(
|
||||||
(savedDocument: any) => {
|
(savedDocument: any) => {
|
||||||
let partitionKeyArray = extractPartitionKey(
|
let partitionKeyArray = extractPartitionKey(
|
||||||
@@ -136,7 +136,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
.finally(() => this.isExecuting(false));
|
.finally(() => this.isExecuting(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveExisitingDocumentClick = (): Q.Promise<any> => {
|
public onSaveExisitingDocumentClick = (): Promise<any> => {
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
const selectedDocumentId = this.selectedDocumentId();
|
||||||
const documentContent = this.selectedDocumentContent();
|
const documentContent = this.selectedDocumentContent();
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
@@ -148,7 +148,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
tabTitle: this.tabTitle()
|
tabTitle: this.tabTitle()
|
||||||
});
|
});
|
||||||
|
|
||||||
return Q(updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent))
|
return updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent)
|
||||||
.then(
|
.then(
|
||||||
(updatedDocument: any) => {
|
(updatedDocument: any) => {
|
||||||
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
||||||
@@ -204,13 +204,10 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
return filter || "{}";
|
return filter || "{}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectDocument(documentId: DocumentId): Q.Promise<any> {
|
public async selectDocument(documentId: DocumentId): Promise<void> {
|
||||||
this.selectedDocumentId(documentId);
|
this.selectedDocumentId(documentId);
|
||||||
return Q(
|
const content = await readDocument(this.collection.databaseId, this.collection, documentId);
|
||||||
readDocument(this.collection.databaseId, this.collection, documentId).then((content: any) => {
|
this.initDocumentEditor(documentId, content);
|
||||||
this.initDocumentEditor(documentId, content);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
public loadNextPage(): Q.Promise<any> {
|
||||||
@@ -330,7 +327,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
|
|||||||
return partitionKey;
|
return partitionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected __deleteDocument(documentId: DocumentId): Q.Promise<any> {
|
protected __deleteDocument(documentId: DocumentId): Promise<void> {
|
||||||
return Q(deleteDocument(this.collection.databaseId, this.collection, documentId));
|
return deleteDocument(this.collection.databaseId, this.collection, documentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,9 @@ export default class MongoShellTab extends TabsBase {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleMessage(event: MessageEvent) {
|
public handleMessage(event: MessageEvent) {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<json-editor
|
<json-editor
|
||||||
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
params="{ content: queryResults, isReadOnly: true, ariaLabel: 'Query results' }"
|
||||||
data-bind="visible: queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
data-bind="visible: queryResults() && queryResults().length > 0 && isResultToggled() && allResultsMetadata().length > 0 && !error()"
|
||||||
>
|
>
|
||||||
</json-editor>
|
</json-editor>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import { QueryUtils } from "../../Utils/QueryUtils";
|
|||||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||||
|
|
||||||
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
|
||||||
import { queryDocuments, queryDocumentsPage } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { queryDocuments } from "../../Common/dataAccess/queryDocuments";
|
||||||
|
import { queryDocumentsPage } from "../../Common/dataAccess/queryDocumentsPage";
|
||||||
|
|
||||||
enum ToggleState {
|
enum ToggleState {
|
||||||
Result,
|
Result,
|
||||||
@@ -163,20 +164,19 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this._buildCommandBarOptions();
|
this._buildCommandBarOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
this.collection && this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Query);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onExecuteQueryClick = (): Q.Promise<any> => {
|
public onExecuteQueryClick = async (): Promise<void> => {
|
||||||
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
const sqlStatement: string = this.selectedContent() || this.sqlQueryEditorContent();
|
||||||
this.sqlStatementToExecute(sqlStatement);
|
this.sqlStatementToExecute(sqlStatement);
|
||||||
this.allResultsMetadata([]);
|
this.allResultsMetadata([]);
|
||||||
this.queryResults("");
|
this.queryResults("");
|
||||||
this._iterator = null;
|
this._iterator = undefined;
|
||||||
|
|
||||||
return this._executeQueryDocumentsPage(0);
|
await this._executeQueryDocumentsPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
public onLoadQueryClick = (): void => {
|
public onLoadQueryClick = (): void => {
|
||||||
@@ -191,13 +191,13 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
this.collection && this.collection.container && this.collection.container.browseQueriesPane.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
public onFetchNextPageClick(): Q.Promise<any> {
|
public async onFetchNextPageClick(): Promise<void> {
|
||||||
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
||||||
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
||||||
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
const firstResultIndex: number = (metadata && Number(metadata.firstItemIndex)) || 1;
|
||||||
const itemCount: number = (metadata && Number(metadata.itemCount)) || 0;
|
const itemCount: number = (metadata && Number(metadata.itemCount)) || 0;
|
||||||
|
|
||||||
return this._executeQueryDocumentsPage(firstResultIndex + itemCount - 1);
|
await this._executeQueryDocumentsPage(firstResultIndex + itemCount - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
||||||
@@ -265,19 +265,18 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
private _executeQueryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
private async _executeQueryDocumentsPage(firstItemIndex: number): Promise<any> {
|
||||||
this.error("");
|
this.error("");
|
||||||
this.roundTrips(undefined);
|
this.roundTrips(undefined);
|
||||||
if (this._iterator == null) {
|
if (this._iterator === undefined) {
|
||||||
const queryIteratorPromise = this._initIterator();
|
this._initIterator();
|
||||||
return queryIteratorPromise.finally(() => this._queryDocumentsPage(firstItemIndex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._queryDocumentsPage(firstItemIndex);
|
await this._queryDocumentsPage(firstItemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Position and enable spinner when request is in progress
|
// TODO: Position and enable spinner when request is in progress
|
||||||
private _queryDocumentsPage(firstItemIndex: number): Q.Promise<any> {
|
private async _queryDocumentsPage(firstItemIndex: number): Promise<void> {
|
||||||
this.isExecutionError(false);
|
this.isExecutionError(false);
|
||||||
this._resetAggregateQueryMetrics();
|
this._resetAggregateQueryMetrics();
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.ExecuteQuery, {
|
||||||
@@ -289,90 +288,90 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
let options: any = {};
|
let options: any = {};
|
||||||
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
options.enableCrossPartitionQuery = HeadersUtility.shouldEnableCrossPartitionKey();
|
||||||
|
|
||||||
const queryDocuments = (firstItemIndex: number) =>
|
const queryDocuments = async (firstItemIndex: number) =>
|
||||||
queryDocumentsPage(this.collection && this.collection.id(), this._iterator, firstItemIndex, options);
|
await queryDocumentsPage(this.collection && this.collection.id(), this._iterator, firstItemIndex);
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
return QueryUtils.queryPagesUntilContentPresent(firstItemIndex, queryDocuments)
|
|
||||||
.then(
|
|
||||||
(queryResults: ViewModels.QueryResults) => {
|
|
||||||
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
|
||||||
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
|
||||||
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
|
||||||
hasMoreResults: queryResults.hasMoreResults,
|
|
||||||
itemCount: queryResults.itemCount,
|
|
||||||
firstItemIndex: queryResults.firstItemIndex,
|
|
||||||
lastItemIndex: queryResults.lastItemIndex
|
|
||||||
};
|
|
||||||
this.allResultsMetadata.push(resultsMetadata);
|
|
||||||
this.activityId(queryResults.activityId);
|
|
||||||
this.roundTrips(queryResults.roundTrips);
|
|
||||||
|
|
||||||
this._updateQueryMetricsMap(queryResults.headers[Constants.HttpHeaders.queryMetrics]);
|
try {
|
||||||
|
const queryResults: ViewModels.QueryResults = await QueryUtils.queryPagesUntilContentPresent(
|
||||||
|
firstItemIndex,
|
||||||
|
queryDocuments
|
||||||
|
);
|
||||||
|
const allResultsMetadata = (this.allResultsMetadata && this.allResultsMetadata()) || [];
|
||||||
|
const metadata: ViewModels.QueryResultsMetadata = allResultsMetadata[allResultsMetadata.length - 1];
|
||||||
|
const resultsMetadata: ViewModels.QueryResultsMetadata = {
|
||||||
|
hasMoreResults: queryResults.hasMoreResults,
|
||||||
|
itemCount: queryResults.itemCount,
|
||||||
|
firstItemIndex: queryResults.firstItemIndex,
|
||||||
|
lastItemIndex: queryResults.lastItemIndex
|
||||||
|
};
|
||||||
|
this.allResultsMetadata.push(resultsMetadata);
|
||||||
|
this.activityId(queryResults.activityId);
|
||||||
|
this.roundTrips(queryResults.roundTrips);
|
||||||
|
|
||||||
if (queryResults.itemCount == 0 && metadata != null && metadata.itemCount >= 0) {
|
this._updateQueryMetricsMap(queryResults.headers[Constants.HttpHeaders.queryMetrics]);
|
||||||
// we let users query for the next page because the SDK sometimes specifies there are more elements
|
|
||||||
// even though there aren't any so we should not update the prior query results.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const documents: any[] = queryResults.documents;
|
if (queryResults.itemCount == 0 && metadata != null && metadata.itemCount >= 0) {
|
||||||
const results = this.renderObjectForEditor(documents, null, 4);
|
// we let users query for the next page because the SDK sometimes specifies there are more elements
|
||||||
|
// even though there aren't any so we should not update the prior query results.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const resultsDisplay: string =
|
const documents: any[] = queryResults.documents;
|
||||||
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
|
const results = this.renderObjectForEditor(documents, null, 4);
|
||||||
this.showingDocumentsDisplayText(resultsDisplay);
|
|
||||||
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
|
|
||||||
|
|
||||||
if (!this.queryResults() && !results) {
|
const resultsDisplay: string =
|
||||||
const errorMessage: string = JSON.stringify({
|
queryResults.itemCount > 0 ? `${queryResults.firstItemIndex} - ${queryResults.lastItemIndex}` : `0 - 0`;
|
||||||
error: `Returned no results after query execution`,
|
this.showingDocumentsDisplayText(resultsDisplay);
|
||||||
accountName: this.collection && this.collection.container.databaseAccount(),
|
this.requestChargeDisplayText(`${queryResults.requestCharge} RUs`);
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
sqlQuery: this.sqlStatementToExecute(),
|
|
||||||
hasMoreResults: resultsMetadata.hasMoreResults,
|
|
||||||
itemCount: resultsMetadata.itemCount,
|
|
||||||
responseHeaders: queryResults && queryResults.headers
|
|
||||||
});
|
|
||||||
Logger.logError(errorMessage, "QueryTab");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queryResults(results);
|
if (!this.queryResults() && !results) {
|
||||||
|
const errorMessage: string = JSON.stringify({
|
||||||
|
error: `Returned no results after query execution`,
|
||||||
|
accountName: this.collection && this.collection.container.databaseAccount(),
|
||||||
|
databaseName: this.collection && this.collection.databaseId,
|
||||||
|
collectionName: this.collection && this.collection.id(),
|
||||||
|
sqlQuery: this.sqlStatementToExecute(),
|
||||||
|
hasMoreResults: resultsMetadata.hasMoreResults,
|
||||||
|
itemCount: resultsMetadata.itemCount,
|
||||||
|
responseHeaders: queryResults && queryResults.headers
|
||||||
|
});
|
||||||
|
Logger.logError(errorMessage, "QueryTab");
|
||||||
|
}
|
||||||
|
|
||||||
TelemetryProcessor.traceSuccess(
|
this.queryResults(results);
|
||||||
Action.ExecuteQuery,
|
|
||||||
{
|
TelemetryProcessor.traceSuccess(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.ExecuteQuery,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle()
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
},
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
startKey
|
tabTitle: this.tabTitle()
|
||||||
);
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
startKey
|
||||||
this.isExecutionError(true);
|
);
|
||||||
const errorMessage = getErrorMessage(error);
|
} catch (error) {
|
||||||
this.error(errorMessage);
|
this.isExecutionError(true);
|
||||||
TelemetryProcessor.traceFailure(
|
const errorMessage = getErrorMessage(error);
|
||||||
Action.ExecuteQuery,
|
this.error(errorMessage);
|
||||||
{
|
TelemetryProcessor.traceFailure(
|
||||||
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
Action.ExecuteQuery,
|
||||||
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
{
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
|
||||||
tabTitle: this.tabTitle(),
|
defaultExperience: this.collection && this.collection.container.defaultExperience(),
|
||||||
error: errorMessage,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
errorStack: getErrorStack(error)
|
tabTitle: this.tabTitle(),
|
||||||
},
|
error: errorMessage,
|
||||||
startKey
|
errorStack: getErrorStack(error)
|
||||||
);
|
},
|
||||||
document.getElementById("error-display").focus();
|
startKey
|
||||||
}
|
);
|
||||||
)
|
document.getElementById("error-display").focus();
|
||||||
.finally(() => {
|
} finally {
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
this.togglesOnFocus();
|
this.togglesOnFocus();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateQueryMetricsMap(metricsMap: { [partitionKeyRange: string]: DataModels.QueryMetrics }): void {
|
private _updateQueryMetricsMap(metricsMap: { [partitionKeyRange: string]: DataModels.QueryMetrics }): void {
|
||||||
@@ -477,16 +476,17 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _initIterator(): Q.Promise<MinimalQueryIterator> {
|
protected _initIterator(): void {
|
||||||
const options: any = QueryTab.getIteratorOptions(this.collection);
|
const options: any = QueryTab.getIteratorOptions(this.collection);
|
||||||
if (this._resourceTokenPartitionKey) {
|
if (this._resourceTokenPartitionKey) {
|
||||||
options.partitionKey = this._resourceTokenPartitionKey;
|
options.partitionKey = this._resourceTokenPartitionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Q(
|
this._iterator = queryDocuments(
|
||||||
queryDocuments(this.collection.databaseId, this.collection.id(), this.sqlStatementToExecute(), options).then(
|
this.collection.databaseId,
|
||||||
iterator => (this._iterator = iterator)
|
this.collection.id(),
|
||||||
)
|
this.sqlStatementToExecute(),
|
||||||
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,17 +161,16 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public onActivate(): void {
|
||||||
return super.onActivate().then(() => {
|
super.onActivate();
|
||||||
const columns =
|
const columns =
|
||||||
!!this.tableEntityListViewModel() &&
|
!!this.tableEntityListViewModel() &&
|
||||||
!!this.tableEntityListViewModel().table &&
|
!!this.tableEntityListViewModel().table &&
|
||||||
this.tableEntityListViewModel().table.columns;
|
this.tableEntityListViewModel().table.columns;
|
||||||
if (!!columns) {
|
if (!!columns) {
|
||||||
columns.adjust();
|
columns.adjust();
|
||||||
$(window).resize();
|
$(window).resize();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||||
|
|||||||
@@ -186,12 +186,11 @@ export default abstract class ScriptTabBase extends TabsBase implements ViewMode
|
|||||||
this._setBaselines();
|
this._setBaselines();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
return super.onTabClick().then(() => {
|
super.onTabClick();
|
||||||
if (this.isNew()) {
|
if (this.isNew()) {
|
||||||
this.collection.selectedSubnodeKind(this.tabKind);
|
this.collection.selectedSubnodeKind(this.tabKind);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract onSaveClick: () => Promise<any>;
|
public abstract onSaveClick: () => Promise<any>;
|
||||||
|
|||||||
@@ -42,54 +42,49 @@ export default class SettingsTabV2 extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onActivate(): Q.Promise<unknown> {
|
public async onActivate(): Promise<void> {
|
||||||
this.isExecuting(true);
|
try {
|
||||||
this.currentCollection.loadOffer().then(
|
this.isExecuting(true);
|
||||||
() => {
|
await this.currentCollection.loadOffer();
|
||||||
// passed in options and set by parent as "Settings" by default
|
// passed in options and set by parent as "Settings" by default
|
||||||
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
||||||
this.offerRead(true);
|
|
||||||
this.options.getPendingNotification.then(
|
|
||||||
(data: DataModels.Notification) => {
|
|
||||||
this.notification = data;
|
|
||||||
this.notificationRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.notification = undefined;
|
|
||||||
this.notificationRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
traceFailure(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
|
||||||
databaseName: this.options.collection.databaseId,
|
|
||||||
collectionName: this.options.collection.id(),
|
|
||||||
defaultExperience: this.options.collection.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle,
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error)
|
|
||||||
},
|
|
||||||
this.options.onLoadStartKey
|
|
||||||
);
|
|
||||||
logConsoleError(
|
|
||||||
`Error while fetching container settings for container ${this.options.collection.id()}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.offerRead(true);
|
|
||||||
this.isExecuting(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return super.onActivate().then(() => {
|
this.options.getPendingNotification.then(
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
(data: DataModels.Notification) => {
|
||||||
});
|
this.notification = data;
|
||||||
|
this.notificationRead(true);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
this.notification = undefined;
|
||||||
|
this.notificationRead(true);
|
||||||
|
traceFailure(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
||||||
|
databaseName: this.options.collection.databaseId,
|
||||||
|
collectionName: this.options.collection.id(),
|
||||||
|
defaultExperience: this.options.collection.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
|
},
|
||||||
|
this.options.onLoadStartKey
|
||||||
|
);
|
||||||
|
logConsoleError(
|
||||||
|
`Error while fetching container settings for container ${this.options.collection.id()}: ${errorMessage}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.offerRead(true);
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivate();
|
||||||
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSettingsTabContainer(): Explorer {
|
public getSettingsTabContainer(): Explorer {
|
||||||
|
|||||||
@@ -94,9 +94,8 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onTabClick(): Q.Promise<any> {
|
public onTabClick(): void {
|
||||||
this.getContainer().tabsManager.activateTab(this);
|
this.getContainer().tabsManager.activateTab(this);
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateSelectedNode(): void {
|
protected updateSelectedNode(): void {
|
||||||
@@ -128,7 +127,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick());
|
||||||
};
|
};
|
||||||
|
|
||||||
public onActivate(): Q.Promise<any> {
|
public onActivate(): void {
|
||||||
this.updateSelectedNode();
|
this.updateSelectedNode();
|
||||||
if (!!this.collection) {
|
if (!!this.collection) {
|
||||||
this.collection.selectedSubnodeKind(this.tabKind);
|
this.collection.selectedSubnodeKind(this.tabKind);
|
||||||
@@ -151,7 +150,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
tabTitle: this.tabTitle(),
|
tabTitle: this.tabTitle(),
|
||||||
tabId: this.tabId
|
tabId: this.tabId
|
||||||
});
|
});
|
||||||
return Q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
public onErrorDetailsClick = (src: any, event: MouseEvent): boolean => {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
<!-- ko if: $data.tabKind === 19 -->
|
<!-- ko if: $data.tabKind === 20 -->
|
||||||
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import * as Constants from "../../Common/Constants";
|
|||||||
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
||||||
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
||||||
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
||||||
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
||||||
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
||||||
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
@@ -39,6 +38,7 @@ import Explorer from "../Explorer";
|
|||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
|
||||||
export default class Collection implements ViewModels.Collection {
|
export default class Collection implements ViewModels.Collection {
|
||||||
public nodeKind: string;
|
public nodeKind: string;
|
||||||
@@ -551,7 +551,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, tab => {
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1091,8 +1091,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createDocumentsFromFile(fileName: string, documentContent: string): Q.Promise<UploadDetailsRecord> {
|
private async _createDocumentsFromFile(fileName: string, documentContent: string): Promise<UploadDetailsRecord> {
|
||||||
const deferred: Q.Deferred<UploadDetailsRecord> = Q.defer();
|
|
||||||
const record: UploadDetailsRecord = {
|
const record: UploadDetailsRecord = {
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
numSucceeded: 0,
|
numSucceeded: 0,
|
||||||
@@ -1102,39 +1101,25 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const content = JSON.parse(documentContent);
|
const content = JSON.parse(documentContent);
|
||||||
const promises: Array<Q.Promise<any>> = [];
|
|
||||||
|
|
||||||
const triggerCreateDocument: (documentContent: any) => Q.Promise<any> = (documentContent: any) => {
|
|
||||||
return createDocument(this, documentContent).then(
|
|
||||||
doc => {
|
|
||||||
record.numSucceeded++;
|
|
||||||
return Q.resolve();
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
record.numFailed++;
|
|
||||||
record.errors = [...record.errors, getErrorMessage(error)];
|
|
||||||
return Q.resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
for (let i = 0; i < content.length; i++) {
|
await Promise.all(
|
||||||
promises.push(triggerCreateDocument(content[i]));
|
content.map(async documentContent => {
|
||||||
}
|
await createDocument(this, documentContent);
|
||||||
|
record.numSucceeded++;
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
promises.push(triggerCreateDocument(content));
|
await createDocument(this, documentContent);
|
||||||
|
record.numSucceeded++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q.all(promises).then(() => {
|
return record;
|
||||||
deferred.resolve(record);
|
} catch (error) {
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
record.numFailed++;
|
record.numFailed++;
|
||||||
record.errors = [...record.errors, e.message];
|
record.errors = [...record.errors, error.message];
|
||||||
deferred.resolve(record);
|
return record;
|
||||||
}
|
}
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { extractPartitionKey } from "@azure/cosmos";
|
import { extractPartitionKey } from "@azure/cosmos";
|
||||||
import ConflictsTab from "../Tabs/ConflictsTab";
|
import ConflictsTab from "../Tabs/ConflictsTab";
|
||||||
import { readDocument } from "../../Common/DocumentClientUtilityBase";
|
import { readDocument } from "../../Common/dataAccess/readDocument";
|
||||||
|
|
||||||
export default class ConflictId {
|
export default class ConflictId {
|
||||||
public container: ConflictsTab;
|
public container: ConflictsTab;
|
||||||
@@ -59,41 +59,42 @@ export default class ConflictId {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadConflict(): Q.Promise<any> {
|
public async loadConflict(): Promise<void> {
|
||||||
const conflictsTab = this.container;
|
|
||||||
this.container.selectedConflictId(this);
|
this.container.selectedConflictId(this);
|
||||||
|
|
||||||
if (this.operationType === Constants.ConflictOperationType.Create) {
|
if (this.operationType === Constants.ConflictOperationType.Create) {
|
||||||
this.container.initDocumentEditorForCreate(this, this.content);
|
this.container.initDocumentEditorForCreate(this, this.content);
|
||||||
return Q();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.loadingConflictData(true);
|
this.container.loadingConflictData(true);
|
||||||
return readDocument(this.container.collection, this.buildDocumentIdFromConflict(this.partitionKeyValue)).then(
|
|
||||||
(currentDocumentContent: any) => {
|
|
||||||
this.container.loadingConflictData(false);
|
|
||||||
if (this.operationType === Constants.ConflictOperationType.Replace) {
|
|
||||||
this.container.initDocumentEditorForReplace(this, this.content, currentDocumentContent);
|
|
||||||
} else {
|
|
||||||
this.container.initDocumentEditorForDelete(this, currentDocumentContent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
this.container.loadingConflictData(false);
|
|
||||||
|
|
||||||
// Document could be deleted
|
try {
|
||||||
if (
|
const currentDocumentContent = await readDocument(
|
||||||
reason &&
|
this.container.collection,
|
||||||
reason.code === Constants.HttpStatusCodes.NotFound &&
|
this.buildDocumentIdFromConflict(this.partitionKeyValue)
|
||||||
this.operationType === Constants.ConflictOperationType.Delete
|
);
|
||||||
) {
|
|
||||||
this.container.initDocumentEditorForNoOp(this);
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Q.reject(reason);
|
if (this.operationType === Constants.ConflictOperationType.Replace) {
|
||||||
|
this.container.initDocumentEditorForReplace(this, this.content, currentDocumentContent);
|
||||||
|
} else {
|
||||||
|
this.container.initDocumentEditorForDelete(this, currentDocumentContent);
|
||||||
}
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
// Document could be deleted
|
||||||
|
if (
|
||||||
|
error &&
|
||||||
|
error.code === Constants.HttpStatusCodes.NotFound &&
|
||||||
|
this.operationType === Constants.ConflictOperationType.Delete
|
||||||
|
) {
|
||||||
|
this.container.initDocumentEditorForNoOp(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.container.loadingConflictData(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPartitionKeyValueAsString(): string {
|
public getPartitionKeyValueAsString(): string {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default class DocumentId {
|
|||||||
return JSON.stringify(partitionKeyValue);
|
return JSON.stringify(partitionKeyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadDocument(): Q.Promise<any> {
|
public async loadDocument(): Promise<void> {
|
||||||
return this.container.selectDocument(this);
|
await this.container.selectDocument(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
import { deleteStoredProcedure } from "../../Common/dataAccess/deleteStoredProcedure";
|
||||||
import { executeStoredProcedure } from "../../Common/DocumentClientUtilityBase";
|
import { executeStoredProcedure } from "../../Common/dataAccess/executeStoredProcedure";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
140
src/HostedExplorer.tsx
Normal file
140
src/HostedExplorer.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { useBoolean } from "@uifabric/react-hooks";
|
||||||
|
import { initializeIcons } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { render } from "react-dom";
|
||||||
|
import ChevronRight from "../images/chevron-right.svg";
|
||||||
|
import "../less/hostedexplorer.less";
|
||||||
|
import { AuthType } from "./AuthType";
|
||||||
|
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
|
||||||
|
import { DatabaseAccount } from "./Contracts/DataModels";
|
||||||
|
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
|
||||||
|
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
|
||||||
|
import "./Explorer/Menus/NavBar/MeControlComponent.less";
|
||||||
|
import { usePortalAccessToken } from "./hooks/usePortalAccessToken";
|
||||||
|
import { MeControl } from "./Platform/Hosted/Components/MeControl";
|
||||||
|
import "./Platform/Hosted/ConnectScreen.less";
|
||||||
|
import "./Shared/appInsights";
|
||||||
|
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
|
||||||
|
import { useAADAuth } from "./hooks/useAADAuth";
|
||||||
|
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
|
||||||
|
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
||||||
|
import { extractMasterKeyfromConnectionString } from "./Platform/Hosted/HostedUtils";
|
||||||
|
|
||||||
|
initializeIcons();
|
||||||
|
|
||||||
|
const App: React.FunctionComponent = () => {
|
||||||
|
// For handling encrypted portal tokens sent via query paramter
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const [encryptedToken, setEncryptedToken] = React.useState<string>(params && params.get("key"));
|
||||||
|
const encryptedTokenMetadata = usePortalAccessToken(encryptedToken);
|
||||||
|
|
||||||
|
// For showing/hiding panel
|
||||||
|
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
|
||||||
|
|
||||||
|
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth();
|
||||||
|
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
||||||
|
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
|
||||||
|
const [connectionString, setConnectionString] = React.useState<string>();
|
||||||
|
|
||||||
|
const ref = React.useRef<HTMLIFrameElement>();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// If ref.current is undefined no iframe has been rendered
|
||||||
|
if (ref.current) {
|
||||||
|
// In hosted mode, we can set global properties directly on the child iframe.
|
||||||
|
// This is not possible in the portal where the iframes have different origins
|
||||||
|
const frameWindow = ref.current.contentWindow as HostedExplorerChildFrame;
|
||||||
|
// AAD authenticated uses ALWAYS using AAD authType
|
||||||
|
if (isLoggedIn) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.AAD,
|
||||||
|
databaseAccount,
|
||||||
|
authorizationToken: armToken
|
||||||
|
};
|
||||||
|
} else if (authType === AuthType.EncryptedToken) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.EncryptedToken,
|
||||||
|
encryptedToken,
|
||||||
|
encryptedTokenMetadata
|
||||||
|
};
|
||||||
|
} else if (authType === AuthType.ConnectionString) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.ConnectionString,
|
||||||
|
encryptedToken,
|
||||||
|
encryptedTokenMetadata,
|
||||||
|
masterKey: extractMasterKeyfromConnectionString(connectionString)
|
||||||
|
};
|
||||||
|
} else if (authType === AuthType.ResourceToken) {
|
||||||
|
frameWindow.hostedConfig = {
|
||||||
|
authType: AuthType.ResourceToken,
|
||||||
|
resourceToken: connectionString
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ref, encryptedToken, encryptedTokenMetadata, isLoggedIn, databaseAccount]);
|
||||||
|
|
||||||
|
const showAccount = (isLoggedIn && databaseAccount) || (encryptedTokenMetadata && encryptedTokenMetadata);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header>
|
||||||
|
<div className="items" role="menubar">
|
||||||
|
<div className="cosmosDBTitle">
|
||||||
|
<span
|
||||||
|
className="title"
|
||||||
|
onClick={() => window.open("https://portal.azure.com", "_blank")}
|
||||||
|
tabIndex={0}
|
||||||
|
title="Go to Azure Portal"
|
||||||
|
>
|
||||||
|
Microsoft Azure
|
||||||
|
</span>
|
||||||
|
<span className="accontSplitter" /> <span className="serviceTitle">Cosmos DB</span>
|
||||||
|
{(isLoggedIn || encryptedTokenMetadata?.accountName) && (
|
||||||
|
<img className="chevronRight" src={ChevronRight} alt="account separator" />
|
||||||
|
)}
|
||||||
|
{isLoggedIn && (
|
||||||
|
<span className="accountSwitchComponentContainer">
|
||||||
|
<AccountSwitcher armToken={armToken} setDatabaseAccount={setDatabaseAccount} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!isLoggedIn && encryptedTokenMetadata?.accountName && (
|
||||||
|
<span className="accountSwitchComponentContainer">
|
||||||
|
<span className="accountNameHeader">{encryptedTokenMetadata?.accountName}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FeedbackCommandButton />
|
||||||
|
<div className="meControl">
|
||||||
|
{isLoggedIn ? (
|
||||||
|
<MeControl {...{ graphToken, openPanel, logout, account }} />
|
||||||
|
) : (
|
||||||
|
<SignInButton {...{ login }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{showAccount && (
|
||||||
|
// Ideally we would import and render data explorer like any other React component, however
|
||||||
|
// because it still has a significant amount of Knockout code, this would lead to memory leaks.
|
||||||
|
// Knockout does not have a way to tear down all of its binding and listeners with a single method.
|
||||||
|
// It's possible this can be changed once all knockout code has been removed.
|
||||||
|
<iframe
|
||||||
|
// Setting key is needed so React will re-render this element on any account change
|
||||||
|
key={databaseAccount?.id || encryptedTokenMetadata?.accountName}
|
||||||
|
ref={ref}
|
||||||
|
id="explorerMenu"
|
||||||
|
name="explorer"
|
||||||
|
className="iframe"
|
||||||
|
title="explorer"
|
||||||
|
src="explorer.html?v=1.0.1&platform=Hosted"
|
||||||
|
></iframe>
|
||||||
|
)}
|
||||||
|
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||||
|
<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />
|
||||||
|
)}
|
||||||
|
{isLoggedIn && <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<App />, document.getElementById("App"));
|
||||||
32
src/HostedExplorerChildFrame.ts
Normal file
32
src/HostedExplorerChildFrame.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { AuthType } from "./AuthType";
|
||||||
|
import { AccessInputMetadata, DatabaseAccount } from "./Contracts/DataModels";
|
||||||
|
|
||||||
|
export interface HostedExplorerChildFrame extends Window {
|
||||||
|
hostedConfig: AAD | ConnectionString | EncryptedToken | ResourceToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AAD {
|
||||||
|
authType: AuthType.AAD;
|
||||||
|
databaseAccount: DatabaseAccount;
|
||||||
|
authorizationToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConnectionString {
|
||||||
|
authType: AuthType.ConnectionString;
|
||||||
|
// Connection string uses still use encrypted token for Cassandra/Mongo APIs as they us the portal backend proxy
|
||||||
|
encryptedToken: string;
|
||||||
|
encryptedTokenMetadata: AccessInputMetadata;
|
||||||
|
// Master key is currently only used by Graph API. All other APIs use encrypted tokens and proxy with connection string
|
||||||
|
masterKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EncryptedToken {
|
||||||
|
authType: AuthType.EncryptedToken;
|
||||||
|
encryptedToken: string;
|
||||||
|
encryptedTokenMetadata: AccessInputMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResourceToken {
|
||||||
|
authType: AuthType.ResourceToken;
|
||||||
|
resourceToken: string;
|
||||||
|
}
|
||||||
@@ -178,8 +178,7 @@ export class JunoClient {
|
|||||||
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
|
return this.getNotebooks(`${this.getNotebooksUrl()}/gallery/public`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// will be renamed once feature.enableCodeOfConduct flag is removed
|
public async getPublicGalleryData(): Promise<IJunoResponse<IPublicGalleryData>> {
|
||||||
public async fetchPublicNotebooks(): Promise<IJunoResponse<IPublicGalleryData>> {
|
|
||||||
const url = `${this.getNotebooksAccountUrl()}/gallery/public`;
|
const url = `${this.getNotebooksAccountUrl()}/gallery/public`;
|
||||||
const response = await window.fetch(url, {
|
const response = await window.fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@@ -405,7 +404,7 @@ export class JunoClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
|
public async reportAbuse(notebookId: string, abuseCategory: string, notes: string): Promise<IJunoResponse<boolean>> {
|
||||||
const response = await window.fetch(`${this.getNotebooksUrl()}/avert/reportAbuse`, {
|
const response = await window.fetch(`${this.getNotebooksUrl()}/gallery/reportAbuse`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
notebookId,
|
notebookId,
|
||||||
|
|||||||
221
src/Main.tsx
221
src/Main.tsx
@@ -44,7 +44,6 @@ import "./Libs/jquery";
|
|||||||
import "bootstrap/dist/js/npm";
|
import "bootstrap/dist/js/npm";
|
||||||
import "../externals/jquery.typeahead.min.js";
|
import "../externals/jquery.typeahead.min.js";
|
||||||
import "../externals/jquery-ui.min.js";
|
import "../externals/jquery-ui.min.js";
|
||||||
import "../externals/adal.js";
|
|
||||||
import "promise-polyfill/src/polyfill";
|
import "promise-polyfill/src/polyfill";
|
||||||
import "abort-controller/polyfill";
|
import "abort-controller/polyfill";
|
||||||
import "whatwg-fetch";
|
import "whatwg-fetch";
|
||||||
@@ -56,19 +55,11 @@ import "url-polyfill/url-polyfill.min";
|
|||||||
|
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as TelemetryProcessor from "./Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "./Shared/Telemetry/TelemetryConstants";
|
|
||||||
|
|
||||||
import { BindingHandlersRegisterer } from "./Bindings/BindingHandlersRegisterer";
|
|
||||||
import * as Emulator from "./Platform/Emulator/Main";
|
|
||||||
import Hosted from "./Platform/Hosted/Main";
|
|
||||||
import * as Portal from "./Platform/Portal/Main";
|
|
||||||
import { AuthType } from "./AuthType";
|
import { AuthType } from "./AuthType";
|
||||||
|
|
||||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||||
import { applyExplorerBindings } from "./applyExplorerBindings";
|
import { applyExplorerBindings } from "./applyExplorerBindings";
|
||||||
import { initializeConfiguration, Platform } from "./ConfigContext";
|
import { configContext, initializeConfiguration, Platform } from "./ConfigContext";
|
||||||
import Explorer from "./Explorer/Explorer";
|
import Explorer from "./Explorer/Explorer";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
@@ -78,49 +69,195 @@ import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
|
|||||||
import refreshImg from "../images/refresh-cosmos.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 { KOCommentEnd, KOCommentIfStart } from "./koComment";
|
||||||
|
import { updateUserContext } from "./UserContext";
|
||||||
|
import AuthHeadersUtil from "./Platform/Hosted/Authorization";
|
||||||
|
import { CollectionCreation } from "./Shared/Constants";
|
||||||
|
import { extractFeatures } from "./Platform/Hosted/extractFeatures";
|
||||||
|
import { emulatorAccount } from "./Platform/Emulator/emulatorAccount";
|
||||||
|
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
|
||||||
|
import {
|
||||||
|
getDatabaseAccountKindFromExperience,
|
||||||
|
getDatabaseAccountPropertiesFromMetadata
|
||||||
|
} from "./Platform/Hosted/HostedUtils";
|
||||||
|
import { DefaultExperienceUtility } from "./Shared/DefaultExperienceUtility";
|
||||||
|
import { parseResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||||
|
import { AccountKind, DefaultAccountExperience } from "./Common/Constants";
|
||||||
|
|
||||||
// TODO: Encapsulate and reuse all global variables as environment variables
|
// const accountResourceId =
|
||||||
window.authType = AuthType.AAD;
|
// authType === AuthType.EncryptedToken
|
||||||
|
// ? Main._databaseAccountId
|
||||||
|
// : authType === AuthType.AAD && account
|
||||||
|
// ? account.id
|
||||||
|
// : "";
|
||||||
|
// const subscriptionId: string =
|
||||||
|
// accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
|
||||||
|
// const resourceGroup: string =
|
||||||
|
// accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||||
|
|
||||||
const App: React.FunctionComponent = () => {
|
const App: React.FunctionComponent = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeConfiguration().then(config => {
|
initializeConfiguration().then(config => {
|
||||||
|
let explorer: Explorer;
|
||||||
if (config.platform === Platform.Hosted) {
|
if (config.platform === Platform.Hosted) {
|
||||||
try {
|
const win = (window as unknown) as HostedExplorerChildFrame;
|
||||||
Hosted.initializeExplorer().then(
|
explorer = new Explorer();
|
||||||
(explorer: Explorer) => {
|
if (win.hostedConfig.authType === AuthType.EncryptedToken) {
|
||||||
applyExplorerBindings(explorer);
|
// TODO: Remove window.authType
|
||||||
Hosted.configureTokenValidationDisplayPrompt(explorer);
|
window.authType = AuthType.EncryptedToken;
|
||||||
},
|
// Impossible to tell if this is a try cosmos sub using an encrypted token
|
||||||
(error: unknown) => {
|
explorer.isTryCosmosDBSubscription(false);
|
||||||
try {
|
updateUserContext({
|
||||||
const uninitializedExplorer: Explorer = Hosted.getUninitializedExplorerForGuestAccess();
|
accessToken: encodeURIComponent(win.hostedConfig.encryptedToken)
|
||||||
window.dataExplorer = uninitializedExplorer;
|
});
|
||||||
ko.applyBindings(uninitializedExplorer);
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
||||||
if (window.authType !== AuthType.AAD) {
|
win.hostedConfig.encryptedTokenMetadata.apiKind
|
||||||
uninitializedExplorer.isRefreshingExplorer(false);
|
|
||||||
uninitializedExplorer.displayConnectExplorerForm();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
explorer.initDataExplorerWithFrameInputs({
|
||||||
console.log(e);
|
databaseAccount: {
|
||||||
|
id: "",
|
||||||
|
// id: Main._databaseAccountId,
|
||||||
|
name: win.hostedConfig.encryptedTokenMetadata.accountName,
|
||||||
|
kind: getDatabaseAccountKindFromExperience(apiExperience),
|
||||||
|
properties: getDatabaseAccountPropertiesFromMetadata(win.hostedConfig.encryptedTokenMetadata),
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
subscriptionId: undefined,
|
||||||
|
resourceGroup: undefined,
|
||||||
|
masterKey: undefined,
|
||||||
|
hasWriteAccess: true, // TODO: we should embed this information in the token ideally
|
||||||
|
authorizationToken: undefined,
|
||||||
|
features: extractFeatures(),
|
||||||
|
csmEndpoint: undefined,
|
||||||
|
dnsSuffix: undefined,
|
||||||
|
serverId: AuthHeadersUtil.serverId,
|
||||||
|
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||||
|
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||||
|
quotaId: undefined,
|
||||||
|
addCollectionDefaultFlight: explorer.flight(),
|
||||||
|
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription()
|
||||||
|
});
|
||||||
|
explorer.isAccountReady(true);
|
||||||
|
} else if (win.hostedConfig.authType === AuthType.ResourceToken) {
|
||||||
|
window.authType = AuthType.ResourceToken;
|
||||||
|
// Resource tokens can only be used with SQL API
|
||||||
|
const apiExperience: string = DefaultAccountExperience.DocumentDB;
|
||||||
|
const parsedResourceToken = parseResourceTokenConnectionString(win.hostedConfig.resourceToken);
|
||||||
|
updateUserContext({
|
||||||
|
resourceToken: parsedResourceToken.resourceToken
|
||||||
|
});
|
||||||
|
return explorer.initDataExplorerWithFrameInputs({
|
||||||
|
databaseAccount: {
|
||||||
|
id: "",
|
||||||
|
name: parsedResourceToken.accountEndpoint,
|
||||||
|
kind: AccountKind.GlobalDocumentDB,
|
||||||
|
properties: { documentEndpoint: parsedResourceToken.accountEndpoint },
|
||||||
|
tags: { defaultExperience: apiExperience }
|
||||||
|
},
|
||||||
|
subscriptionId: undefined,
|
||||||
|
resourceGroup: undefined,
|
||||||
|
masterKey: undefined,
|
||||||
|
hasWriteAccess: true, // TODO: we should embed this information in the token ideally
|
||||||
|
authorizationToken: undefined,
|
||||||
|
features: extractFeatures(),
|
||||||
|
csmEndpoint: undefined,
|
||||||
|
dnsSuffix: undefined,
|
||||||
|
serverId: AuthHeadersUtil.serverId,
|
||||||
|
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||||
|
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||||
|
quotaId: undefined,
|
||||||
|
addCollectionDefaultFlight: explorer.flight(),
|
||||||
|
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
|
||||||
|
isAuthWithresourceToken: true
|
||||||
|
});
|
||||||
|
} else if (win.hostedConfig.authType === AuthType.ConnectionString) {
|
||||||
|
// For legacy reasons lots of code expects a connection string login to look and act like an encrypted token login
|
||||||
|
window.authType = AuthType.EncryptedToken;
|
||||||
|
// Impossible to tell if this is a try cosmos sub using an encrypted token
|
||||||
|
explorer.isTryCosmosDBSubscription(false);
|
||||||
|
updateUserContext({
|
||||||
|
accessToken: encodeURIComponent(win.hostedConfig.encryptedToken)
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
|
||||||
|
win.hostedConfig.encryptedTokenMetadata.apiKind
|
||||||
|
);
|
||||||
|
explorer.initDataExplorerWithFrameInputs({
|
||||||
|
databaseAccount: {
|
||||||
|
id: "",
|
||||||
|
// id: Main._databaseAccountId,
|
||||||
|
name: win.hostedConfig.encryptedTokenMetadata.accountName,
|
||||||
|
kind: getDatabaseAccountKindFromExperience(apiExperience),
|
||||||
|
properties: getDatabaseAccountPropertiesFromMetadata(win.hostedConfig.encryptedTokenMetadata),
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
subscriptionId: undefined,
|
||||||
|
resourceGroup: undefined,
|
||||||
|
masterKey: win.hostedConfig.masterKey,
|
||||||
|
hasWriteAccess: true, // TODO: we should embed this information in the token ideally
|
||||||
|
authorizationToken: undefined,
|
||||||
|
features: extractFeatures(),
|
||||||
|
csmEndpoint: undefined,
|
||||||
|
dnsSuffix: undefined,
|
||||||
|
serverId: AuthHeadersUtil.serverId,
|
||||||
|
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||||
|
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||||
|
quotaId: undefined,
|
||||||
|
addCollectionDefaultFlight: explorer.flight(),
|
||||||
|
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription()
|
||||||
|
});
|
||||||
|
explorer.isAccountReady(true);
|
||||||
|
} else if (win.hostedConfig.authType === AuthType.AAD) {
|
||||||
|
window.authType = AuthType.AAD;
|
||||||
|
const account = win.hostedConfig.databaseAccount;
|
||||||
|
const accountResourceId = account.id;
|
||||||
|
const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
|
||||||
|
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: win.hostedConfig.databaseAccount
|
||||||
|
});
|
||||||
|
explorer.initDataExplorerWithFrameInputs({
|
||||||
|
databaseAccount: account,
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
masterKey: "",
|
||||||
|
hasWriteAccess: true, //TODO: 425017 - support read access
|
||||||
|
authorizationToken: `Bearer ${win.hostedConfig.authorizationToken}`,
|
||||||
|
features: extractFeatures(),
|
||||||
|
csmEndpoint: undefined,
|
||||||
|
dnsSuffix: undefined,
|
||||||
|
serverId: AuthHeadersUtil.serverId,
|
||||||
|
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||||
|
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||||
|
quotaId: undefined,
|
||||||
|
addCollectionDefaultFlight: explorer.flight(),
|
||||||
|
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription()
|
||||||
|
});
|
||||||
|
explorer.isAccountReady(true);
|
||||||
}
|
}
|
||||||
} else if (config.platform === Platform.Emulator) {
|
} else if (config.platform === Platform.Emulator) {
|
||||||
window.authType = AuthType.MasterKey;
|
window.authType = AuthType.MasterKey;
|
||||||
const explorer = Emulator.initializeExplorer();
|
explorer = new Explorer();
|
||||||
applyExplorerBindings(explorer);
|
explorer.databaseAccount(emulatorAccount);
|
||||||
|
explorer.isAccountReady(true);
|
||||||
} else if (config.platform === Platform.Portal) {
|
} else if (config.platform === Platform.Portal) {
|
||||||
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.Open, {});
|
explorer = new Explorer();
|
||||||
const explorer = Portal.initializeExplorer();
|
|
||||||
TelemetryProcessor.trace(Action.InitializeDataExplorer, ActionModifiers.IFrameReady, {});
|
// In development mode, try to load the iframe message from session storage.
|
||||||
applyExplorerBindings(explorer);
|
// This allows webpack hot reload to funciton properly
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
|
||||||
|
if (initMessage) {
|
||||||
|
const message = JSON.parse(initMessage);
|
||||||
|
console.warn("Loaded cached portal iframe message from session storage");
|
||||||
|
console.dir(message);
|
||||||
|
explorer.initDataExplorerWithFrameInputs(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
||||||
}
|
}
|
||||||
|
applyExplorerBindings(explorer);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -177,7 +314,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
aria-label="Share url link"
|
aria-label="Share url link"
|
||||||
className="shareLink"
|
className="shareLink"
|
||||||
type="text"
|
type="text"
|
||||||
read-only
|
read-only={true}
|
||||||
data-bind="value: shareAccessUrl"
|
data-bind="value: shareAccessUrl"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { getErrorMessage } from "../Common/ErrorHandlingUtils";
|
|||||||
export class NotebookWorkspaceManager {
|
export class NotebookWorkspaceManager {
|
||||||
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
|
||||||
|
|
||||||
constructor(private _armEndpoint: string) {
|
constructor() {
|
||||||
this.resourceProviderClientFactory = new ResourceProviderClientFactory(this._armEndpoint);
|
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user