mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-26 12:21:23 +00:00
Compare commits
36 Commits
codescan
...
remove-ru-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627c346559 | ||
|
|
415ebc505b | ||
|
|
a133134b8b | ||
|
|
79dec6a8a8 | ||
|
|
53a8cea95e | ||
|
|
5f1f7a8266 | ||
|
|
a009a8ba5f | ||
|
|
3e782527d0 | ||
|
|
e6ca1d25c9 | ||
|
|
473f722dcc | ||
|
|
5741802c25 | ||
|
|
e2e58f73b1 | ||
|
|
79769e9689 | ||
|
|
542abf4d3a | ||
|
|
4bee46ce55 | ||
|
|
fe58722002 | ||
|
|
94ff6b3e81 | ||
|
|
b4219e2994 | ||
|
|
294270b6aa | ||
|
|
e4bab1de4b | ||
|
|
703ceacd3f | ||
|
|
734df3dd18 | ||
|
|
1e19f02fd7 | ||
|
|
24b5b754ca | ||
|
|
e09730d782 | ||
|
|
09a95fded4 | ||
|
|
7ffa18a190 | ||
|
|
30353c26f3 | ||
|
|
34d8704071 | ||
|
|
23714831bd | ||
|
|
9a5d46b6e0 | ||
|
|
b9245101bc | ||
|
|
274deb13be | ||
|
|
9d50577800 | ||
|
|
bd00e5eb9b | ||
|
|
821f665e78 |
@@ -1,7 +1,10 @@
|
|||||||
# These options are only needed when if running end to end tests locally
|
|
||||||
PORTAL_RUNNER_USERNAME=
|
PORTAL_RUNNER_USERNAME=
|
||||||
PORTAL_RUNNER_PASSWORD=
|
PORTAL_RUNNER_PASSWORD=
|
||||||
PORTAL_RUNNER_SUBSCRIPTION=
|
PORTAL_RUNNER_SUBSCRIPTION=
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
PORTAL_RUNNER_RESOURCE_GROUP=
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
||||||
PORTAL_RUNNER_CONNECTION_STRING=
|
PORTAL_RUNNER_CONNECTION_STRING=
|
||||||
|
CASSANDRA_CONNECTION_STRING=
|
||||||
|
MONGO_CONNECTION_STRING=
|
||||||
|
TABLES_CONNECTION_STRING=
|
||||||
|
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||||
@@ -15,8 +15,6 @@ 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/EnvironmentUtility.ts
|
||||||
src/Common/ErrorParserUtility.test.ts
|
|
||||||
src/Common/ErrorParserUtility.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
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ module.exports = {
|
|||||||
"no-null/no-null": "error",
|
"no-null/no-null": "error",
|
||||||
"@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",
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
|
||||||
|
message: "Do not use JSON.stringify(error). It will print '{}'"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -150,6 +150,12 @@ jobs:
|
|||||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||||
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
||||||
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
||||||
|
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||||
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
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/')
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -33,7 +33,7 @@ To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin t
|
|||||||
|
|
||||||
### 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 enironment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
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.
|
||||||
|
|
||||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ The Cosmos Portal that consumes this repo is not currently open source. If you h
|
|||||||
You can however load a local running instance of data explorer in the production portal.
|
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)
|
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. Whitelist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
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`
|
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
|
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
|
||||||
@@ -84,16 +84,19 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
|
|||||||
4. Install dependencies: `npm install`
|
4. Install dependencies: `npm install`
|
||||||
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
||||||
|
|
||||||
#### End to End Production Runners
|
#### End to End Production Tests
|
||||||
|
|
||||||
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
||||||
|
|
||||||
1. Copy .env.example to .env and fill in all variables
|
1. Copy .env.example to .env
|
||||||
2. Run `npm run test:e2e`
|
2. Update the values in .env including your local data explorer endpoint (ask a teammate/codeowner for help with .env values)
|
||||||
|
3. Make sure all packages are installed `npm install`
|
||||||
|
4. Run the server `npm run start` and wait for it to start
|
||||||
|
5. Run `npm run test:e2e`
|
||||||
|
|
||||||
### Releasing
|
### Releasing
|
||||||
|
|
||||||
We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Add steps that build, run tests, deploy, and more:
|
# Add steps that build, run tests, deploy, and more:
|
||||||
# https://aka.ms/yaml
|
# https://aka.ms/yaml
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "ubuntu-latest"
|
vmImage: "ubuntu-latest"
|
||||||
|
|
||||||
|
|||||||
@@ -3023,3 +3023,7 @@ settings-pane {
|
|||||||
.infoBoxContent a {
|
.infoBoxContent a {
|
||||||
color: @AccentMediumHigh
|
color: @AccentMediumHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapsibleSection :hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
158
package-lock.json
generated
158
package-lock.json
generated
@@ -76,7 +76,6 @@
|
|||||||
"version": "7.9.0",
|
"version": "7.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
||||||
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.8.3",
|
"@babel/code-frame": "^7.8.3",
|
||||||
"@babel/generator": "^7.9.0",
|
"@babel/generator": "^7.9.0",
|
||||||
@@ -99,8 +98,7 @@
|
|||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2805,12 +2803,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nteract/monaco-editor": {
|
"@nteract/monaco-editor": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.2.tgz",
|
||||||
"integrity": "sha512-PGEUvy/GTBMECy4RUfh4wxO7GfA9YDBSV3hGt8MyrVz/GxUDtjB7FqrYS0ZhmVQPYl8hnV2i48F3YlypC+xIXA==",
|
"integrity": "sha512-51Pxt6v6qaAlbDY0BgEydk/Jxuu93t+uB8Geg3vJfE6VDphTEakB0wocBIfvcTKVV55Lx53/rTSp6QHqtaHiGg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nteract/core": "^14.0.0",
|
"@nteract/core": "^14.0.0",
|
||||||
"@nteract/messaging": "^7.0.10",
|
"@nteract/messaging": "^7.0.12",
|
||||||
|
"lodash.debounce": "^4.0.6",
|
||||||
"monaco-editor": "0.18.1",
|
"monaco-editor": "0.18.1",
|
||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
},
|
},
|
||||||
@@ -2859,6 +2858,40 @@
|
|||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nteract/messaging": {
|
||||||
|
"version": "7.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.12.tgz",
|
||||||
|
"integrity": "sha512-5z2Ffd1hj7AsGBJTAoqJshLlUZ+ISJBjiZAdNDjb70PNEv0x8UOMk/di80RI3WBLK5MKxSJkGXfs4jfzfdW6bA==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/types": "^7.1.2",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nteract/commutable": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-Z6aUtIZN0CKUMJwbZjUUqaaBhT6P0RiEG5nHso+oG/FOXF20Qv+hf/TyvYhw9SXQVmmacaMk4zj0iOID20pIng==",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/types": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-I/1TvaUC/m9I/LFk1HemsOUqB0eNdagu0KRLA1YEtChPh9pk5F9flglA7m5+0/j31gLXBISj5+6tL8ikA8BxOQ==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/commutable": "^7.3.4",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nteract/mythic-notifications": {
|
"@nteract/mythic-notifications": {
|
||||||
"version": "0.1.9",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
||||||
@@ -7203,11 +7236,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-react-class": {
|
"create-react-class": {
|
||||||
"version": "15.6.3",
|
"version": "15.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||||
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
|
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "^0.8.9",
|
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
@@ -8504,24 +8536,6 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@@ -9667,27 +9681,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbjs": {
|
|
||||||
"version": "0.8.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
|
||||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
|
||||||
"requires": {
|
|
||||||
"core-js": "^1.0.0",
|
|
||||||
"isomorphic-fetch": "^2.1.1",
|
|
||||||
"loose-envify": "^1.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"promise": "^7.1.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"ua-parser-js": "^0.7.18"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
|
||||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fd-slicer": {
|
"fd-slicer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
@@ -11888,26 +11881,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
|
||||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^1.0.1",
|
|
||||||
"whatwg-fetch": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"node-fetch": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
|
||||||
"requires": {
|
|
||||||
"encoding": "^0.1.11",
|
|
||||||
"is-stream": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
@@ -13406,36 +13379,6 @@
|
|||||||
"micromatch": "^3.1.10",
|
"micromatch": "^3.1.10",
|
||||||
"pretty-format": "^24.9.0",
|
"pretty-format": "^24.9.0",
|
||||||
"realpath-native": "^1.1.0"
|
"realpath-native": "^1.1.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": {
|
|
||||||
"version": "7.11.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
|
|
||||||
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/code-frame": "^7.10.4",
|
|
||||||
"@babel/generator": "^7.11.6",
|
|
||||||
"@babel/helper-module-transforms": "^7.11.0",
|
|
||||||
"@babel/helpers": "^7.10.4",
|
|
||||||
"@babel/parser": "^7.11.5",
|
|
||||||
"@babel/template": "^7.10.4",
|
|
||||||
"@babel/traverse": "^7.11.5",
|
|
||||||
"@babel/types": "^7.11.5",
|
|
||||||
"convert-source-map": "^1.7.0",
|
|
||||||
"debug": "^4.1.0",
|
|
||||||
"gensync": "^1.0.0-beta.1",
|
|
||||||
"json5": "^2.1.2",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"resolve": "^1.3.2",
|
|
||||||
"semver": "^5.4.1",
|
|
||||||
"source-map": "^0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.5.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-dev-server": {
|
"jest-dev-server": {
|
||||||
@@ -14514,6 +14457,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||||
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
||||||
},
|
},
|
||||||
|
"lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||||
|
},
|
||||||
"lodash.defaults": {
|
"lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
@@ -16619,6 +16567,8 @@
|
|||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
@@ -18246,7 +18196,8 @@
|
|||||||
"setimmediate": {
|
"setimmediate": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"setprototypeof": {
|
"setprototypeof": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -20006,11 +19957,6 @@
|
|||||||
"free-style": "3.1.0"
|
"free-style": "3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
|
||||||
"version": "0.7.22",
|
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
|
||||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.4.0",
|
"@nteract/markdown": "4.4.0",
|
||||||
"@nteract/monaco-editor": "3.2.0",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "3.0.9",
|
"@nteract/outputs": "3.0.9",
|
||||||
"@nteract/presentational-components": "3.0.7",
|
"@nteract/presentational-components": "3.0.7",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AutopilotTier } from "../Contracts/DataModels";
|
|
||||||
import { HashMap } from "./HashMap";
|
import { HashMap } from "./HashMap";
|
||||||
|
|
||||||
export class AuthorizationEndpoints {
|
export class AuthorizationEndpoints {
|
||||||
@@ -124,7 +123,6 @@ export class Features {
|
|||||||
public static readonly notebookBasePath = "notebookbasepath";
|
public static readonly notebookBasePath = "notebookbasepath";
|
||||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
|
||||||
public static readonly ttl90Days = "ttl90days";
|
public static readonly ttl90Days = "ttl90days";
|
||||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||||
@@ -133,6 +131,7 @@ export class Features {
|
|||||||
// flight names returned from the portal are always lowercase
|
// flight names returned from the portal are always lowercase
|
||||||
export class Flights {
|
export class Flights {
|
||||||
public static readonly SettingsV2 = "settingsv2";
|
public static readonly SettingsV2 = "settingsv2";
|
||||||
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -261,7 +260,6 @@ export class HttpHeaders {
|
|||||||
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
||||||
public static autoPilotThroughput = "autoscaleSettings";
|
public static autoPilotThroughput = "autoscaleSettings";
|
||||||
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
||||||
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
|
|
||||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||||
@@ -406,54 +404,6 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AutoPilot {
|
|
||||||
public static tier1Text: string = "4,000 RU/s";
|
|
||||||
public static tier2Text: string = "20,000 RU/s";
|
|
||||||
public static tier3Text: string = "100,000 RU/s";
|
|
||||||
public static tier4Text: string = "500,000 RU/s";
|
|
||||||
|
|
||||||
public static tierText = {
|
|
||||||
[AutopilotTier.Tier1]: "Tier 1",
|
|
||||||
[AutopilotTier.Tier2]: "Tier 2",
|
|
||||||
[AutopilotTier.Tier3]: "Tier 3",
|
|
||||||
[AutopilotTier.Tier4]: "Tier 4"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMaxRus = {
|
|
||||||
[AutopilotTier.Tier1]: 2000,
|
|
||||||
[AutopilotTier.Tier2]: 20000,
|
|
||||||
[AutopilotTier.Tier3]: 100000,
|
|
||||||
[AutopilotTier.Tier4]: 500000
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMinRus = {
|
|
||||||
[AutopilotTier.Tier1]: 0,
|
|
||||||
[AutopilotTier.Tier2]: 0,
|
|
||||||
[AutopilotTier.Tier3]: 0,
|
|
||||||
[AutopilotTier.Tier4]: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierStorageInGB = {
|
|
||||||
[AutopilotTier.Tier1]: 50,
|
|
||||||
[AutopilotTier.Tier2]: 200,
|
|
||||||
[AutopilotTier.Tier3]: 1000,
|
|
||||||
[AutopilotTier.Tier4]: 5000
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerVersions {
|
|
||||||
public static readonly v_1_0_0: string = "1.0.0";
|
|
||||||
public static readonly v_1_0_1: string = "1.0.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerFeatures {
|
|
||||||
public static offerCache: string = "OfferCache";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DataExplorerFeaturesVersions: any = {
|
|
||||||
OfferCache: DataExplorerVersions.v_1_0_1
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ describe("endpoint", () => {
|
|||||||
|
|
||||||
describe("requestPlugin", () => {
|
describe("requestPlugin", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
delete window.dataExplorerPlatform;
|
|
||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -69,7 +70,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
const result = JSON.parse(await response.json());
|
const result = JSON.parse(await response.json());
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
|
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,22 +168,6 @@ export function deleteConflict(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
export function queryConflicts(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
containerId: string,
|
containerId: string,
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||||
import * as Logger from "./Logger";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||||
export function queryDocuments(
|
export function queryDocuments(
|
||||||
@@ -59,13 +56,11 @@ export function executeStoredProcedure(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
error,
|
||||||
error
|
"ExecuteStoredProcedure",
|
||||||
)}`
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
);
|
);
|
||||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -93,9 +88,7 @@ export function queryDocumentsPage(
|
|||||||
deferred.resolve(result);
|
deferred.resolve(result);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -116,9 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
|||||||
deferred.resolve(document);
|
deferred.resolve(document);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -144,9 +135,7 @@ export function updateDocument(
|
|||||||
deferred.resolve(updatedDocument);
|
deferred.resolve(updatedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -168,11 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
|||||||
deferred.resolve(savedDocument);
|
deferred.resolve(savedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -194,9 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -222,9 +205,7 @@ export function deleteConflict(
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -234,11 +215,3 @@ export function deleteConflict(
|
|||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedOffers();
|
|
||||||
}
|
|
||||||
|
|||||||
56
src/Common/ErrorHandlingUtils.ts
Normal file
56
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ARMError } from "../Utils/arm/request";
|
||||||
|
import { HttpStatusCodes } from "./Constants";
|
||||||
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
|
import { SubscriptionType } from "../Contracts/ViewModels";
|
||||||
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { logError } from "./Logger";
|
||||||
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
|
export const handleError = (error: string | ARMError | Error, area: string, consoleErrorPrefix?: string): void => {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
const errorCode = error instanceof ARMError ? error.code : undefined;
|
||||||
|
|
||||||
|
// logs error to data explorer console
|
||||||
|
const consoleErrorMessage = consoleErrorPrefix ? `${consoleErrorPrefix}:\n ${errorMessage}` : errorMessage;
|
||||||
|
logConsoleError(consoleErrorMessage);
|
||||||
|
|
||||||
|
// logs error to both app insight and kusto
|
||||||
|
logError(errorMessage, area, errorCode);
|
||||||
|
|
||||||
|
// checks for errors caused by firewall and sends them to portal to handle
|
||||||
|
sendNotificationForError(errorMessage, errorCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorMessage = (error: string | Error): string => {
|
||||||
|
const errorMessage = typeof error === "string" ? error : error.message;
|
||||||
|
return replaceKnownError(errorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorStack = (error: string | Error): string => {
|
||||||
|
return typeof error === "string" ? undefined : error.stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
|
||||||
|
if (errorCode === HttpStatusCodes.Forbidden) {
|
||||||
|
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMessage({
|
||||||
|
type: MessageTypes.ForbiddenError,
|
||||||
|
reason: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceKnownError = (errorMessage: string): string => {
|
||||||
|
if (
|
||||||
|
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||||
|
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||||
|
) {
|
||||||
|
return "Database throughput is not supported for internal subscriptions.";
|
||||||
|
} else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) {
|
||||||
|
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
};
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
|
||||||
|
|
||||||
describe("Error Parser Utility", () => {
|
|
||||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
|
||||||
it("should parse a backend error correctly", () => {
|
|
||||||
// A fake error matching what is thrown by the SDK on a bad collection create request
|
|
||||||
const innerMessage =
|
|
||||||
"The partition key component definition path '/asdwqr31 @#$#$WRadf' could not be accepted, failed near position '10'. Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
|
||||||
const message = `Message: {\"Errors\":[\"${innerMessage}\"]}\r\nActivityId: 97b2e684-7505-4921-85f6-2513b9b28220, Request URI: /apps/89fdcf25-2a0b-4d2a-aab6-e161e565b26f/services/54911149-7bb1-4e7d-a1fa-22c8b36a4bb9/partitions/cc2a7a04-5f5a-4709-bcf7-8509b264963f/replicas/132304018743619218p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.10.0`;
|
|
||||||
const err = new Error(message) as any;
|
|
||||||
err.code = 400;
|
|
||||||
err.body = {
|
|
||||||
code: "BadRequest",
|
|
||||||
message
|
|
||||||
};
|
|
||||||
err.headers = {};
|
|
||||||
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";
|
|
||||||
|
|
||||||
const parsedError = ErrorParserUtility.parse(err);
|
|
||||||
expect(parsedError.length).toBe(1);
|
|
||||||
expect(parsedError[0].message).toBe(innerMessage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
|
|
||||||
export function replaceKnownError(err: string): string {
|
|
||||||
if (
|
|
||||||
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
|
|
||||||
err.indexOf("SharedOffer is Disabled for your account") >= 0
|
|
||||||
) {
|
|
||||||
return "Database throughput is not supported for internal subscriptions.";
|
|
||||||
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
|
|
||||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
try {
|
|
||||||
return _parse(err);
|
|
||||||
} catch (e) {
|
|
||||||
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
var normalizedErrors: DataModels.ErrorDataModel[] = [];
|
|
||||||
if (err.message && !err.code) {
|
|
||||||
normalizedErrors.push(err);
|
|
||||||
} else {
|
|
||||||
const innerErrors: any[] = _getInnerErrors(err.message);
|
|
||||||
normalizedErrors = innerErrors.map(innerError =>
|
|
||||||
typeof innerError === "string" ? { message: innerError } : innerError
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getInnerErrors(message: string): any[] {
|
|
||||||
/*
|
|
||||||
The backend error message has an inner-message which is a stringified object.
|
|
||||||
|
|
||||||
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
|
||||||
Example:
|
|
||||||
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
|
||||||
For non-SQL errors the "Errors" propery is an array of string.
|
|
||||||
Example:
|
|
||||||
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
|
|
||||||
*/
|
|
||||||
|
|
||||||
let innerMessage: any = null;
|
|
||||||
|
|
||||||
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
|
|
||||||
try {
|
|
||||||
// Multi-Partition error flavor
|
|
||||||
const regExp = /^(.*)ActivityId: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
} catch (e) {
|
|
||||||
// Single-partition error flavor
|
|
||||||
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
|
|
||||||
}
|
|
||||||
@@ -21,14 +21,8 @@ export function logWarning(message: string, area: string, code?: number): void {
|
|||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logError(message: string | Error, area: string, code?: number): void {
|
export function logError(errorMessage: string, area: string, code?: number | string): void {
|
||||||
let logMessage: string;
|
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
|
||||||
if (typeof message === "string") {
|
|
||||||
logMessage = message;
|
|
||||||
} else {
|
|
||||||
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
|
|
||||||
}
|
|
||||||
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
|
|
||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +53,7 @@ function _generateLogEntry(
|
|||||||
level: Diagnostics.LogEntryLevel,
|
level: Diagnostics.LogEntryLevel,
|
||||||
message: string,
|
message: string,
|
||||||
area: string,
|
area: string,
|
||||||
code?: number
|
code?: number | string
|
||||||
): Diagnostics.LogEntry {
|
): Diagnostics.LogEntry {
|
||||||
return {
|
return {
|
||||||
timestamp: new Date().getUTCSeconds(),
|
timestamp: new Date().getUTCSeconds(),
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
import * as Logger from "./Logger";
|
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -53,13 +52,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "setupQueriesCollection");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -102,19 +96,11 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
let errorMessage: string;
|
if (error.code === HttpStatusCodes.Conflict.toString()) {
|
||||||
const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
|
error = `Query ${query.queryName} already exists`;
|
||||||
if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
|
|
||||||
errorMessage = `Query ${query.queryName} already exists`;
|
|
||||||
} else {
|
|
||||||
errorMessage = parsedError.message;
|
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
|
||||||
ConsoleDataType.Error,
|
return Promise.reject(error);
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(parsedError), "saveQuery");
|
|
||||||
return Promise.reject(errorMessage);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -163,25 +149,15 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queries);
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
// should never get into this state but we handle this regardless
|
// should never get into this state but we handle this regardless
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -232,13 +208,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "deleteQuery");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
@@ -23,21 +22,19 @@ import {
|
|||||||
getGremlinGraph
|
getGremlinGraph
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { createDatabase } from "./createDatabase";
|
import { createDatabase } from "./createDatabase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
let collection: DataModels.Collection;
|
|
||||||
const clearMessage = logConsoleProgress(
|
const clearMessage = logConsoleProgress(
|
||||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
let collection: DataModels.Collection;
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
if (params.createNewDatabase) {
|
if (params.createNewDatabase) {
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
@@ -54,18 +51,15 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
} else {
|
} else {
|
||||||
collection = await createCollectionWithSDK(params);
|
collection = await createCollectionWithSDK(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||||
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
||||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
|
||||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
clearMessage();
|
|
||||||
return collection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
|||||||
@@ -24,36 +24,28 @@ import {
|
|||||||
createUpdateGremlinDatabase,
|
createUpdateGremlinDatabase,
|
||||||
getGremlinDatabase
|
getGremlinDatabase
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
let database: DataModels.Database;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||||
throw new Error("Creating database resources is not allowed for tables accounts");
|
throw new Error("Creating database resources is not allowed for tables accounts");
|
||||||
}
|
}
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
|
||||||
database = await createDatabaseWithARM(params);
|
? createDatabaseWithARM(params)
|
||||||
} else {
|
: createDatabaseWithSDK(params));
|
||||||
database = await createDatabaseWithSDK(params);
|
|
||||||
}
|
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||||
|
return database;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
handleError(error, "CreateDatabase", `Error while creating database ${params.databaseId}`);
|
||||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
await refreshCachedOffers();
|
|
||||||
clearMessage();
|
|
||||||
return database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
|
|||||||
@@ -1,28 +1,78 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createStoredProcedure(
|
export async function createStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.create(storedProcedure);
|
.scripts.storedProcedures.create(storedProcedure);
|
||||||
createdStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdStoredProcedure;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,73 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createTrigger(
|
export async function createTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition & Resource> {
|
): Promise<TriggerDefinition & Resource> {
|
||||||
let createdTrigger: TriggerDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.create(trigger);
|
.scripts.triggers.create(trigger);
|
||||||
createdTrigger = response.resource;
|
return response.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdTrigger;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,82 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createUserDefinedFunction(
|
export async function createUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||||
createdUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"CreateUserupdateUserDefinedFunction",
|
||||||
|
`Error while creating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdUserDefinedFunction;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteCollection", () => {
|
describe("deleteCollection", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteCollection", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
|
|||||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
|
|
||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
@@ -23,15 +21,13 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
|||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteDatabase", () => {
|
describe("deleteDatabase", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteDatabase", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
|
|||||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteDatabase(databaseId: string): Promise<void> {
|
export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||||
@@ -25,15 +23,13 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteDatabase", `Error while deleting database ${databaseId}`);
|
||||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteStoredProcedure(
|
export async function deleteStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
@@ -10,17 +13,30 @@ export async function deleteStoredProcedure(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.storedProcedure(storedProcedureId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedureId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.storedProcedure(storedProcedureId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);
|
||||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.trigger(triggerId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
triggerId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.trigger(triggerId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);
|
||||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.userDefinedFunction(id)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.userDefinedFunction(id)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);
|
||||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import * as Constants from "../Constants";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let indexTransformationPercentage: number;
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.read({ populateQuotaInfo: true });
|
||||||
|
|
||||||
|
indexTransformationPercentage = parseInt(
|
||||||
|
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return indexTransformationPercentage;
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
||||||
let collection: DataModels.Collection;
|
let collection: DataModels.Collection;
|
||||||
@@ -14,9 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
|||||||
.read();
|
.read();
|
||||||
collection = response.resource as DataModels.Collection;
|
collection = response.resource as DataModels.Collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
|
|||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readCollectionOffer = async (
|
export const readCollectionOffer = async (
|
||||||
@@ -57,9 +56,7 @@ export const readCollectionOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as HeadersUtility from "../HeadersUtility";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import { ContainerDefinition, Resource } from "@azure/cosmos";
|
|
||||||
import { HttpHeaders } from "../Constants";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
interface ResourceWithStatistics {
|
|
||||||
statistics: DataModels.Statistic[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const readCollectionQuotaInfo = async (
|
|
||||||
collection: ViewModels.Collection
|
|
||||||
): Promise<DataModels.CollectionQuotaInfo> => {
|
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${collection.id}`);
|
|
||||||
const options: RequestOptions = {};
|
|
||||||
options.populateQuotaInfo = true;
|
|
||||||
options.initialHeaders = options.initialHeaders || {};
|
|
||||||
options.initialHeaders[HttpHeaders.populatePartitionStatistics] = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.read(options);
|
|
||||||
const quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
|
|
||||||
const resource = response.resource as ContainerDefinition & Resource & ResourceWithStatistics;
|
|
||||||
quota["usageSizeInKB"] = resource.statistics.reduce(
|
|
||||||
(previousValue: number, currentValue: DataModels.Statistic) => previousValue + currentValue.sizeInKB,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
quota["numPartitions"] = resource.statistics.length;
|
|
||||||
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
|
|
||||||
|
|
||||||
return quota;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
|
||||||
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -2,18 +2,16 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
let collections: DataModels.Collection[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
@@ -22,22 +20,20 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
collections = await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
} else {
|
|
||||||
const sdkResponse = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.containers.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
collections = sdkResponse.resources as DataModels.Collection[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sdkResponse = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.containers.readAll()
|
||||||
|
.fetchAll();
|
||||||
|
return sdkResponse.resources as DataModels.Collection[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollections", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return collections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
|
|||||||
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readDatabaseOffer = async (
|
export const readDatabaseOffer = async (
|
||||||
@@ -48,9 +47,7 @@ export const readDatabaseOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -2,24 +2,23 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readDatabases(): Promise<DataModels.Database[]> {
|
export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
|
||||||
return [{ id: "TablesDB" } as DataModels.Database];
|
|
||||||
}
|
|
||||||
|
|
||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
@@ -28,9 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
databases = sdkResponse.resources as DataModels.Database[];
|
databases = sdkResponse.resources as DataModels.Database[];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabases", `Error while querying databases`);
|
||||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function readMongoDBCollectionThroughRP(
|
||||||
|
databaseId: string,
|
||||||
|
collectionId: string
|
||||||
|
): Promise<MongoDBCollectionResource> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let collection: MongoDBCollectionResource;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
|
collection = response.properties.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
@@ -1,26 +1,10 @@
|
|||||||
import { Offer } from "../../Contracts/DataModels";
|
import { Offer } from "../../Contracts/DataModels";
|
||||||
import { ClientDefaults } from "../Constants";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
import { Platform, configContext } from "../../ConfigContext";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export const readOffers = async (): Promise<Offer[]> => {
|
export const readOffers = async (): Promise<Offer[]> => {
|
||||||
const clearMessage = logConsoleProgress(`Querying offers`);
|
const clearMessage = logConsoleProgress(`Querying offers`);
|
||||||
try {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
|
||||||
userContext.databaseAccount.id,
|
|
||||||
ClientDefaults.portalCacheTimeoutMs
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await client()
|
const response = await client()
|
||||||
@@ -29,13 +13,11 @@ export const readOffers = async (): Promise<Offer[]> => {
|
|||||||
return response?.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
||||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
if (getErrorMessage(error)?.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadOffers", `Error while querying offers`);
|
||||||
logError(JSON.stringify(error), "ReadOffers", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readStoredProcedures(
|
export async function readStoredProcedures(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlStoredProcedures(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.readAll()
|
.scripts.storedProcedures.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
sprocs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadStoredProcedures", `Failed to query stored procedures for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return sprocs;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export async function readTriggers(
|
export async function readTriggers(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(TriggerDefinition & Resource)[]> {
|
): Promise<(TriggerDefinition & Resource)[]> {
|
||||||
let triggers: (TriggerDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlTriggers(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.readAll()
|
.scripts.triggers.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
triggers = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return triggers;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readUserDefinedFunctions(
|
export async function readUserDefinedFunctions(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlUserDefinedFunctions(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.readAll()
|
.scripts.userDefinedFunctions.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
udfs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"ReadUserDefinedFunctions",
|
||||||
|
`Failed to query user defined functions for container ${collectionId}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return udfs;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import * as Constants from "../Constants";
|
|
||||||
import { sendMessage } from "../MessageHandler";
|
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
|
|
||||||
interface CosmosError {
|
|
||||||
code: number;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendNotificationForError(error: CosmosError): void {
|
|
||||||
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
|
|
||||||
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage({
|
|
||||||
type: MessageTypes.ForbiddenError,
|
|
||||||
reason: error && error.message ? error.message : error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,10 @@ import { Collection } from "../../Contracts/DataModels";
|
|||||||
import { ContainerDefinition } from "@azure/cosmos";
|
import { ContainerDefinition } from "@azure/cosmos";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import {
|
import {
|
||||||
|
CreateUpdateOptions,
|
||||||
ExtendedResourceProperties,
|
ExtendedResourceProperties,
|
||||||
|
MongoDBCollectionCreateUpdateParameters,
|
||||||
|
MongoDBCollectionResource,
|
||||||
SqlContainerCreateUpdateParameters,
|
SqlContainerCreateUpdateParameters,
|
||||||
SqlContainerResource
|
SqlContainerResource
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
@@ -23,10 +26,8 @@ import {
|
|||||||
getGremlinGraph
|
getGremlinGraph
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateCollection(
|
export async function updateCollection(
|
||||||
@@ -51,16 +52,14 @@ export async function updateCollection(
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.replace(newCollection as ContainerDefinition, options);
|
.replace(newCollection as ContainerDefinition, options);
|
||||||
|
|
||||||
collection = sdkResponse.resource as Collection;
|
collection = sdkResponse.resource as Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateCollection", `Failed to update container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -80,15 +79,6 @@ async function updateCollectionWithARM(
|
|||||||
switch (defaultExperience) {
|
switch (defaultExperience) {
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return updateMongoDBCollection(
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.Graph:
|
case DefaultAccountExperienceType.Graph:
|
||||||
@@ -125,26 +115,35 @@ async function updateSqlContainer(
|
|||||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateMongoDBCollection(
|
export async function updateMongoDBCollectionThroughRP(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
subscriptionId: string,
|
newCollection: MongoDBCollectionResource,
|
||||||
resourceGroup: string,
|
updateOptions?: CreateUpdateOptions
|
||||||
accountName: string,
|
): Promise<MongoDBCollectionResource> {
|
||||||
newCollection: Collection
|
const subscriptionId = userContext.subscriptionId;
|
||||||
): Promise<Collection> {
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: newCollection,
|
||||||
|
options: updateOptions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateResponse = await createUpdateMongoDBCollection(
|
const updateResponse = await createUpdateMongoDBCollection(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
accountName,
|
accountName,
|
||||||
databaseId,
|
databaseId,
|
||||||
collectionId,
|
collectionId,
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
updateParams
|
||||||
);
|
);
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
|
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import { OfferDefinition } from "@azure/cosmos";
|
|||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { readCollectionOffer } from "./readCollectionOffer";
|
import { readCollectionOffer } from "./readCollectionOffer";
|
||||||
import { readDatabaseOffer } from "./readDatabaseOffer";
|
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import {
|
import {
|
||||||
updateSqlDatabaseThroughput,
|
updateSqlDatabaseThroughput,
|
||||||
migrateSqlDatabaseToAutoscale,
|
migrateSqlDatabaseToAutoscale,
|
||||||
@@ -71,14 +69,10 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
} else {
|
} else {
|
||||||
updatedOffer = await updateOfferWithSDK(params);
|
updatedOffer = await updateOfferWithSDK(params);
|
||||||
}
|
}
|
||||||
await refreshCachedOffers();
|
|
||||||
await refreshCachedResources();
|
|
||||||
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||||
return updatedOffer;
|
return updatedOffer;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateCollection", `Error updating offer for ${offerResourceText}`);
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -406,10 +400,14 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
|
|
||||||
const options: RequestOptions = {};
|
const options: RequestOptions = {};
|
||||||
if (params.migrateToAutoPilot) {
|
if (params.migrateToAutoPilot) {
|
||||||
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToAutopilot]: "true"
|
||||||
|
};
|
||||||
delete newOffer.content.offerAutopilotSettings;
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
} else if (params.migrateToManual) {
|
} else if (params.migrateToManual) {
|
||||||
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToManualThroughput]: "true"
|
||||||
|
};
|
||||||
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { updateOfferThroughputBeyondLimit } from "./updateOfferThroughputBeyondLimit";
|
|
||||||
|
|
||||||
describe("updateOfferThroughputBeyondLimit", () => {
|
|
||||||
it("should call fetch", async () => {
|
|
||||||
window.fetch = jest.fn(() => {
|
|
||||||
return {
|
|
||||||
ok: true
|
|
||||||
};
|
|
||||||
});
|
|
||||||
window.dataExplorer = {
|
|
||||||
logConsoleData: jest.fn(),
|
|
||||||
deleteInProgressConsoleDataWithId: jest.fn()
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} as any;
|
|
||||||
await updateOfferThroughputBeyondLimit({
|
|
||||||
subscriptionId: "foo",
|
|
||||||
resourceGroup: "foo",
|
|
||||||
databaseAccountName: "foo",
|
|
||||||
databaseName: "foo",
|
|
||||||
throughput: 1000000000,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
|
||||||
});
|
|
||||||
expect(window.fetch).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { Platform, configContext } from "../../ConfigContext";
|
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
|
||||||
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
|
||||||
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { HttpHeaders } from "../Constants";
|
|
||||||
|
|
||||||
interface UpdateOfferThroughputRequest {
|
|
||||||
subscriptionId: string;
|
|
||||||
resourceGroup: string;
|
|
||||||
databaseAccountName: string;
|
|
||||||
databaseName: string;
|
|
||||||
collectionName?: string;
|
|
||||||
throughput: number;
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
|
||||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThroughputRequest): Promise<void> {
|
|
||||||
if (configContext.platform !== Platform.Portal) {
|
|
||||||
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceDescriptionInfo = request.collectionName
|
|
||||||
? `database ${request.databaseName} and container ${request.collectionName}`
|
|
||||||
: `database ${request.databaseName}`;
|
|
||||||
|
|
||||||
const clearMessage = logConsoleProgress(
|
|
||||||
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const url = `${configContext.BACKEND_ENDPOINT}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
|
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
headers: { [authorizationHeader.header]: authorizationHeader.token, [HttpHeaders.contentType]: "application/json" }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
logConsoleInfo(
|
|
||||||
`Successfully requested an increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
|
|
||||||
);
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const error = await response.json();
|
|
||||||
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
|
|
||||||
clearMessage();
|
|
||||||
throw new Error(error.message);
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,72 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateStoredProcedure(
|
export async function updateStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
.scripts.storedProcedure(storedProcedure.id)
|
||||||
.replace(storedProcedure);
|
.replace(storedProcedure);
|
||||||
updatedStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateStoredProcedure", `Error while updating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedStoredProcedure;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,69 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { TriggerDefinition } from "@azure/cosmos";
|
import { TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateTrigger(
|
export async function updateTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition> {
|
): Promise<TriggerDefinition> {
|
||||||
let updatedTrigger: TriggerDefinition;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.trigger(trigger.id)
|
.scripts.trigger(trigger.id)
|
||||||
.replace(trigger);
|
.replace(trigger);
|
||||||
updatedTrigger = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedTrigger;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,76 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateUserDefinedFunction(
|
export async function updateUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||||
.replace(userDefinedFunction);
|
.replace(userDefinedFunction);
|
||||||
updatedUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"UpdateUserupdateUserDefinedFunction",
|
||||||
|
`Error while updating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedUserDefinedFunction;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,18 +191,6 @@ export interface OfferWithHeaders extends Offer {
|
|||||||
headers: any;
|
headers: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionQuotaInfo {
|
|
||||||
storedProcedures: number;
|
|
||||||
triggers: number;
|
|
||||||
functions: number;
|
|
||||||
documentsSize: number;
|
|
||||||
collectionSize: number;
|
|
||||||
documentsCount: number;
|
|
||||||
usageSizeInKB: number;
|
|
||||||
numPartitions: number;
|
|
||||||
uniqueKeyPolicy?: UniqueKeyPolicy; // TODO: This should ideally not be a part of the collection quota. Remove after refactoring. (#119617)
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OfferThroughputInfo {
|
export interface OfferThroughputInfo {
|
||||||
minimumRUForCollection: number;
|
minimumRUForCollection: number;
|
||||||
numPhysicalPartitions: number;
|
numPhysicalPartitions: number;
|
||||||
@@ -216,18 +204,6 @@ export interface UniqueKey {
|
|||||||
paths: string[];
|
paths: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned by DocumentDb client proxy
|
|
||||||
// Inner errors in BackendErrorDataModel when error is in SQL syntax
|
|
||||||
export interface ErrorDataModel {
|
|
||||||
message: string;
|
|
||||||
severity?: string;
|
|
||||||
location?: {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
};
|
|
||||||
code?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateDatabaseAndCollectionRequest {
|
export interface CreateDatabaseAndCollectionRequest {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -239,21 +215,12 @@ export interface CreateDatabaseAndCollectionRequest {
|
|||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
autoPilot?: AutoPilotCreationSettings;
|
autoPilot?: AutoPilotCreationSettings;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
hasAutoPilotV2FeatureFlag?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotCreationSettings {
|
export interface AutoPilotCreationSettings {
|
||||||
autopilotTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutopilotTier {
|
|
||||||
Tier1 = 1,
|
|
||||||
Tier2 = 2,
|
|
||||||
Tier3 = 3,
|
|
||||||
Tier4 = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
id: string;
|
id: string;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
@@ -262,9 +229,7 @@ export interface Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotOfferSettings {
|
export interface AutoPilotOfferSettings {
|
||||||
tier?: AutopilotTier;
|
|
||||||
maximumTierThroughput?: number;
|
maximumTierThroughput?: number;
|
||||||
targetTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
targetMaxThroughput?: number;
|
targetMaxThroughput?: number;
|
||||||
}
|
}
|
||||||
@@ -491,7 +456,6 @@ export interface MongoParameters extends RpParameters {
|
|||||||
rid?: string;
|
rid?: string;
|
||||||
rtype?: string;
|
rtype?: string;
|
||||||
isAutoPilot?: Boolean;
|
isAutoPilot?: Boolean;
|
||||||
autoPilotTier?: string;
|
|
||||||
autoPilotThroughput?: string;
|
autoPilotThroughput?: string;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export interface LogEntry {
|
|||||||
/**
|
/**
|
||||||
* The message code.
|
* The message code.
|
||||||
*/
|
*/
|
||||||
code?: number;
|
code?: number | string;
|
||||||
/**
|
/**
|
||||||
* Any additional data to be logged.
|
* Any additional data to be logged.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ export interface Collection extends CollectionBase {
|
|||||||
analyticalStorageTtl: ko.Observable<number>;
|
analyticalStorageTtl: ko.Observable<number>;
|
||||||
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
|
||||||
offer: ko.Observable<DataModels.Offer>;
|
offer: ko.Observable<DataModels.Offer>;
|
||||||
conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||||
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||||
@@ -289,6 +288,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SettingsTabV2Options extends TabOptions {
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConflictsTabOptions extends TabOptions {
|
export interface ConflictsTabOptions extends TabOptions {
|
||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
conflictIds: ko.ObservableArray<ConflictId>;
|
conflictIds: ko.ObservableArray<ConflictId>;
|
||||||
|
|||||||
@@ -123,8 +123,4 @@ describe("Component Registerer", () => {
|
|||||||
it("should register dynamic-list component", () => {
|
it("should register dynamic-list component", () => {
|
||||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register throughput-input component", () => {
|
|
||||||
expect(ko.components.isRegistered("throughput-input")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
|||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
||||||
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
||||||
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
|
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
|
|
||||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||||
@@ -23,7 +22,6 @@ ko.components.register("editor", new EditorComponent());
|
|||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input", ThroughputInputComponent);
|
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
||||||
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
||||||
import {
|
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
IContextualMenuItem,
|
|
||||||
IContextualMenuProps,
|
|
||||||
ContextualMenuItemType
|
|
||||||
} from "office-ui-fabric-react/lib/ContextualMenu";
|
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface ArcadiaMenuPickerProps {
|
export interface ArcadiaMenuPickerProps {
|
||||||
selectText?: string;
|
selectText?: string;
|
||||||
@@ -47,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
|
|||||||
selectedSparkPool: item.text
|
selectedSparkPool: item.text
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
|
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
|
||||||
|
|
||||||
|
describe("CollapsibleSectionComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: CollapsibleSectionProps = {
|
||||||
|
title: "Sample title"
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
|
export interface CollapsibleSectionProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollapsibleSectionState {
|
||||||
|
isExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
|
||||||
|
constructor(props: CollapsibleSectionProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isExpanded: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleCollapsed = (): void => {
|
||||||
|
this.setState({ isExpanded: !this.state.isExpanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
||||||
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
||||||
|
<Label>{this.props.title}</Label>
|
||||||
|
</Stack>
|
||||||
|
{this.state.isExpanded && this.props.children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
|
<Fragment>
|
||||||
|
<Stack
|
||||||
|
className="collapsibleSection"
|
||||||
|
horizontal={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledIconBase
|
||||||
|
iconName="ChevronDown"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingTop": 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledLabelBase>
|
||||||
|
Sample title
|
||||||
|
</StyledLabelBase>
|
||||||
|
</Stack>
|
||||||
|
</Fragment>
|
||||||
|
`;
|
||||||
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -71,9 +69,10 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
params: Map<string, string>
|
params: Map<string, string>
|
||||||
): string {
|
): string {
|
||||||
if (!serverInfo.notebookServerEndpoint) {
|
if (!serverInfo.notebookServerEndpoint) {
|
||||||
const error = "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.";
|
handleError(
|
||||||
Logger.logError(error, "NotebookTerminalComponent/createNotebookAppSrc");
|
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
"NotebookTerminalComponent/createNotebookAppSrc"
|
||||||
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
showDownload: true,
|
showDownload: true,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface CodeOfConductComponentProps {
|
export interface CodeOfConductComponentProps {
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
@@ -45,9 +44,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
|||||||
|
|
||||||
this.props.onAcceptCodeOfConduct(response.data);
|
this.props.onAcceptCodeOfConduct(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to accept code of conduct: ${error}`;
|
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||||
Logger.logError(message, "CodeOfConductComponent/acceptCodeOfConduct");
|
|
||||||
logConsoleError(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
IPivotItemProps,
|
IPivotItemProps,
|
||||||
@@ -11,14 +12,12 @@ import {
|
|||||||
Pivot,
|
Pivot,
|
||||||
PivotItem,
|
PivotItem,
|
||||||
SearchBox,
|
SearchBox,
|
||||||
Stack
|
Stack,
|
||||||
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
import "./GalleryViewerComponent.less";
|
import "./GalleryViewerComponent.less";
|
||||||
@@ -26,6 +25,7 @@ import { HttpStatusCodes } from "../../../Common/Constants";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface GalleryViewerComponentProps {
|
export interface GalleryViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -151,7 +151,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
// 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.
|
||||||
if (this.state.isCodeOfConductAccepted !== false) {
|
if (this.state.isCodeOfConductAccepted !== false) {
|
||||||
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +197,59 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createTabContent(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
content: this.createPublishedNotebooksTabContent(data)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
|
||||||
|
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
|
||||||
|
const content = (
|
||||||
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
|
{published?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
undefined,
|
||||||
|
"You have successfully published the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.",
|
||||||
|
this.createCardsTabContent(published)
|
||||||
|
)}
|
||||||
|
{underReview?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Under Review",
|
||||||
|
"Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)",
|
||||||
|
this.createCardsTabContent(underReview)
|
||||||
|
)}
|
||||||
|
{removed?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Removed",
|
||||||
|
"These notebooks were found to contain illegal content and has been taken down.",
|
||||||
|
this.createPolicyViolationsListContent(removed)
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.createSearchBarHeader(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksSectionContent = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
content: JSX.Element
|
||||||
|
): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
|
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
|
||||||
|
{description && <Text>{description}</Text>}
|
||||||
|
{content}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
||||||
return acceptedCodeOfConduct === false ? (
|
return acceptedCodeOfConduct === false ? (
|
||||||
<CodeOfConductComponent
|
<CodeOfConductComponent
|
||||||
@@ -210,11 +259,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
this.createTabContent(data)
|
this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTabContent(data: IGalleryItem[]): JSX.Element {
|
private createSearchBarHeader(content: JSX.Element): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
||||||
@@ -233,7 +282,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{data && this.createCardsTabContent(data)}
|
<Stack.Item>{content}</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -251,6 +300,25 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Policy violations</th>
|
||||||
|
</tr>
|
||||||
|
{data.map(item => (
|
||||||
|
<tr key={`policy-violations-tr-${item.id}`}>
|
||||||
|
<td>{item.name}</td>
|
||||||
|
<td>{item.policyViolations.join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case GalleryTab.OfficialSamples:
|
case GalleryTab.OfficialSamples:
|
||||||
@@ -284,9 +352,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.sampleNotebooks = response.data;
|
this.sampleNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load sample notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadSampleNotebooks", "Failed to load sample notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadSampleNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,9 +378,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load public notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublicNotebooks", "Failed to load public notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +398,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.favoriteNotebooks = response.data;
|
this.favoriteNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load favorite notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadFavoriteNotebooks", "Failed to load favorite notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadFavoriteNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,9 +424,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.publishedNotebooks = response.data;
|
this.publishedNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load published notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublishedNotebooks", "Failed to load published notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublishedNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
|
|||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
{this.props.onReportAbuseClick !== undefined && (
|
{this.props.onReportAbuseClick && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -81,6 +81,21 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</StackItem>
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<StackItem>
|
||||||
|
<FocusZone
|
||||||
|
direction={2}
|
||||||
|
isCircularNavigation={false}
|
||||||
|
shouldRaiseClicks={true}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
getPageSpecification={[Function]}
|
||||||
|
onRenderCell={[Function]}
|
||||||
|
renderedWindowsAhead={3}
|
||||||
|
renderedWindowsBehind={2}
|
||||||
|
startIndex={0}
|
||||||
|
/>
|
||||||
|
</FocusZone>
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</StyledPivotBase>
|
</StyledPivotBase>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
@@ -48,7 +50,9 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import Explorer from "../../Explorer";
|
|||||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||||
import { DialogHost } from "../../../Utils/GalleryUtils";
|
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookViewerComponentProps {
|
export interface NotebookViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -100,9 +101,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ showProgressBar: false });
|
this.setState({ showProgressBar: false });
|
||||||
const message = `Failed to load notebook content: ${error}`;
|
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
|
||||||
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso
|
|||||||
|
|
||||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface QueriesGridComponentProps {
|
export interface QueriesGridComponentProps {
|
||||||
queriesClient: QueriesClient;
|
queriesClient: QueriesClient;
|
||||||
@@ -244,7 +245,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
databaseAccountName: container && container.databaseAccount().name,
|
databaseAccountName: container && container.databaseAccount().name,
|
||||||
defaultExperience: container && container.defaultExperience(),
|
defaultExperience: container && container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
paneTitle: container && container.browseQueriesPane.title()
|
paneTitle: container && container.browseQueriesPane.title(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import * as DataModels from "../../../Contracts/DataModels";
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { TtlType, isDirty } from "./SettingsUtils";
|
import { TtlType, isDirty } from "./SettingsUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
|
||||||
|
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||||
|
}));
|
||||||
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||||
updateCollection: jest.fn().mockReturnValue({
|
updateCollection: jest.fn().mockReturnValue({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -18,9 +21,17 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
changeFeedPolicy: undefined,
|
changeFeedPolicy: undefined,
|
||||||
analyticalStorageTtl: undefined,
|
analyticalStorageTtl: undefined,
|
||||||
geospatialConfig: undefined
|
geospatialConfig: undefined
|
||||||
} as DataModels.Collection)
|
} as DataModels.Collection),
|
||||||
|
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
||||||
|
id: undefined,
|
||||||
|
shardKey: undefined,
|
||||||
|
indexes: [],
|
||||||
|
analyticalStorageTtl: undefined
|
||||||
|
} as MongoDBCollectionResource)
|
||||||
}));
|
}));
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
|
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import Q from "q";
|
||||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
@@ -35,7 +46,10 @@ describe("SettingsComponent", () => {
|
|||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: undefined
|
onUpdateTabsButtons: undefined,
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
|
||||||
|
return;
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,13 +202,17 @@ describe("SettingsComponent", () => {
|
|||||||
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("save calls updateCollection and updateOffer", async () => {
|
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => {
|
||||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
|
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||||
|
settingsComponentInstance.mongoDBCollectionResource = {
|
||||||
|
id: "id"
|
||||||
|
};
|
||||||
await settingsComponentInstance.onSaveClick();
|
await settingsComponentInstance.onSaveClick();
|
||||||
expect(updateCollection).toBeCalled();
|
expect(updateCollection).toBeCalled();
|
||||||
|
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
||||||
expect(updateOffer).toBeCalled();
|
expect(updateOffer).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,49 @@
|
|||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
|
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||||
import Explorer from "../../Explorer";
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { trace, traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../../UserContext";
|
import Explorer from "../../Explorer";
|
||||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
|
||||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||||
import { throughputUnit } from "./SettingsRenderUtils";
|
import "./SettingsComponent.less";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
|
||||||
import {
|
|
||||||
getMaxRUs,
|
|
||||||
hasDatabaseSharedThroughput,
|
|
||||||
GeospatialConfigType,
|
|
||||||
TtlType,
|
|
||||||
ChangeFeedPolicyState,
|
|
||||||
SettingsV2TabTypes,
|
|
||||||
getTabTitle,
|
|
||||||
isDirty,
|
|
||||||
parseConflictResolutionMode,
|
|
||||||
parseConflictResolutionProcedure
|
|
||||||
} from "./SettingsUtils";
|
|
||||||
import {
|
import {
|
||||||
ConflictResolutionComponent,
|
ConflictResolutionComponent,
|
||||||
ConflictResolutionComponentProps
|
ConflictResolutionComponentProps
|
||||||
} from "./SettingsSubComponents/ConflictResolutionComponent";
|
} from "./SettingsSubComponents/ConflictResolutionComponent";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
|
||||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
|
||||||
import "./SettingsComponent.less";
|
|
||||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||||
|
import {
|
||||||
|
MongoIndexingPolicyComponent,
|
||||||
|
MongoIndexingPolicyComponentProps
|
||||||
|
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||||
|
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||||
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
|
||||||
|
import {
|
||||||
|
AddMongoIndexProps,
|
||||||
|
ChangeFeedPolicyState,
|
||||||
|
GeospatialConfigType,
|
||||||
|
getMongoNotification,
|
||||||
|
getTabTitle,
|
||||||
|
hasDatabaseSharedThroughput,
|
||||||
|
isDirty,
|
||||||
|
MongoIndexTypes,
|
||||||
|
parseConflictResolutionMode,
|
||||||
|
parseConflictResolutionProcedure,
|
||||||
|
SettingsV2TabTypes,
|
||||||
|
TtlType
|
||||||
|
} from "./SettingsUtils";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
tab: SettingsV2TabTypes;
|
tab: SettingsV2TabTypes;
|
||||||
@@ -84,6 +90,13 @@ export interface SettingsComponentState {
|
|||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
isIndexingPolicyDirty: boolean;
|
isIndexingPolicyDirty: boolean;
|
||||||
|
|
||||||
|
isMongoIndexingPolicySaveable: boolean;
|
||||||
|
isMongoIndexingPolicyDiscardable: boolean;
|
||||||
|
currentMongoIndexes: MongoIndex[];
|
||||||
|
indexesToDrop: number[];
|
||||||
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
@@ -98,17 +111,16 @@ export interface SettingsComponentState {
|
|||||||
|
|
||||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||||
private static readonly sixMonthsInSeconds = 15768000;
|
private static readonly sixMonthsInSeconds = 15768000;
|
||||||
|
private saveSettingsButton: ButtonV2;
|
||||||
|
private discardSettingsChangesButton: ButtonV2;
|
||||||
|
|
||||||
public saveSettingsButton: ButtonV2;
|
private isAnalyticalStorageEnabled: boolean;
|
||||||
public discardSettingsChangesButton: ButtonV2;
|
|
||||||
|
|
||||||
public isAnalyticalStorageEnabled: boolean;
|
|
||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -158,6 +170,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
shouldDiscardIndexingPolicy: false,
|
shouldDiscardIndexingPolicy: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: undefined,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
|
||||||
conflictResolutionPolicyMode: undefined,
|
conflictResolutionPolicyMode: undefined,
|
||||||
conflictResolutionPolicyModeBaseline: undefined,
|
conflictResolutionPolicyModeBaseline: undefined,
|
||||||
conflictResolutionPolicyPath: undefined,
|
conflictResolutionPolicyPath: undefined,
|
||||||
@@ -186,6 +205,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
this.refreshIndexTransformationProgress();
|
||||||
|
this.loadMongoIndexes();
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
@@ -199,6 +220,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadMongoIndexes = async (): Promise<void> => {
|
||||||
|
if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent() &&
|
||||||
|
this.container.databaseAccount()
|
||||||
|
) {
|
||||||
|
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.mongoDBCollectionResource) {
|
||||||
|
this.setState({
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||||
|
const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
|
||||||
|
this.setState({ indexTransformationProgress: currentProgress });
|
||||||
|
};
|
||||||
|
|
||||||
public isSaveSettingsButtonEnabled = (): boolean => {
|
public isSaveSettingsButtonEnabled = (): boolean => {
|
||||||
if (this.isOfferReplacePending()) {
|
if (this.isOfferReplacePending()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -208,7 +254,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,7 +264,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleDiscardable ||
|
this.state.isScaleDiscardable ||
|
||||||
this.state.isSubSettingsDiscardable ||
|
this.state.isSubSettingsDiscardable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -293,6 +341,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||||
newCollection.defaultTtl = defaultTtl;
|
newCollection.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||||
@@ -328,6 +377,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
|
|
||||||
|
if (wasIndexingPolicyModified) {
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
@@ -336,10 +390,61 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
|
try {
|
||||||
|
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||||
|
const newMongoCollection: MongoDBCollectionResource = {
|
||||||
|
...this.mongoDBCollectionResource,
|
||||||
|
indexes: newMongoIndexes
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id(),
|
||||||
|
newMongoCollection
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
this.setState({
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
traceSuccess(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.isScaleSaveable) {
|
if (this.state.isScaleSaveable) {
|
||||||
const newThroughput = this.state.throughput;
|
const newThroughput = this.state.throughput;
|
||||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||||
const originalThroughputValue: number = this.state.throughput;
|
|
||||||
|
|
||||||
if (newOffer.content) {
|
if (newOffer.content) {
|
||||||
newOffer.content.offerThroughput = newThroughput;
|
newOffer.content.offerThroughput = newThroughput;
|
||||||
@@ -377,78 +482,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
getMaxRUs(this.collection, this.container) <=
|
databaseId: this.collection.databaseId,
|
||||||
SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
collectionId: this.collection.id(),
|
||||||
newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
currentOffer: this.collection.offer(),
|
||||||
this.container
|
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||||
) {
|
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
|
||||||
const requestPayload = {
|
};
|
||||||
subscriptionId: userContext.subscriptionId,
|
if (this.hasProvisioningTypeChanged()) {
|
||||||
databaseAccountName: userContext.databaseAccount.name,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
databaseName: this.collection.databaseId,
|
|
||||||
collectionName: this.collection.id(),
|
|
||||||
throughput: newThroughput,
|
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
|
||||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
|
||||||
this.setState({
|
|
||||||
throughput: originalThroughputValue,
|
|
||||||
throughputBaseline: originalThroughputValue,
|
|
||||||
initialNotification: {
|
|
||||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
|
||||||
} as DataModels.Notification
|
|
||||||
});
|
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.SettingsV2Updated,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
|
||||||
databaseId: this.collection.databaseId,
|
|
||||||
collectionId: this.collection.id(),
|
|
||||||
currentOffer: this.collection.offer(),
|
|
||||||
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
|
||||||
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
|
|
||||||
};
|
|
||||||
if (this.hasProvisioningTypeChanged()) {
|
|
||||||
if (this.state.isAutoPilotSelected) {
|
|
||||||
updateOfferParams.migrateToAutoPilot = true;
|
|
||||||
} else {
|
|
||||||
updateOfferParams.migrateToManual = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
|
||||||
this.collection.offer(updatedOffer);
|
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
|
||||||
if (this.state.isAutoPilotSelected) {
|
if (this.state.isAutoPilotSelected) {
|
||||||
this.setState({
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
|
||||||
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
updateOfferParams.migrateToManual = true;
|
||||||
throughput: updatedOffer.content.offerThroughput,
|
|
||||||
throughputBaseline: updatedOffer.content.offerThroughput
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
|
this.collection.offer(updatedOffer);
|
||||||
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
this.setState({
|
||||||
|
autoPilotThroughput: updatedOffer.content.offerAutopilotSettings.maxThroughput,
|
||||||
|
autoPilotThroughputBaseline: updatedOffer.content.offerAutopilotSettings.maxThroughput
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
throughput: updatedOffer.content.offerThroughput,
|
||||||
|
throughputBaseline: updatedOffer.content.offerThroughput
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
@@ -457,23 +518,29 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
Action.SettingsV2Updated,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
} catch (reason) {
|
} catch (error) {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.props.settingsTab.isExecutionError(true);
|
this.props.settingsTab.isExecutionError(true);
|
||||||
console.error(reason);
|
console.error(error);
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.SettingsV2Updated,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
@@ -492,6 +559,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||||
|
indexesToAdd: [],
|
||||||
|
indexesToDrop: [],
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
||||||
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
||||||
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
||||||
@@ -506,10 +575,23 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
isConflictResolutionDirty: false
|
isConflictResolutionDirty: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||||
|
let finalIndexes: MongoIndex[] = [];
|
||||||
|
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||||
|
if (!this.state.indexesToDrop.includes(index)) {
|
||||||
|
finalIndexes.push(mongoIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
finalIndexes = finalIndexes.concat(this.state.indexesToAdd.map((m: AddMongoIndexProps) => m.mongoIndex));
|
||||||
|
return finalIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
||||||
this.setState({ isScaleSaveable: isScaleSaveable });
|
this.setState({ isScaleSaveable: isScaleSaveable });
|
||||||
|
|
||||||
@@ -539,6 +621,36 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onIndexDrop = (index: number): void => this.setState({ indexesToDrop: [...this.state.indexesToDrop, index] });
|
||||||
|
|
||||||
|
private onRevertIndexDrop = (index: number): void => {
|
||||||
|
const indexesToDrop = [...this.state.indexesToDrop];
|
||||||
|
indexesToDrop.splice(index, 1);
|
||||||
|
this.setState({ indexesToDrop });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onRevertIndexAdd = (index: number): void => {
|
||||||
|
const indexesToAdd = [...this.state.indexesToAdd];
|
||||||
|
indexesToAdd.splice(index, 1);
|
||||||
|
this.setState({ indexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onIndexAddOrChange = (index: number, description: string, type: MongoIndexTypes): void => {
|
||||||
|
const newIndexesToAdd = [...this.state.indexesToAdd];
|
||||||
|
const notification = getMongoNotification(description, type);
|
||||||
|
const newMongoIndexWithType: AddMongoIndexProps = {
|
||||||
|
mongoIndex: { key: { keys: [description] } } as MongoIndex,
|
||||||
|
type: type,
|
||||||
|
notification: notification
|
||||||
|
};
|
||||||
|
if (index === newIndexesToAdd.length) {
|
||||||
|
newIndexesToAdd.push(newMongoIndexWithType);
|
||||||
|
} else {
|
||||||
|
newIndexesToAdd[index] = newMongoIndexWithType;
|
||||||
|
}
|
||||||
|
this.setState({ indexesToAdd: newIndexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||||
this.setState({ conflictResolutionPolicyMode: newMode });
|
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||||
|
|
||||||
@@ -577,6 +689,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicySaveable });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
public getAnalyticalStorageTtl = (): number => {
|
public getAnalyticalStorageTtl = (): number => {
|
||||||
if (this.isAnalyticalStorageEnabled) {
|
if (this.isAnalyticalStorageEnabled) {
|
||||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||||
@@ -747,7 +865,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
autoPilotTiersList: this.autoPilotTiersList,
|
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
throughput: this.state.throughput,
|
throughput: this.state.throughput,
|
||||||
throughputBaseline: this.state.throughputBaseline,
|
throughputBaseline: this.state.throughputBaseline,
|
||||||
@@ -796,9 +913,25 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
||||||
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
||||||
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: this.state.currentMongoIndexes,
|
||||||
|
onIndexDrop: this.onIndexDrop,
|
||||||
|
indexesToDrop: this.state.indexesToDrop,
|
||||||
|
onRevertIndexDrop: this.onRevertIndexDrop,
|
||||||
|
indexesToAdd: this.state.indexesToAdd,
|
||||||
|
onRevertIndexAdd: this.onRevertIndexAdd,
|
||||||
|
onIndexAddOrChange: this.onIndexAddOrChange,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
|
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
|
||||||
|
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
|
||||||
|
};
|
||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
@@ -815,7 +948,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs: SettingsV2TabInfo[] = [];
|
const tabs: SettingsV2TabInfo[] = [];
|
||||||
if (!hasDatabaseSharedThroughput(this.collection)) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ScaleTab,
|
tab: SettingsV2TabTypes.ScaleTab,
|
||||||
content: <ScaleComponent {...scaleComponentProps} />
|
content: <ScaleComponent {...scaleComponentProps} />
|
||||||
@@ -832,6 +965,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent()
|
||||||
|
) {
|
||||||
|
tabs.push({
|
||||||
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
|
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasConflictResolution()) {
|
if (this.hasConflictResolution()) {
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|||||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
||||||
|
|
||||||
export class SettingsComponentAdapter implements ReactAdapter {
|
export class SettingsComponentAdapter implements ReactAdapter {
|
||||||
public parameters: ko.Observable<number>;
|
public parameters: ko.Computed<boolean>;
|
||||||
|
|
||||||
constructor(private props: SettingsComponentProps) {
|
constructor(private props: SettingsComponentProps) {}
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
return <SettingsComponent {...this.props} />;
|
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getEstimatedAutoscaleSpendElement,
|
getEstimatedAutoscaleSpendElement,
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicyTTLWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
updateThroughputBeyondLimitWarningMessage,
|
updateThroughputBeyondLimitWarningMessage,
|
||||||
updateThroughputDelayedApplyWarningMessage,
|
updateThroughputDelayedApplyWarningMessage,
|
||||||
getThroughputApplyDelayedMessage,
|
getThroughputApplyDelayedMessage,
|
||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
getToolTipContainer,
|
getToolTipContainer,
|
||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
conflictResolutionLwwTooltip
|
conflictResolutionLwwTooltip,
|
||||||
|
mongoIndexingPolicyDisclaimer,
|
||||||
|
mongoIndexingPolicyAADError,
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
@@ -33,7 +37,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
{indexingPolicyTTLWarningMessage}
|
{indexingPolicynUnsavedWarningMessage}
|
||||||
{updateThroughputBeyondLimitWarningMessage}
|
{updateThroughputBeyondLimitWarningMessage}
|
||||||
{updateThroughputDelayedApplyWarningMessage}
|
{updateThroughputDelayedApplyWarningMessage}
|
||||||
|
|
||||||
@@ -45,6 +49,16 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{conflictResolutionLwwTooltip}
|
{conflictResolutionLwwTooltip}
|
||||||
{conflictResolutionCustomToolTip}
|
{conflictResolutionCustomToolTip}
|
||||||
{changeFeedPolicyToolTip}
|
{changeFeedPolicyToolTip}
|
||||||
|
|
||||||
|
{mongoIndexingPolicyDisclaimer}
|
||||||
|
{mongoIndexingPolicyAADError}
|
||||||
|
{mongoIndexTransformationRefreshingMessage}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(0, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(90, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,24 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
IMessageBarStyles,
|
IMessageBarStyles,
|
||||||
ITextStyles
|
ITextStyles,
|
||||||
|
IDetailsRowStyles,
|
||||||
|
IStackStyles,
|
||||||
|
IIconStyles,
|
||||||
|
IDetailsListStyles,
|
||||||
|
IDropdownStyles,
|
||||||
|
ISeparatorStyles,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||||
|
|
||||||
export const spendAckCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: "2 0 2 0"
|
padding: "2 0 2 0"
|
||||||
@@ -45,6 +56,20 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
|
|||||||
tokens: { childrenGap: 5 }
|
tokens: { childrenGap: 5 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mongoWarningStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
|
||||||
|
|
||||||
|
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 10 }
|
||||||
|
};
|
||||||
|
|
||||||
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
||||||
tokens: { childrenGap: 10 }
|
tokens: { childrenGap: 10 }
|
||||||
};
|
};
|
||||||
@@ -53,6 +78,54 @@ export const toolTipLabelStackTokens: IStackTokens = {
|
|||||||
childrenGap: 6
|
childrenGap: 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const accordionStackTokens: IStackTokens = {
|
||||||
|
childrenGap: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexSubElementsTokens: IStackTokens = {
|
||||||
|
childrenGap: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
||||||
|
|
||||||
|
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||||
|
|
||||||
|
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||||
|
|
||||||
|
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
||||||
|
|
||||||
|
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
background: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
".ms-FocusZone": {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const separatorStyles: Partial<ISeparatorStyles> = {
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
selectors: {
|
||||||
|
"::before": {
|
||||||
|
background: StyleConstants.BaseMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||||
|
|
||||||
export const throughputUnit = "RU/s";
|
export const throughputUnit = "RU/s";
|
||||||
@@ -172,15 +245,9 @@ export const ttlWarning: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const indexingPolicyTTLWarningMessage: JSX.Element = (
|
export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and
|
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||||
the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
|
|
||||||
more information see,{" "}
|
|
||||||
<Link target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">
|
|
||||||
Modifying Indexing Policies
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -313,6 +380,56 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
||||||
|
{` Compound indexes `}
|
||||||
|
</Link>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||||
|
shell.
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
<Text>
|
||||||
|
To use the indexing policy editor, please login to the
|
||||||
|
<Link target="_blank" href="https://portal.azure.com">
|
||||||
|
{"azure portal."}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||||
|
<Stack horizontal {...mongoWarningStackProps}>
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
|
||||||
|
<Spinner size={SpinnerSize.small} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const renderMongoIndexTransformationRefreshMessage = (
|
||||||
|
progress: number,
|
||||||
|
performRefresh: () => void
|
||||||
|
): JSX.Element => {
|
||||||
|
if (progress === 0) {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
height: 25,
|
height: 25,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
|
|||||||
},
|
},
|
||||||
onIndexingPolicyDirtyChange: () => {
|
onIndexingPolicyDirtyChange: () => {
|
||||||
return;
|
return;
|
||||||
}
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
import { isDirty } from "../SettingsUtils";
|
import { isDirty, isIndexTransforming } from "../SettingsUtils";
|
||||||
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
|
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
|
||||||
import { indexingPolicyTTLWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
|
||||||
|
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
export interface IndexingPolicyComponentProps {
|
export interface IndexingPolicyComponentProps {
|
||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
@@ -12,6 +13,8 @@ export interface IndexingPolicyComponentProps {
|
|||||||
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
||||||
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
|
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
|
||||||
logIndexingPolicySuccessMessage: () => void;
|
logIndexingPolicySuccessMessage: () => void;
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +54,9 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
if (!this.indexingPolicyEditor) {
|
if (!this.indexingPolicyEditor) {
|
||||||
this.createIndexingPolicyEditor();
|
this.createIndexingPolicyEditor();
|
||||||
} else {
|
} else {
|
||||||
|
this.indexingPolicyEditor.updateOptions({
|
||||||
|
readOnly: isIndexTransforming(this.props.indexTransformationProgress)
|
||||||
|
});
|
||||||
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
|
||||||
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
|
||||||
indexingPolicyEditorModel.setValue(value);
|
indexingPolicyEditorModel.setValue(value);
|
||||||
@@ -84,7 +90,7 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
|
||||||
value: value,
|
value: value,
|
||||||
language: "json",
|
language: "json",
|
||||||
readOnly: false,
|
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
|
||||||
ariaLabel: "Indexing Policy"
|
ariaLabel: "Indexing Policy"
|
||||||
});
|
});
|
||||||
if (this.indexingPolicyEditor) {
|
if (this.indexingPolicyEditor) {
|
||||||
@@ -108,8 +114,12 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
|
/>
|
||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicyTTLWarningMessage}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar>
|
||||||
)}
|
)}
|
||||||
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { IndexingPolicyRefreshComponentProps, IndexingPolicyRefreshComponent } from "./IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
|
describe("IndexingPolicyRefreshComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: IndexingPolicyRefreshComponentProps = {
|
||||||
|
indexTransformationProgress: 90,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<IndexingPolicyRefreshComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
|
||||||
|
import { isIndexTransforming } from "../../SettingsUtils";
|
||||||
|
|
||||||
|
export interface IndexingPolicyRefreshComponentProps {
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IndexingPolicyRefreshComponentState {
|
||||||
|
isRefreshing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IndexingPolicyRefreshComponent extends React.Component<
|
||||||
|
IndexingPolicyRefreshComponentProps,
|
||||||
|
IndexingPolicyRefreshComponentState
|
||||||
|
> {
|
||||||
|
constructor(props: IndexingPolicyRefreshComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isRefreshing: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
|
||||||
|
|
||||||
|
private renderIndexTransformationWarning = (): JSX.Element => {
|
||||||
|
if (this.state.isRefreshing) {
|
||||||
|
return mongoIndexTransformationRefreshingMessage;
|
||||||
|
} else if (isIndexTransforming(this.props.indexTransformationProgress)) {
|
||||||
|
return renderMongoIndexTransformationRefreshMessage(
|
||||||
|
this.props.indexTransformationProgress,
|
||||||
|
this.onClickRefreshIndexingTransformationLink
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private refreshIndexTransformationProgress = async () => {
|
||||||
|
this.setState({ isRefreshing: true });
|
||||||
|
try {
|
||||||
|
await this.props.refreshIndexTransformationProgress();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "RefreshIndexTransformationProgress", "Refreshing index transformation progress failed");
|
||||||
|
} finally {
|
||||||
|
this.setState({ isRefreshing: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return this.renderIndexTransformationWarning() ? (
|
||||||
|
<MessageBar messageBarType={MessageBarType.warning}>{this.renderIndexTransformationWarning()}</MessageBar>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={5}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||||
|
<StyledLinkBase
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Refresh to check the progress.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { MongoIndexTypes, MongoNotificationType } from "../../SettingsUtils";
|
||||||
|
import { AddMongoIndexComponent, AddMongoIndexComponentProps } from "./AddMongoIndexComponent";
|
||||||
|
|
||||||
|
describe("AddMongoIndexComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: AddMongoIndexComponentProps = {
|
||||||
|
position: 1,
|
||||||
|
description: "sample_key",
|
||||||
|
type: MongoIndexTypes.Single,
|
||||||
|
notification: { type: MongoNotificationType.Error, message: "sample error" },
|
||||||
|
onIndexAddOrChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onDiscard: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<AddMongoIndexComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
TextField,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
ITextField
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
addMongoIndexSubElementsTokens,
|
||||||
|
mongoErrorMessageStyles,
|
||||||
|
mongoWarningStackProps,
|
||||||
|
shortWidthDropDownStyles,
|
||||||
|
shortWidthTextFieldStyles
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import {
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
MongoIndexTypes,
|
||||||
|
MongoNotificationMessage,
|
||||||
|
MongoNotificationType,
|
||||||
|
MongoWildcardPlaceHolder
|
||||||
|
} from "../../SettingsUtils";
|
||||||
|
|
||||||
|
export interface AddMongoIndexComponentProps {
|
||||||
|
position: number;
|
||||||
|
description: string;
|
||||||
|
type: MongoIndexTypes;
|
||||||
|
notification: MongoNotificationMessage;
|
||||||
|
onIndexAddOrChange: (description: string, type: MongoIndexTypes) => void;
|
||||||
|
onDiscard: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddMongoIndexComponent extends React.Component<AddMongoIndexComponentProps> {
|
||||||
|
private descriptionTextField: ITextField;
|
||||||
|
private indexTypes: IDropdownOption[] = [MongoIndexTypes.Single, MongoIndexTypes.Wildcard].map(
|
||||||
|
(value: MongoIndexTypes) => ({
|
||||||
|
text: getMongoIndexTypeText(value),
|
||||||
|
key: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
private onDescriptionChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => {
|
||||||
|
this.props.onIndexAddOrChange(newValue, this.props.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onTypeChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||||
|
const newType = MongoIndexTypes[option.key as keyof typeof MongoIndexTypes];
|
||||||
|
this.props.onIndexAddOrChange(this.props.description, newType);
|
||||||
|
};
|
||||||
|
|
||||||
|
private setRef = (textField: ITextField) => (this.descriptionTextField = textField);
|
||||||
|
|
||||||
|
public focus = (): void => {
|
||||||
|
this.descriptionTextField.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Stack {...mongoWarningStackProps}>
|
||||||
|
<Stack horizontal tokens={addMongoIndexSubElementsTokens}>
|
||||||
|
<TextField
|
||||||
|
ariaLabel={"Index Field Name " + this.props.position}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
styles={shortWidthTextFieldStyles}
|
||||||
|
componentRef={this.setRef}
|
||||||
|
value={this.props.description}
|
||||||
|
placeholder={this.props.type === MongoIndexTypes.Wildcard ? MongoWildcardPlaceHolder : undefined}
|
||||||
|
onChange={this.onDescriptionChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
ariaLabel={"Index Type " + this.props.position}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
styles={shortWidthDropDownStyles}
|
||||||
|
placeholder="Select an index type"
|
||||||
|
selectedKey={this.props.type}
|
||||||
|
options={this.indexTypes}
|
||||||
|
onChange={this.onTypeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
ariaLabel={"Undo Button " + this.props.position}
|
||||||
|
iconProps={{ iconName: "Undo" }}
|
||||||
|
disabled={!this.props.description && !this.props.type}
|
||||||
|
onClick={() => this.props.onDiscard()}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
{this.props.notification?.type === MongoNotificationType.Error && (
|
||||||
|
<MessageBar styles={mongoErrorMessageStyles} messageBarType={MessageBarType.error}>
|
||||||
|
{this.props.notification.message}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
|
||||||
|
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
|
||||||
|
import { renderToString } from "react-dom/server";
|
||||||
|
|
||||||
|
describe("MongoIndexingPolicyComponent", () => {
|
||||||
|
const baseProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: [],
|
||||||
|
onIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToDrop: [],
|
||||||
|
onRevertIndexDrop: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexesToAdd: [],
|
||||||
|
onRevertIndexAdd: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onIndexAddOrChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn()),
|
||||||
|
onMongoIndexingPolicySaveableChange: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
onMongoIndexingPolicyDiscardableChange: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("AddMongoIndexProps test", () => {
|
||||||
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
|
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||||
|
|
||||||
|
it("defaults", () => {
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
|
||||||
|
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sampleWarning = "sampleWarning";
|
||||||
|
const sampleError = "sampleError";
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Warning, message: sampleWarning } as MongoNotificationMessage,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
sampleWarning
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[undefined, false, true, true, undefined],
|
||||||
|
[undefined, true, true, true, undefined]
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(cases)(
|
||||||
|
"",
|
||||||
|
(
|
||||||
|
notification: MongoNotificationMessage,
|
||||||
|
indexToDropIsPresent: boolean,
|
||||||
|
isMongoIndexingPolicySaveable: boolean,
|
||||||
|
isMongoIndexingPolicyDiscardable: boolean,
|
||||||
|
mongoWarningNotificationMessage: string
|
||||||
|
) => {
|
||||||
|
const addMongoIndexProps = {
|
||||||
|
mongoIndex: { key: { keys: ["sampleKey"] } },
|
||||||
|
type: MongoIndexTypes.Single,
|
||||||
|
notification: notification
|
||||||
|
};
|
||||||
|
|
||||||
|
let indexesToDrop: number[] = [];
|
||||||
|
if (indexToDropIsPresent) {
|
||||||
|
indexesToDrop = [0];
|
||||||
|
}
|
||||||
|
wrapper.setProps({ indexesToAdd: [addMongoIndexProps], indexesToDrop: indexesToDrop });
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(isMongoIndexingPolicySaveable);
|
||||||
|
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
|
||||||
|
isMongoIndexingPolicyDiscardable
|
||||||
|
);
|
||||||
|
if (mongoWarningNotificationMessage) {
|
||||||
|
const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
|
||||||
|
expect(elementAsString).toContain(mongoWarningNotificationMessage);
|
||||||
|
} else {
|
||||||
|
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
DetailsList,
|
||||||
|
DetailsListLayoutMode,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
SelectionMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
|
IColumn,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize,
|
||||||
|
Separator
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import {
|
||||||
|
addMongoIndexStackProps,
|
||||||
|
customDetailsListStyles,
|
||||||
|
mongoIndexingPolicyDisclaimer,
|
||||||
|
mediumWidthStackStyles,
|
||||||
|
subComponentStackProps,
|
||||||
|
transparentDetailsRowStyles,
|
||||||
|
createAndAddMongoIndexStackProps,
|
||||||
|
separatorStyles,
|
||||||
|
mongoIndexingPolicyAADError,
|
||||||
|
indexingPolicynUnsavedWarningMessage,
|
||||||
|
infoAndToolTipTextStyle
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import {
|
||||||
|
MongoIndexTypes,
|
||||||
|
AddMongoIndexProps,
|
||||||
|
MongoIndexIdField,
|
||||||
|
MongoNotificationType,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
isIndexTransforming
|
||||||
|
} from "../../SettingsUtils";
|
||||||
|
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
|
||||||
|
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
import { AuthType } from "../../../../../AuthType";
|
||||||
|
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
|
||||||
|
|
||||||
|
export interface MongoIndexingPolicyComponentProps {
|
||||||
|
mongoIndexes: MongoIndex[];
|
||||||
|
onIndexDrop: (index: number) => void;
|
||||||
|
indexesToDrop: number[];
|
||||||
|
onRevertIndexDrop: (index: number) => void;
|
||||||
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
|
onRevertIndexAdd: (index: number) => void;
|
||||||
|
onIndexAddOrChange: (index: number, description: string, type: MongoIndexTypes) => void;
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
refreshIndexTransformationProgress: () => Promise<void>;
|
||||||
|
onMongoIndexingPolicySaveableChange: (isMongoIndexingPolicySaveable: boolean) => void;
|
||||||
|
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MongoIndexDisplayProps {
|
||||||
|
definition: JSX.Element;
|
||||||
|
type: JSX.Element;
|
||||||
|
actionButton: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingPolicyComponentProps> {
|
||||||
|
private shouldCheckComponentIsDirty = true;
|
||||||
|
private addMongoIndexComponentRefs: React.RefObject<AddMongoIndexComponent>[] = [];
|
||||||
|
private initialIndexesColumns: IColumn[] = [
|
||||||
|
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{
|
||||||
|
key: "actionButton",
|
||||||
|
name: "Drop Index",
|
||||||
|
fieldName: "actionButton",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
private indexesToBeDroppedColumns: IColumn[] = [
|
||||||
|
{ key: "definition", name: "Definition", fieldName: "definition", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{ key: "type", name: "Type", fieldName: "type", minWidth: 100, maxWidth: 200, isResizable: true },
|
||||||
|
{
|
||||||
|
key: "actionButton",
|
||||||
|
name: "Add index back",
|
||||||
|
fieldName: "actionButton",
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 200,
|
||||||
|
isResizable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
|
||||||
|
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
|
||||||
|
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
|
||||||
|
}
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.onComponentUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onComponentUpdate = (): void => {
|
||||||
|
if (!this.shouldCheckComponentIsDirty) {
|
||||||
|
this.shouldCheckComponentIsDirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onMongoIndexingPolicySaveableChange(this.isMongoIndexingPolicySaveable());
|
||||||
|
this.props.onMongoIndexingPolicyDiscardableChange(this.isMongoIndexingPolicyDiscardable());
|
||||||
|
this.shouldCheckComponentIsDirty = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public isMongoIndexingPolicySaveable = (): boolean => {
|
||||||
|
if (this.props.indexesToAdd.length === 0 && this.props.indexesToDrop.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addErrorsExist = !!this.props.indexesToAdd.find(addMongoIndexProps => addMongoIndexProps.notification);
|
||||||
|
|
||||||
|
if (addErrorsExist) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
public isMongoIndexingPolicyDiscardable = (): boolean => {
|
||||||
|
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getMongoWarningNotificationMessage = (): JSX.Element => {
|
||||||
|
const warningMessage = this.props.indexesToAdd.find(
|
||||||
|
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
|
||||||
|
)?.notification.message;
|
||||||
|
|
||||||
|
if (warningMessage) {
|
||||||
|
return <Text styles={infoAndToolTipTextStyle}>{warningMessage}</Text>;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||||
|
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||||
|
return isCurrentIndex ? (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Delete index Button"
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.onIndexDrop(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="Add back Index Button"
|
||||||
|
iconProps={{ iconName: "Add" }}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.onRevertIndexDrop(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private getMongoIndexDisplayProps = (
|
||||||
|
mongoIndex: MongoIndex,
|
||||||
|
arrayPosition: number,
|
||||||
|
isCurrentIndex: boolean
|
||||||
|
): MongoIndexDisplayProps => {
|
||||||
|
const keys = mongoIndex?.key?.keys;
|
||||||
|
const type = getMongoIndexType(keys);
|
||||||
|
const definition = keys?.join();
|
||||||
|
let mongoIndexDisplayProps: MongoIndexDisplayProps;
|
||||||
|
if (type) {
|
||||||
|
mongoIndexDisplayProps = {
|
||||||
|
definition: <Text>{definition}</Text>,
|
||||||
|
type: <Text>{getMongoIndexTypeText(type)}</Text>,
|
||||||
|
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return mongoIndexDisplayProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderIndexesToBeAdded = (): JSX.Element => {
|
||||||
|
const indexesToAddLength = this.props.indexesToAdd.length;
|
||||||
|
for (let i = 0; i < indexesToAddLength; i++) {
|
||||||
|
const existingIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||||
|
this.addMongoIndexComponentRefs[i] = existingIndexToAddRef;
|
||||||
|
}
|
||||||
|
const newIndexToAddRef = React.createRef<AddMongoIndexComponent>();
|
||||||
|
this.addMongoIndexComponentRefs[indexesToAddLength] = newIndexToAddRef;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
|
{this.props.indexesToAdd.map((mongoIndexWithType, arrayPosition) => {
|
||||||
|
const keys = mongoIndexWithType.mongoIndex.key.keys;
|
||||||
|
const type = mongoIndexWithType.type;
|
||||||
|
const notification = mongoIndexWithType.notification;
|
||||||
|
return (
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
ref={this.addMongoIndexComponentRefs[arrayPosition]}
|
||||||
|
position={arrayPosition}
|
||||||
|
key={arrayPosition}
|
||||||
|
description={keys.join()}
|
||||||
|
type={type}
|
||||||
|
notification={notification}
|
||||||
|
onIndexAddOrChange={(description, type) =>
|
||||||
|
this.props.onIndexAddOrChange(arrayPosition, description, type)
|
||||||
|
}
|
||||||
|
onDiscard={() => {
|
||||||
|
this.addMongoIndexComponentRefs.splice(arrayPosition, 1);
|
||||||
|
this.props.onRevertIndexAdd(arrayPosition);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
ref={this.addMongoIndexComponentRefs[indexesToAddLength]}
|
||||||
|
disabled={isIndexTransforming(this.props.indexTransformationProgress)}
|
||||||
|
position={indexesToAddLength}
|
||||||
|
key={indexesToAddLength}
|
||||||
|
description={undefined}
|
||||||
|
type={undefined}
|
||||||
|
notification={undefined}
|
||||||
|
onIndexAddOrChange={(description, type) =>
|
||||||
|
this.props.onIndexAddOrChange(indexesToAddLength, description, type)
|
||||||
|
}
|
||||||
|
onDiscard={() => {
|
||||||
|
this.props.onRevertIndexAdd(indexesToAddLength);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderInitialIndexes = (): JSX.Element => {
|
||||||
|
const initialIndexes = this.props.mongoIndexes
|
||||||
|
.map((mongoIndex, arrayPosition) => this.getMongoIndexDisplayProps(mongoIndex, arrayPosition, true))
|
||||||
|
.filter((value, arrayPosition) => !!value && !this.props.indexesToDrop.includes(arrayPosition));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack {...createAndAddMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
||||||
|
<CollapsibleSectionComponent title="Current index(es)">
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<DetailsList
|
||||||
|
styles={customDetailsListStyles}
|
||||||
|
disableSelectionZone
|
||||||
|
items={initialIndexes}
|
||||||
|
columns={this.initialIndexesColumns}
|
||||||
|
selectionMode={SelectionMode.none}
|
||||||
|
onRenderRow={this.onRenderRow}
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
/>
|
||||||
|
{this.renderIndexesToBeAdded()}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderIndexesToBeDropped = (): JSX.Element => {
|
||||||
|
const indexesToBeDropped = this.props.indexesToDrop.map((dropIndex, arrayPosition) =>
|
||||||
|
this.getMongoIndexDisplayProps(this.props.mongoIndexes[dropIndex], arrayPosition, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack styles={mediumWidthStackStyles}>
|
||||||
|
<CollapsibleSectionComponent title="Index(es) to be dropped">
|
||||||
|
{indexesToBeDropped.length > 0 && (
|
||||||
|
<DetailsList
|
||||||
|
styles={customDetailsListStyles}
|
||||||
|
disableSelectionZone
|
||||||
|
items={indexesToBeDropped}
|
||||||
|
columns={this.indexesToBeDroppedColumns}
|
||||||
|
selectionMode={SelectionMode.none}
|
||||||
|
onRenderRow={this.onRenderRow}
|
||||||
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderWarningMessage = (): JSX.Element => {
|
||||||
|
let warningMessage: JSX.Element;
|
||||||
|
if (this.getMongoWarningNotificationMessage()) {
|
||||||
|
warningMessage = this.getMongoWarningNotificationMessage();
|
||||||
|
} else if (this.isMongoIndexingPolicySaveable()) {
|
||||||
|
warningMessage = indexingPolicynUnsavedWarningMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
|
/>
|
||||||
|
{warningMessage && <MessageBar messageBarType={MessageBarType.warning}>{warningMessage}</MessageBar>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
if (this.props.mongoIndexes) {
|
||||||
|
return (
|
||||||
|
<Stack {...subComponentStackProps}>
|
||||||
|
{this.renderWarningMessage()}
|
||||||
|
{mongoIndexingPolicyDisclaimer}
|
||||||
|
{this.renderInitialIndexes()}
|
||||||
|
<Separator styles={separatorStyles} />
|
||||||
|
{this.renderIndexesToBeDropped()}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : <Spinner size={SpinnerSize.large} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AddMongoIndexComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledTextFieldBase
|
||||||
|
ariaLabel="Index Field Name 1"
|
||||||
|
componentRef={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingLeft": 10,
|
||||||
|
"width": 210,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value="sample_key"
|
||||||
|
/>
|
||||||
|
<StyledWithResponsiveMode
|
||||||
|
ariaLabel="Index Type 1"
|
||||||
|
onChange={[Function]}
|
||||||
|
options={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"key": "Single",
|
||||||
|
"text": "Single Field",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "Wildcard",
|
||||||
|
"text": "Wildcard",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
placeholder="Select an index type"
|
||||||
|
selectedKey="Single"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"dropdown": Object {
|
||||||
|
"paddingleft": 10,
|
||||||
|
"width": 202,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CustomizedIconButton
|
||||||
|
ariaLabel="Undo Button 1"
|
||||||
|
disabled={false}
|
||||||
|
iconProps={
|
||||||
|
Object {
|
||||||
|
"iconName": "Undo",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClick={[Function]}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={1}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"marginLeft": 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
sample error
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
</Stack>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||||
|
<Stack
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
refreshIndexTransformationProgress={[Function]}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Compound indexes
|
||||||
|
</StyledLinkBase>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||||
|
</Text>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Current index(es)"
|
||||||
|
>
|
||||||
|
<StyledWithViewportComponent
|
||||||
|
columns={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"fieldName": "definition",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "definition",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Definition",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "type",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "type",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Type",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"fieldName": "actionButton",
|
||||||
|
"isResizable": true,
|
||||||
|
"key": "actionButton",
|
||||||
|
"maxWidth": 200,
|
||||||
|
"minWidth": 100,
|
||||||
|
"name": "Drop Index",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
disableSelectionZone={true}
|
||||||
|
items={Array []}
|
||||||
|
layoutMode={1}
|
||||||
|
onRenderRow={[Function]}
|
||||||
|
selectionMode={0}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"selectors": Object {
|
||||||
|
".ms-FocusZone": Object {
|
||||||
|
"paddingTop": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AddMongoIndexComponent
|
||||||
|
disabled={false}
|
||||||
|
key="0"
|
||||||
|
onDiscard={[Function]}
|
||||||
|
onIndexAddOrChange={[Function]}
|
||||||
|
position={0}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
<Styled
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Array [
|
||||||
|
Object {
|
||||||
|
"selectors": Object {
|
||||||
|
"::before": Object {
|
||||||
|
"background": undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Index(es) to be dropped"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
`;
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
|
||||||
import { container, collection } from "../TestUtils";
|
|
||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
|
||||||
import Explorer from "../../../Explorer";
|
|
||||||
import * as Constants from "../../../../Common/Constants";
|
|
||||||
import { PlatformType } from "../../../../PlatformType";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
|
||||||
import { throughputUnit } from "../SettingsRenderUtils";
|
|
||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
import React from "react";
|
||||||
|
import * as Constants from "../../../../Common/Constants";
|
||||||
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
|
import Explorer from "../../../Explorer";
|
||||||
|
import { throughputUnit } from "../SettingsRenderUtils";
|
||||||
|
import { collection, container } from "../TestUtils";
|
||||||
|
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||||
|
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||||
|
|
||||||
describe("ScaleComponent", () => {
|
describe("ScaleComponent", () => {
|
||||||
const nonNationalCloudContainer = new Explorer();
|
const nonNationalCloudContainer = new Explorer();
|
||||||
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
|
|
||||||
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
|
||||||
|
|
||||||
const targetThroughput = 6000;
|
const targetThroughput = 6000;
|
||||||
@@ -22,7 +19,6 @@ describe("ScaleComponent", () => {
|
|||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
container: container,
|
||||||
isFixedContainer: false,
|
isFixedContainer: false,
|
||||||
autoPilotTiersList: [],
|
|
||||||
onThroughputChange: () => {
|
onThroughputChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
@@ -51,9 +47,7 @@ describe("ScaleComponent", () => {
|
|||||||
let wrapper = shallow(<ScaleComponent {...baseProps} />);
|
let wrapper = shallow(<ScaleComponent {...baseProps} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
||||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
|
|
||||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
||||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
|
|
||||||
|
|
||||||
const newCollection = { ...collection };
|
const newCollection = { ...collection };
|
||||||
const maxThroughput = 5000;
|
const maxThroughput = 5000;
|
||||||
@@ -112,14 +106,9 @@ describe("ScaleComponent", () => {
|
|||||||
expect(scaleComponent.isAutoScaleEnabled()).toEqual(true);
|
expect(scaleComponent.isAutoScaleEnabled()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getMaxRUThroughputInputLimit", () => {
|
|
||||||
const scaleComponent = new ScaleComponent(baseProps);
|
|
||||||
expect(scaleComponent.getMaxRUThroughputInputLimit()).toEqual(40000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getThroughputTitle", () => {
|
it("getThroughputTitle", () => {
|
||||||
let scaleComponent = new ScaleComponent(baseProps);
|
let scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - 40,000 RU/s)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (6,000 - unlimited RU/s)");
|
||||||
|
|
||||||
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
let newProps = { ...baseProps, container: nonNationalCloudContainer };
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
@@ -129,26 +118,4 @@ describe("ScaleComponent", () => {
|
|||||||
scaleComponent = new ScaleComponent(newProps);
|
scaleComponent = new ScaleComponent(newProps);
|
||||||
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (autoscale)");
|
expect(scaleComponent.getThroughputTitle()).toEqual("Throughput (autoscale)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("canThroughputExceedMaximumValue", () => {
|
|
||||||
let scaleComponent = new ScaleComponent(baseProps);
|
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(false);
|
|
||||||
|
|
||||||
const newProps = { ...baseProps, container: nonNationalCloudContainer };
|
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
|
||||||
expect(scaleComponent.canThroughputExceedMaximumValue()).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getThroughputWarningMessage", () => {
|
|
||||||
const throughputBeyondLimit = SharedConstants.CollectionCreation.DefaultCollectionRUs1Million + 1000;
|
|
||||||
const throughputBeyondMaxRus = SharedConstants.CollectionCreation.DefaultCollectionRUs1Million - 1000;
|
|
||||||
|
|
||||||
const newProps = { ...baseProps, container: nonNationalCloudContainer, throughput: throughputBeyondLimit };
|
|
||||||
let scaleComponent = new ScaleComponent(newProps);
|
|
||||||
expect(scaleComponent.getThroughputWarningMessage().props.id).toEqual("updateThroughputBeyondLimitWarningMessage");
|
|
||||||
|
|
||||||
newProps.throughput = throughputBeyondMaxRus;
|
|
||||||
scaleComponent = new ScaleComponent(newProps);
|
|
||||||
expect(scaleComponent.getThroughputWarningMessage().props.id).toEqual("updateThroughputDelayedApplyWarningMessage");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,31 +1,25 @@
|
|||||||
|
import { Label, MessageBar, MessageBarType, Stack, Text, TextField } from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Constants from "../../../../Common/Constants";
|
import * as Constants from "../../../../Common/Constants";
|
||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
import { configContext, Platform } from "../../../../ConfigContext";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../Contracts/DataModels";
|
||||||
import * as SharedConstants from "../../../../Shared/Constants";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
|
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { PlatformType } from "../../../../PlatformType";
|
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
subComponentStackProps,
|
|
||||||
titleAndInputStackProps,
|
|
||||||
throughputUnit,
|
|
||||||
getThroughputApplyLongDelayMessage,
|
|
||||||
getThroughputApplyShortDelayMessage,
|
getThroughputApplyShortDelayMessage,
|
||||||
updateThroughputBeyondLimitWarningMessage,
|
subComponentStackProps,
|
||||||
updateThroughputDelayedApplyWarningMessage
|
throughputUnit,
|
||||||
|
titleAndInputStackProps
|
||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
import { getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
|
||||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
||||||
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
|
||||||
import { configContext, Platform } from "../../../../ConfigContext";
|
|
||||||
|
|
||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
isFixedContainer: boolean;
|
isFixedContainer: boolean;
|
||||||
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
throughput: number;
|
throughput: number;
|
||||||
throughputBaseline: number;
|
throughputBaseline: number;
|
||||||
@@ -77,41 +71,17 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public getMaxRUThroughputInputLimit = (): number => {
|
|
||||||
if (this.props.container?.getPlatformType() === PlatformType.Hosted && this.props.collection.partitionKey) {
|
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMaxRUs(this.props.collection, this.props.container);
|
|
||||||
};
|
|
||||||
|
|
||||||
public getThroughputTitle = (): string => {
|
public getThroughputTitle = (): string => {
|
||||||
if (this.props.isAutoPilotSelected) {
|
if (this.props.isAutoPilotSelected) {
|
||||||
return AutoPilotUtils.getAutoPilotHeaderText(false);
|
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||||
}
|
}
|
||||||
|
|
||||||
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
||||||
const maxThroughput: string =
|
const maxThroughput: string = !this.props.isFixedContainer ? "unlimited" : "10000";
|
||||||
this.canThroughputExceedMaximumValue() && !this.props.isFixedContainer
|
|
||||||
? "unlimited"
|
|
||||||
: getMaxRUs(this.props.collection, this.props.container).toLocaleString();
|
|
||||||
return `Throughput (${minThroughput} - ${maxThroughput} RU/s)`;
|
return `Throughput (${minThroughput} - ${maxThroughput} RU/s)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
public canThroughputExceedMaximumValue = (): boolean => {
|
|
||||||
const isPublicAzurePortal: boolean =
|
|
||||||
this.props.container.getPlatformType() === PlatformType.Portal &&
|
|
||||||
!this.props.container.isRunningOnNationalCloud();
|
|
||||||
const hasPartitionKey = !!this.props.collection.partitionKey;
|
|
||||||
|
|
||||||
return isPublicAzurePortal && hasPartitionKey;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getInitialNotificationElement = (): JSX.Element => {
|
public getInitialNotificationElement = (): JSX.Element => {
|
||||||
if (this.props.initialNotification) {
|
|
||||||
return this.getLongDelayMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
const offer = this.props.collection?.offer && this.props.collection.offer();
|
const offer = this.props.collection?.offer && this.props.collection.offer();
|
||||||
if (
|
if (
|
||||||
offer &&
|
offer &&
|
||||||
@@ -138,47 +108,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
public getThroughputWarningMessage = (): JSX.Element => {
|
|
||||||
const throughputExceedsBackendLimits: boolean =
|
|
||||||
this.canThroughputExceedMaximumValue() &&
|
|
||||||
getMaxRUs(this.props.collection, this.props.container) <=
|
|
||||||
SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
|
||||||
this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
|
||||||
|
|
||||||
if (throughputExceedsBackendLimits && !!this.props.collection.partitionKey && !this.props.isFixedContainer) {
|
|
||||||
return updateThroughputBeyondLimitWarningMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const throughputExceedsMaxValue: boolean =
|
|
||||||
!this.isEmulator && this.props.throughput > getMaxRUs(this.props.collection, this.props.container);
|
|
||||||
|
|
||||||
if (throughputExceedsMaxValue && !!this.props.collection.partitionKey && !this.props.isFixedContainer) {
|
|
||||||
return updateThroughputDelayedApplyWarningMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
public getLongDelayMessage = (): JSX.Element => {
|
|
||||||
const matches: string[] = this.props.initialNotification?.description.match(
|
|
||||||
`Throughput update for (.*) ${throughputUnit}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const throughput = this.props.throughputBaseline;
|
|
||||||
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
|
|
||||||
if (targetThroughput) {
|
|
||||||
return getThroughputApplyLongDelayMessage(
|
|
||||||
this.props.wasAutopilotOriginallySet,
|
|
||||||
throughput,
|
|
||||||
throughputUnit,
|
|
||||||
this.props.collection.databaseId,
|
|
||||||
this.props.collection.id(),
|
|
||||||
targetThroughput
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
private getThroughputInputComponent = (): JSX.Element => (
|
private getThroughputInputComponent = (): JSX.Element => (
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
databaseAccount={this.props.container.databaseAccount()}
|
databaseAccount={this.props.container.databaseAccount()}
|
||||||
@@ -187,9 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
throughputBaseline={this.props.throughputBaseline}
|
throughputBaseline={this.props.throughputBaseline}
|
||||||
onThroughputChange={this.props.onThroughputChange}
|
onThroughputChange={this.props.onThroughputChange}
|
||||||
minimum={getMinRUs(this.props.collection, this.props.container)}
|
minimum={getMinRUs(this.props.collection, this.props.container)}
|
||||||
maximum={this.getMaxRUThroughputInputLimit()}
|
|
||||||
isEnabled={!hasDatabaseSharedThroughput(this.props.collection)}
|
isEnabled={!hasDatabaseSharedThroughput(this.props.collection)}
|
||||||
canExceedMaximumValue={this.canThroughputExceedMaximumValue()}
|
|
||||||
label={this.getThroughputTitle()}
|
label={this.getThroughputTitle()}
|
||||||
isEmulator={this.isEmulator}
|
isEmulator={this.isEmulator}
|
||||||
isFixed={this.props.isFixedContainer}
|
isFixed={this.props.isFixedContainer}
|
||||||
@@ -202,7 +129,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
spendAckChecked={false}
|
spendAckChecked={false}
|
||||||
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
||||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
throughputBaseline: 100,
|
throughputBaseline: 100,
|
||||||
onThroughputChange: undefined,
|
onThroughputChange: undefined,
|
||||||
minimum: 10000,
|
minimum: 10000,
|
||||||
maximum: 400,
|
|
||||||
step: 100,
|
step: 100,
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
isEmulator: false,
|
isEmulator: false,
|
||||||
@@ -38,8 +37,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
},
|
},
|
||||||
onScaleDiscardableChange: () => {
|
onScaleDiscardableChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
getThroughputWarningMessage: () => undefined
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it("throughput input visible", () => {
|
it("throughput input visible", () => {
|
||||||
|
|||||||
@@ -1,35 +1,33 @@
|
|||||||
import React from "react";
|
|
||||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
Checkbox,
|
||||||
getToolTipContainer,
|
|
||||||
spendAckCheckBoxStyle,
|
|
||||||
titleAndInputStackProps,
|
|
||||||
checkBoxAndInputStackProps,
|
|
||||||
getChoiceGroupStyles,
|
|
||||||
messageBarStyles,
|
|
||||||
getEstimatedSpendElement,
|
|
||||||
getEstimatedAutoscaleSpendElement,
|
|
||||||
getAutoPilotV3SpendElement,
|
|
||||||
manualToAutoscaleDisclaimerElement
|
|
||||||
} from "../../SettingsRenderUtils";
|
|
||||||
import {
|
|
||||||
Text,
|
|
||||||
TextField,
|
|
||||||
ChoiceGroup,
|
ChoiceGroup,
|
||||||
IChoiceGroupOption,
|
IChoiceGroupOption,
|
||||||
Checkbox,
|
|
||||||
Stack,
|
|
||||||
Label,
|
Label,
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import React from "react";
|
||||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
|
||||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
|
||||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||||
|
import {
|
||||||
|
checkBoxAndInputStackProps,
|
||||||
|
getAutoPilotV3SpendElement,
|
||||||
|
getChoiceGroupStyles,
|
||||||
|
getEstimatedAutoscaleSpendElement,
|
||||||
|
getEstimatedSpendElement,
|
||||||
|
getTextFieldStyles,
|
||||||
|
getToolTipContainer,
|
||||||
|
manualToAutoscaleDisclaimerElement,
|
||||||
|
messageBarStyles,
|
||||||
|
noLeftPaddingCheckBoxStyle,
|
||||||
|
titleAndInputStackProps
|
||||||
|
} from "../../SettingsRenderUtils";
|
||||||
|
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||||
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
|
|
||||||
export interface ThroughputInputAutoPilotV3Props {
|
export interface ThroughputInputAutoPilotV3Props {
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
@@ -38,7 +36,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
throughputBaseline: number;
|
throughputBaseline: number;
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
minimum: number;
|
minimum: number;
|
||||||
maximum: number;
|
|
||||||
step?: number;
|
step?: number;
|
||||||
isEnabled?: boolean;
|
isEnabled?: boolean;
|
||||||
spendAckChecked?: boolean;
|
spendAckChecked?: boolean;
|
||||||
@@ -59,7 +56,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
||||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
getThroughputWarningMessage: () => JSX.Element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThroughputInputAutoPilotV3State {
|
interface ThroughputInputAutoPilotV3State {
|
||||||
@@ -119,13 +115,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
if (isDirty(this.props.throughput, this.props.throughputBaseline)) {
|
if (isDirty(this.props.throughput, this.props.throughputBaseline)) {
|
||||||
isDiscardable = true;
|
isDiscardable = true;
|
||||||
isSaveable = true;
|
isSaveable = true;
|
||||||
if (
|
if (!this.props.throughput || this.props.throughput < this.props.minimum) {
|
||||||
!this.props.throughput ||
|
|
||||||
this.props.throughput < this.props.minimum ||
|
|
||||||
(this.props.throughput > this.props.maximum && (this.props.isEmulator || this.props.isFixed)) ||
|
|
||||||
(this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million &&
|
|
||||||
!this.props.canExceedMaximumValue)
|
|
||||||
) {
|
|
||||||
isSaveable = false;
|
isSaveable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,8 +131,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||||
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
|
this.throughputInputMaxValue = Number.MAX_SAFE_INTEGER;
|
||||||
this.autoPilotInputMaxValue = Int32.Max;
|
this.autoPilotInputMaxValue = Number.MAX_SAFE_INTEGER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasProvisioningTypeChanged = (): boolean =>
|
public hasProvisioningTypeChanged = (): boolean =>
|
||||||
@@ -278,12 +268,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
{this.props.spendAckVisible && (
|
{this.props.spendAckVisible && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
styles={spendAckCheckBoxStyle}
|
styles={noLeftPaddingCheckBoxStyle}
|
||||||
label={this.props.spendAckText}
|
label={this.props.spendAckText}
|
||||||
checked={this.state.spendAckChecked}
|
checked={this.state.spendAckChecked}
|
||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -305,32 +296,26 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
onChange={this.onThroughputChange}
|
onChange={this.onThroughputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.props.getThroughputWarningMessage() && (
|
|
||||||
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
|
||||||
{this.props.getThroughputWarningMessage()}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!this.props.isEmulator && this.getRequestUnitsUsageCost()}
|
{!this.props.isEmulator && this.getRequestUnitsUsageCost()}
|
||||||
|
|
||||||
{this.props.spendAckVisible && (
|
{this.props.spendAckVisible && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
styles={spendAckCheckBoxStyle}
|
styles={noLeftPaddingCheckBoxStyle}
|
||||||
label={this.props.spendAckText}
|
label={this.props.spendAckText}
|
||||||
checked={this.state.spendAckChecked}
|
checked={this.state.spendAckChecked}
|
||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.props.isFixed && <p>Choose unlimited storage capacity for more than 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>
|
||||||
);
|
);
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...checkBoxAndInputStackProps}>
|
<Stack {...checkBoxAndInputStackProps}>
|
||||||
{!this.props.isFixed && this.renderThroughputModeChoices()}
|
{this.renderThroughputModeChoices()}
|
||||||
|
|
||||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ exports[`IndexingPolicyComponent renders 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<IndexingPolicyRefreshComponent
|
||||||
|
refreshIndexTransformationProgress={[Function]}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="settingsV2IndexingPolicyEditor"
|
className="settingsV2IndexingPolicyEditor"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -8,29 +8,6 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledMessageBarBase
|
|
||||||
messageBarType={5}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
id="throughputApplyLongDelayMessage"
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"fontSize": 12,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
|
|
||||||
<br />
|
|
||||||
Database:
|
|
||||||
test
|
|
||||||
, Container:
|
|
||||||
test
|
|
||||||
|
|
||||||
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
|
||||||
</Text>
|
|
||||||
</StyledMessageBarBase>
|
|
||||||
<Stack
|
<Stack
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
@@ -39,16 +16,13 @@ exports[`ScaleComponent renders with correct intiial notification 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
canExceedMaximumValue={false}
|
|
||||||
getThroughputWarningMessage={[Function]}
|
|
||||||
isAutoPilotSelected={false}
|
isAutoPilotSelected={false}
|
||||||
isEmulator={false}
|
isEmulator={false}
|
||||||
isEnabled={true}
|
isEnabled={true}
|
||||||
isFixed={false}
|
isFixed={false}
|
||||||
label="Throughput (6,000 - 40,000 RU/s)"
|
label="Throughput (6,000 - unlimited RU/s)"
|
||||||
maxAutoPilotThroughput={4000}
|
maxAutoPilotThroughput={4000}
|
||||||
maxAutoPilotThroughputBaseline={4000}
|
maxAutoPilotThroughputBaseline={4000}
|
||||||
maximum={40000}
|
|
||||||
minimum={6000}
|
minimum={6000}
|
||||||
onAutoPilotSelected={[Function]}
|
onAutoPilotSelected={[Function]}
|
||||||
onMaxAutoPilotThroughputChange={[Function]}
|
onMaxAutoPilotThroughputChange={[Function]}
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
import { collection, container } from "./TestUtils";
|
import { collection, container } from "./TestUtils";
|
||||||
import {
|
import {
|
||||||
getMaxRUs,
|
|
||||||
getMinRUs,
|
getMinRUs,
|
||||||
|
getMongoIndexType,
|
||||||
|
getMongoNotification,
|
||||||
getSanitizedInputValue,
|
getSanitizedInputValue,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
isDirtyTypes,
|
isDirtyTypes,
|
||||||
|
MongoIndexTypes,
|
||||||
|
MongoNotificationType,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure,
|
||||||
|
MongoWildcardPlaceHolder,
|
||||||
|
getMongoIndexTypeText,
|
||||||
|
SingleFieldText,
|
||||||
|
WildcardText,
|
||||||
|
isIndexTransforming
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
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 ko from "knockout";
|
import ko from "knockout";
|
||||||
|
|
||||||
describe("SettingsUtils", () => {
|
describe("SettingsUtils", () => {
|
||||||
it("getMaxRUs", () => {
|
|
||||||
expect(collection.offer().content.collectionThroughputInfo.numPhysicalPartitions).toEqual(4);
|
|
||||||
expect(getMaxRUs(collection, container)).toEqual(40000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getMinRUs", () => {
|
it("getMinRUs", () => {
|
||||||
expect(collection.offer().content.collectionThroughputInfo.numPhysicalPartitions).toEqual(4);
|
expect(collection.offer().content.collectionThroughputInfo.numPhysicalPartitions).toEqual(4);
|
||||||
expect(getMinRUs(collection, container)).toEqual(6000);
|
expect(getMinRUs(collection, container)).toEqual(6000);
|
||||||
@@ -91,7 +94,50 @@ describe("SettingsUtils", () => {
|
|||||||
it("getSanitizedInputValue", () => {
|
it("getSanitizedInputValue", () => {
|
||||||
const max = 100;
|
const max = 100;
|
||||||
expect(getSanitizedInputValue("", max)).toEqual(0);
|
expect(getSanitizedInputValue("", max)).toEqual(0);
|
||||||
expect(getSanitizedInputValue("999", max)).toEqual(99);
|
expect(getSanitizedInputValue("999", max)).toEqual(100);
|
||||||
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
expect(getSanitizedInputValue("10", max)).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("getMongoIndexType", () => {
|
||||||
|
expect(getMongoIndexType(["Single"])).toEqual(MongoIndexTypes.Single);
|
||||||
|
expect(getMongoIndexType(["Wildcard.$**"])).toEqual(MongoIndexTypes.Wildcard);
|
||||||
|
expect(getMongoIndexType(["Key1", "Key2"])).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getMongoIndexTypeText", () => {
|
||||||
|
expect(getMongoIndexTypeText(MongoIndexTypes.Single)).toEqual(SingleFieldText);
|
||||||
|
expect(getMongoIndexTypeText(MongoIndexTypes.Wildcard)).toEqual(WildcardText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getMongoNotification", () => {
|
||||||
|
const singleIndexDescription = "sampleKey";
|
||||||
|
const wildcardIndexDescription = "sampleKey.$**";
|
||||||
|
|
||||||
|
let notification = getMongoNotification(singleIndexDescription, undefined);
|
||||||
|
expect(notification.message).toEqual("Please select a type for each index.");
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Warning);
|
||||||
|
|
||||||
|
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Single);
|
||||||
|
expect(notification).toEqual(undefined);
|
||||||
|
|
||||||
|
notification = getMongoNotification(wildcardIndexDescription, MongoIndexTypes.Wildcard);
|
||||||
|
expect(notification).toEqual(undefined);
|
||||||
|
|
||||||
|
notification = getMongoNotification("", MongoIndexTypes.Single);
|
||||||
|
expect(notification.message).toEqual("Please enter a field name.");
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||||
|
|
||||||
|
notification = getMongoNotification(singleIndexDescription, MongoIndexTypes.Wildcard);
|
||||||
|
expect(notification.message).toEqual(
|
||||||
|
"Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||||
|
);
|
||||||
|
expect(notification.type).toEqual(MongoNotificationType.Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isIndexingTransforming", () => {
|
||||||
|
expect(isIndexTransforming(undefined)).toBeFalsy();
|
||||||
|
expect(isIndexTransforming(0)).toBeTruthy();
|
||||||
|
expect(isIndexTransforming(90)).toBeTruthy();
|
||||||
|
expect(isIndexTransforming(100)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
import { MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
const zeroValue = 0;
|
const zeroValue = 0;
|
||||||
@@ -11,6 +10,10 @@ export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy
|
|||||||
export const TtlOff = "off";
|
export const TtlOff = "off";
|
||||||
export const TtlOn = "on";
|
export const TtlOn = "on";
|
||||||
export const TtlOnNoDefault = "on-nodefault";
|
export const TtlOnNoDefault = "on-nodefault";
|
||||||
|
export const MongoIndexIdField = "_id";
|
||||||
|
export const MongoWildcardPlaceHolder = "properties.$**";
|
||||||
|
export const SingleFieldText = "Single Field";
|
||||||
|
export const WildcardText = "Wildcard";
|
||||||
|
|
||||||
export enum ChangeFeedPolicyState {
|
export enum ChangeFeedPolicyState {
|
||||||
Off = "Off",
|
Off = "Off",
|
||||||
@@ -28,6 +31,17 @@ export enum GeospatialConfigType {
|
|||||||
Geometry = "Geometry"
|
Geometry = "Geometry"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MongoIndexTypes {
|
||||||
|
Single = "Single",
|
||||||
|
Wildcard = "Wildcard"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddMongoIndexProps {
|
||||||
|
mongoIndex: MongoIndex;
|
||||||
|
type: MongoIndexTypes;
|
||||||
|
notification: MongoNotificationMessage;
|
||||||
|
}
|
||||||
|
|
||||||
export enum SettingsV2TabTypes {
|
export enum SettingsV2TabTypes {
|
||||||
ScaleTab,
|
ScaleTab,
|
||||||
ConflictResolutionTab,
|
ConflictResolutionTab,
|
||||||
@@ -40,27 +54,21 @@ export interface IsComponentDirtyResult {
|
|||||||
isDiscardable: boolean;
|
isDiscardable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MongoNotificationType {
|
||||||
|
Warning = "Warning",
|
||||||
|
Error = "Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MongoNotificationMessage {
|
||||||
|
type: MongoNotificationType;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
||||||
const database: ViewModels.Database = collection.getDatabase();
|
const database: ViewModels.Database = collection.getDatabase();
|
||||||
return database?.isDatabaseShared() && !collection.offer();
|
return database?.isDatabaseShared() && !collection.offer();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMaxRUs = (collection: ViewModels.Collection, container: Explorer): number => {
|
|
||||||
const isTryCosmosDBSubscription = container?.isTryCosmosDBSubscription() || false;
|
|
||||||
if (isTryCosmosDBSubscription) {
|
|
||||||
return Constants.TryCosmosExperience.maxRU;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numPartitionsFromOffer: number =
|
|
||||||
collection?.offer && collection.offer()?.content?.collectionThroughputInfo?.numPhysicalPartitions;
|
|
||||||
|
|
||||||
const numPartitionsFromQuotaInfo: number = collection?.quotaInfo().numPartitions;
|
|
||||||
|
|
||||||
const numPartitions = numPartitionsFromOffer ?? numPartitionsFromQuotaInfo ?? 1;
|
|
||||||
|
|
||||||
return SharedConstants.CollectionCreation.MaxRUPerPartition * numPartitions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMinRUs = (collection: ViewModels.Collection, container: Explorer): number => {
|
export const getMinRUs = (collection: ViewModels.Collection, container: Explorer): number => {
|
||||||
const isTryCosmosDBSubscription = container?.isTryCosmosDBSubscription();
|
const isTryCosmosDBSubscription = container?.isTryCosmosDBSubscription();
|
||||||
if (isTryCosmosDBSubscription) {
|
if (isTryCosmosDBSubscription) {
|
||||||
@@ -79,21 +87,7 @@ export const getMinRUs = (collection: ViewModels.Collection, container: Explorer
|
|||||||
return collectionThroughputInfo.minimumRUForCollection;
|
return collectionThroughputInfo.minimumRUForCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numPartitions = collectionThroughputInfo?.numPhysicalPartitions ?? collection.quotaInfo().numPartitions;
|
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||||
|
|
||||||
if (!numPartitions || numPartitions === 1) {
|
|
||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseRU = SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
|
||||||
|
|
||||||
const quotaInKb = collection.quotaInfo().collectionSize;
|
|
||||||
const quotaInGb = PricingUtils.usageInGB(quotaInKb);
|
|
||||||
|
|
||||||
const perPartitionGBQuota: number = Math.max(10, quotaInGb / numPartitions);
|
|
||||||
const baseRUbyPartitions: number = ((numPartitions * perPartitionGBQuota) / 10) * 100;
|
|
||||||
|
|
||||||
return Math.max(baseRU, baseRUbyPartitions);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseConflictResolutionMode = (modeFromBackend: string): DataModels.ConflictResolutionMode => {
|
export const parseConflictResolutionMode = (modeFromBackend: string): DataModels.ConflictResolutionMode => {
|
||||||
@@ -131,13 +125,12 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||||
let newValue = parseInt(newValueString);
|
const newValue = parseInt(newValueString);
|
||||||
if (isNaN(newValue)) {
|
if (isNaN(newValue)) {
|
||||||
newValue = zeroValue;
|
return zeroValue;
|
||||||
} else if (newValue > max) {
|
|
||||||
newValue = Math.floor(newValue / 10);
|
|
||||||
}
|
}
|
||||||
return newValue;
|
// make sure new value does not exceed the maximum throughput
|
||||||
|
return Math.min(newValue, max);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||||
@@ -180,3 +173,52 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
|||||||
throw new Error(`Unknown tab ${tab}`);
|
throw new Error(`Unknown tab ${tab}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMongoNotification = (description: string, type: MongoIndexTypes): MongoNotificationMessage => {
|
||||||
|
if (description && !type) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Warning,
|
||||||
|
message: "Please select a type for each index."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type && (!description || description.trim().length === 0)) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Error,
|
||||||
|
message: "Please enter a field name."
|
||||||
|
};
|
||||||
|
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
|
||||||
|
return {
|
||||||
|
type: MongoNotificationType.Error,
|
||||||
|
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMongoIndexType = (keys: string[]): MongoIndexTypes => {
|
||||||
|
const length = keys?.length;
|
||||||
|
let type: MongoIndexTypes;
|
||||||
|
|
||||||
|
if (length === 1) {
|
||||||
|
if (keys[0].indexOf("$**") !== -1) {
|
||||||
|
type = MongoIndexTypes.Wildcard;
|
||||||
|
} else {
|
||||||
|
type = MongoIndexTypes.Single;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
|
||||||
|
if (index === MongoIndexTypes.Single) {
|
||||||
|
return SingleFieldText;
|
||||||
|
}
|
||||||
|
return WildcardText;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isIndexTransforming = (indexTransformationProgress: number): boolean =>
|
||||||
|
// index transformation progress can be 0
|
||||||
|
indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const collection = ({
|
|||||||
excludedPaths: []
|
excludedPaths: []
|
||||||
}),
|
}),
|
||||||
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
|
||||||
quotaInfo: ko.observable<DataModels.CollectionQuotaInfo>({} as DataModels.CollectionQuotaInfo),
|
|
||||||
offer: ko.observable<DataModels.Offer>({
|
offer: ko.observable<DataModels.Offer>({
|
||||||
content: {
|
content: {
|
||||||
offerThroughput: 10000,
|
offerThroughput: 10000,
|
||||||
|
|||||||
@@ -40,11 +40,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_openShareDialog": [Function],
|
"_openShareDialog": [Function],
|
||||||
"_panes": Array [
|
"_panes": Array [
|
||||||
AddDatabasePane {
|
AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -56,7 +54,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -69,7 +66,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -86,11 +82,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -108,7 +102,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -140,10 +133,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -354,11 +344,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
CassandraAddCollectionPane {
|
CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -366,7 +354,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -387,10 +374,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -585,11 +569,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -607,7 +589,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -639,10 +620,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -672,11 +650,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -688,7 +664,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -701,7 +676,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -778,11 +752,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -790,7 +762,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -811,10 +782,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -969,7 +937,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"title": [Function],
|
"title": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"hasStorageAnalyticsAfecFeature": [Function],
|
"hasStorageAnalyticsAfecFeature": [Function],
|
||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
@@ -983,6 +950,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -1327,7 +1295,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperty": "partitionKey",
|
"partitionKeyProperty": "partitionKey",
|
||||||
"quotaInfo": [Function],
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
}
|
}
|
||||||
@@ -1346,11 +1313,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_openShareDialog": [Function],
|
"_openShareDialog": [Function],
|
||||||
"_panes": Array [
|
"_panes": Array [
|
||||||
AddDatabasePane {
|
AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -1362,7 +1327,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -1375,7 +1339,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -1392,11 +1355,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -1414,7 +1375,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -1446,10 +1406,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -1660,11 +1617,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
CassandraAddCollectionPane {
|
CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -1672,7 +1627,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -1693,10 +1647,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -1891,11 +1842,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -1913,7 +1862,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -1945,10 +1893,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -1978,11 +1923,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -1994,7 +1937,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -2007,7 +1949,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -2084,11 +2025,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -2096,7 +2035,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -2117,10 +2055,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -2275,7 +2210,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"title": [Function],
|
"title": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"hasStorageAnalyticsAfecFeature": [Function],
|
"hasStorageAnalyticsAfecFeature": [Function],
|
||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
@@ -2289,6 +2223,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -2665,11 +2600,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_openShareDialog": [Function],
|
"_openShareDialog": [Function],
|
||||||
"_panes": Array [
|
"_panes": Array [
|
||||||
AddDatabasePane {
|
AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -2681,7 +2614,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -2694,7 +2626,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -2711,11 +2642,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -2733,7 +2662,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -2765,10 +2693,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -2979,11 +2904,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
CassandraAddCollectionPane {
|
CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -2991,7 +2914,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -3012,10 +2934,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -3210,11 +3129,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -3232,7 +3149,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -3264,10 +3180,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -3297,11 +3210,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -3313,7 +3224,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -3326,7 +3236,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -3403,11 +3312,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -3415,7 +3322,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -3436,10 +3342,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -3594,7 +3497,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"title": [Function],
|
"title": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"hasStorageAnalyticsAfecFeature": [Function],
|
"hasStorageAnalyticsAfecFeature": [Function],
|
||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
@@ -3608,6 +3510,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -3952,7 +3855,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
},
|
},
|
||||||
"partitionKeyProperty": "partitionKey",
|
"partitionKeyProperty": "partitionKey",
|
||||||
"quotaInfo": [Function],
|
|
||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": Object {},
|
"uniqueKeyPolicy": Object {},
|
||||||
}
|
}
|
||||||
@@ -3971,11 +3873,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"_openShareDialog": [Function],
|
"_openShareDialog": [Function],
|
||||||
"_panes": Array [
|
"_panes": Array [
|
||||||
AddDatabasePane {
|
AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -3987,7 +3887,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -4000,7 +3899,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -4017,11 +3915,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
AddCollectionPane {
|
AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -4039,7 +3935,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -4071,10 +3966,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -4285,11 +4177,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
CassandraAddCollectionPane {
|
CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -4297,7 +4187,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -4318,10 +4207,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -4516,11 +4402,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"addCollectionPane": AddCollectionPane {
|
"addCollectionPane": AddCollectionPane {
|
||||||
"_isSynapseLinkEnabled": [Function],
|
"_isSynapseLinkEnabled": [Function],
|
||||||
"autoPilotThroughput": [Function],
|
"autoPilotThroughput": [Function],
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"collectionId": [Function],
|
"collectionId": [Function],
|
||||||
"collectionIdTitle": [Function],
|
"collectionIdTitle": [Function],
|
||||||
"collectionWithThroughputInShared": [Function],
|
"collectionWithThroughputInShared": [Function],
|
||||||
@@ -4538,7 +4422,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -4570,10 +4453,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"rupm": [Function],
|
"rupm": [Function],
|
||||||
"rupmVisible": [Function],
|
"rupmVisible": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"shouldCreateMongoWildcardIndex": [Function],
|
"shouldCreateMongoWildcardIndex": [Function],
|
||||||
"shouldUseDatabaseThroughput": [Function],
|
"shouldUseDatabaseThroughput": [Function],
|
||||||
@@ -4603,11 +4483,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"addCollectionText": [Function],
|
"addCollectionText": [Function],
|
||||||
"addDatabasePane": AddDatabasePane {
|
"addDatabasePane": AddDatabasePane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"databaseCreateNewShared": [Function],
|
"databaseCreateNewShared": [Function],
|
||||||
@@ -4619,7 +4497,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -4632,7 +4509,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onMoreDetailsKeyPress": [Function],
|
"onMoreDetailsKeyPress": [Function],
|
||||||
"requestUnitsUsageCost": [Function],
|
"requestUnitsUsageCost": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"showUpsellMessage": [Function],
|
"showUpsellMessage": [Function],
|
||||||
"throughput": [Function],
|
"throughput": [Function],
|
||||||
"throughputRangeText": [Function],
|
"throughputRangeText": [Function],
|
||||||
@@ -4709,11 +4585,9 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||||
"autoPilotTiersList": [Function],
|
|
||||||
"autoPilotUsageCost": [Function],
|
"autoPilotUsageCost": [Function],
|
||||||
"canConfigureThroughput": [Function],
|
"canConfigureThroughput": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canRequestSupport": [Function],
|
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
"costsVisible": [Function],
|
"costsVisible": [Function],
|
||||||
"createTableQuery": [Function],
|
"createTableQuery": [Function],
|
||||||
@@ -4721,7 +4595,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"id": "cassandraaddcollectionpane",
|
"id": "cassandraaddcollectionpane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -4742,10 +4615,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"requestUnitsUsageCostShared": [Function],
|
"requestUnitsUsageCostShared": [Function],
|
||||||
"ruToolTipText": [Function],
|
"ruToolTipText": [Function],
|
||||||
"selectedAutoPilotThroughput": [Function],
|
"selectedAutoPilotThroughput": [Function],
|
||||||
"selectedAutoPilotTier": [Function],
|
|
||||||
"selectedSharedAutoPilotTier": [Function],
|
|
||||||
"sharedAutoPilotThroughput": [Function],
|
"sharedAutoPilotThroughput": [Function],
|
||||||
"sharedAutoPilotTiersList": [Function],
|
|
||||||
"sharedThroughputRangeText": [Function],
|
"sharedThroughputRangeText": [Function],
|
||||||
"sharedThroughputSpendAck": [Function],
|
"sharedThroughputSpendAck": [Function],
|
||||||
"sharedThroughputSpendAckText": [Function],
|
"sharedThroughputSpendAckText": [Function],
|
||||||
@@ -4900,7 +4770,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"title": [Function],
|
"title": [Function],
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"hasAutoPilotV2FeatureFlag": [Function],
|
|
||||||
"hasStorageAnalyticsAfecFeature": [Function],
|
"hasStorageAnalyticsAfecFeature": [Function],
|
||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
@@ -4914,6 +4783,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
|
"isMongoIndexEditorEnabled": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -5293,6 +5163,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
logIndexingPolicySuccessMessage={[Function]}
|
logIndexingPolicySuccessMessage={[Function]}
|
||||||
onIndexingPolicyContentChange={[Function]}
|
onIndexingPolicyContentChange={[Function]}
|
||||||
onIndexingPolicyDirtyChange={[Function]}
|
onIndexingPolicyDirtyChange={[Function]}
|
||||||
|
refreshIndexTransformationProgress={[Function]}
|
||||||
resetShouldDiscardIndexingPolicy={[Function]}
|
resetShouldDiscardIndexingPolicy={[Function]}
|
||||||
shouldDiscardIndexingPolicy={false}
|
shouldDiscardIndexingPolicy={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -166,15 +166,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For more information see,
|
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||||
|
|
||||||
<StyledLinkBase
|
|
||||||
href="https://aka.ms/cosmosdb/modify-index-policy"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Modifying Indexing Policies
|
|
||||||
</StyledLinkBase>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
id="updateThroughputBeyondLimitWarningMessage"
|
id="updateThroughputBeyondLimitWarningMessage"
|
||||||
@@ -310,5 +302,83 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
Enable change feed log retention policy to retain last 10 minutes of history for items in the container by default. To support this, the request unit (RU) charge for this container will be multiplied by a factor of two for writes. Reads are unaffected.
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Compound indexes
|
||||||
|
</StyledLinkBase>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo shell.
|
||||||
|
</Text>
|
||||||
|
<StyledMessageBarBase
|
||||||
|
messageBarType={1}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
To use the indexing policy editor, please login to the
|
||||||
|
<StyledLinkBase
|
||||||
|
href="https://portal.azure.com"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
azure portal.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
</StyledMessageBarBase>
|
||||||
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Refreshing index transformation progress
|
||||||
|
</Text>
|
||||||
|
<StyledSpinnerBase
|
||||||
|
size={1}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
You can make more indexing changes once the current index transformation is complete.
|
||||||
|
<StyledLinkBase
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Refresh to check if it has completed.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
|
||||||
|
<StyledLinkBase
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Refresh to check the progress.
|
||||||
|
</StyledLinkBase>
|
||||||
|
</Text>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import editable from "../../../Common/EditableUtility";
|
|
||||||
import { ThroughputInputComponent, ThroughputInputParams, ThroughputInputViewModel } from "./ThroughputInputComponent";
|
|
||||||
|
|
||||||
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
|
|
||||||
|
|
||||||
describe.skip("Throughput Input Component", () => {
|
|
||||||
let component: any;
|
|
||||||
let vm: ThroughputInputViewModel;
|
|
||||||
const testId: string = "ThroughputValue";
|
|
||||||
const value: ViewModels.Editable<number> = editable.observable(500);
|
|
||||||
const minimum: ko.Observable<number> = ko.observable(400);
|
|
||||||
const maximum: ko.Observable<number> = ko.observable(2000);
|
|
||||||
|
|
||||||
function buildListOptions(
|
|
||||||
value: ViewModels.Editable<number>,
|
|
||||||
minimum: ko.Observable<number>,
|
|
||||||
maxium: ko.Observable<number>,
|
|
||||||
canExceedMaximumValue?: boolean
|
|
||||||
): ThroughputInputParams {
|
|
||||||
return {
|
|
||||||
testId,
|
|
||||||
value,
|
|
||||||
minimum,
|
|
||||||
maximum,
|
|
||||||
canExceedMaximumValue: ko.computed<boolean>(() => Boolean(canExceedMaximumValue)),
|
|
||||||
costsVisible: ko.observable(false),
|
|
||||||
isFixed: false,
|
|
||||||
label: ko.observable("Label"),
|
|
||||||
requestUnitsUsageCost: ko.observable("requestUnitsUsageCost"),
|
|
||||||
showAsMandatory: false,
|
|
||||||
autoPilotTiersList: null,
|
|
||||||
autoPilotUsageCost: null,
|
|
||||||
isAutoPilotSelected: null,
|
|
||||||
selectedAutoPilotTier: null,
|
|
||||||
throughputAutoPilotRadioId: null,
|
|
||||||
throughputProvisionedRadioId: null,
|
|
||||||
throughputModeRadioName: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function simulateKeyPressSpace(target: HTMLElement): Promise<boolean> {
|
|
||||||
const event = new KeyboardEvent("keydown", {
|
|
||||||
key: "space"
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = target.dispatchEvent(event);
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(result);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
component = ThroughputInputComponent;
|
|
||||||
document.body.innerHTML = component.template as any;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await ko.cleanNode(document);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Rendering", () => {
|
|
||||||
it("should display value text", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
expect(($("input") as HTMLInputElement).value).toContain(value().toString());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Behavior", () => {
|
|
||||||
it("should decrease value", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
value(450);
|
|
||||||
$(".testhook-decreaseThroughput").click();
|
|
||||||
expect(value()).toBe(400);
|
|
||||||
$(".testhook-decreaseThroughput").click();
|
|
||||||
expect(value()).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should increase value", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
value(1950);
|
|
||||||
$(".test-increaseThroughput").click();
|
|
||||||
expect(value()).toBe(2000);
|
|
||||||
$(".test-increaseThroughput").click();
|
|
||||||
expect(value()).toBe(2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should respect lower bound limits", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
value(minimum());
|
|
||||||
$(".testhook-decreaseThroughput").click();
|
|
||||||
expect(value()).toBe(minimum());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should respect upper bound limits", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
value(maximum());
|
|
||||||
$(".test-increaseThroughput").click();
|
|
||||||
expect(value()).toBe(maximum());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should allow throughput to exceed upper bound limit when canExceedMaximumValue is set", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
value(maximum());
|
|
||||||
$(".test-increaseThroughput").click();
|
|
||||||
expect(value()).toBe(maximum() + 100);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Accessibility", () => {
|
|
||||||
it.skip("should decrease value with keypress", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const target = $(".testhook-decreaseThroughput");
|
|
||||||
|
|
||||||
value(500);
|
|
||||||
expect(value()).toBe(500);
|
|
||||||
|
|
||||||
const result = await simulateKeyPressSpace(target);
|
|
||||||
expect(value()).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip("should increase value with keypress", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const target = $(".test-increaseThroughput");
|
|
||||||
|
|
||||||
value(400);
|
|
||||||
expect(value()).toBe(400);
|
|
||||||
|
|
||||||
const result = await simulateKeyPressSpace(target);
|
|
||||||
// expect(value()).toBe(500);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set the decreaseButtonAriaLabel using the default step value", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 100");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set the increaseButtonAriaLabel using the default step value", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 100");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set the increaseButtonAriaLabel using the params step value", async () => {
|
|
||||||
const options = buildListOptions(value, minimum, maximum, true);
|
|
||||||
options.step = 10;
|
|
||||||
vm = new component.viewModel(options);
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 10");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
|
|
||||||
const options = buildListOptions(value, minimum, maximum, true);
|
|
||||||
options.step = 10;
|
|
||||||
vm = new component.viewModel(options);
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 10");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
|
|
||||||
const options = buildListOptions(value, minimum, maximum, true);
|
|
||||||
options.step = 10;
|
|
||||||
vm = new component.viewModel(options);
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have aria-label attribute on increase button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const ariaLabel = $(".test-increaseThroughput").attributes.getNamedItem("aria-label").value;
|
|
||||||
expect(ariaLabel).toBe("Increase throughput by 100");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have aria-label attribute on increase button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const ariaLabel = $(".testhook-decreaseThroughput").attributes.getNamedItem("aria-label").value;
|
|
||||||
expect(ariaLabel).toBe("Decrease throughput by 100");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have role on increase button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const role = $(".test-increaseThroughput").attributes.getNamedItem("role").value;
|
|
||||||
expect(role).toBe("button");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have role on decrease button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("role").value;
|
|
||||||
expect(role).toBe("button");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have tabindex 0 on increase button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
|
|
||||||
expect(role).toBe("0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have tabindex 0 on decrease button", async () => {
|
|
||||||
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
|
|
||||||
await ko.applyBindings(vm);
|
|
||||||
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
|
|
||||||
expect(role).toBe("0");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div>
|
|
||||||
<p class="pkPadding">
|
|
||||||
<!-- ko if: showAsMandatory -->
|
|
||||||
<span class="mandatoryStar">*</span>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<span class="addCollectionLabel" data-bind="text: label"></span>
|
|
||||||
|
|
||||||
<!-- ko if: infoBubbleText -->
|
|
||||||
<span class="infoTooltip" role="tooltip" tabindex="0">
|
|
||||||
<img class="infoImg" src="../../../../images/info-bubble.svg" alt="More information" />
|
|
||||||
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
|
|
||||||
</span>
|
|
||||||
<!-- /ko -->
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ko if: !isFixed -->
|
|
||||||
<div data-bind="visible: showAutoPilot" class="throughputModeContainer">
|
|
||||||
<input
|
|
||||||
class="throughputModeRadio"
|
|
||||||
aria-label="Autopilot mode"
|
|
||||||
data-test="throughput-autoPilot"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
checked: isAutoPilotSelected,
|
|
||||||
checkedValue: true,
|
|
||||||
attr: {
|
|
||||||
id: throughputAutoPilotRadioId,
|
|
||||||
name: throughputModeRadioName,
|
|
||||||
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="throughputModeSpace"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
for: throughputAutoPilotRadioId
|
|
||||||
}"
|
|
||||||
>Autopilot (preview)
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
class="throughputModeRadio nonFirstRadio"
|
|
||||||
aria-label="Provisioned Throughput mode"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
checked: isAutoPilotSelected,
|
|
||||||
checkedValue: false,
|
|
||||||
attr: {
|
|
||||||
id: throughputProvisionedRadioId,
|
|
||||||
name: throughputModeRadioName,
|
|
||||||
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="throughputModeSpace"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
for: throughputProvisionedRadioId
|
|
||||||
}"
|
|
||||||
>Manual
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<div data-bind="visible: isAutoPilotSelected">
|
|
||||||
<select
|
|
||||||
name="autoPilotTiers"
|
|
||||||
class="collid select-font-size"
|
|
||||||
aria-label="Autopilot Max RU/s"
|
|
||||||
data-bind="
|
|
||||||
options: autoPilotTiersList,
|
|
||||||
optionsText: 'text',
|
|
||||||
optionsValue: 'value',
|
|
||||||
value: selectedAutoPilotTier,
|
|
||||||
optionsCaption: 'Choose Max RU/s'"
|
|
||||||
>
|
|
||||||
</select>
|
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
data-bind="
|
|
||||||
html: autoPilotUsageCost,
|
|
||||||
visible: selectedAutoPilotTier"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
|
||||||
<div data-bind="setTemplateReady: true">
|
|
||||||
<p class="addContainerThroughputInput">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
required
|
|
||||||
data-bind="
|
|
||||||
textInput: value,
|
|
||||||
css: {
|
|
||||||
dirty: value.editableIsDirty
|
|
||||||
},
|
|
||||||
enable: isEnabled,
|
|
||||||
attr:{
|
|
||||||
'data-test': testId,
|
|
||||||
'class': cssClass,
|
|
||||||
step: step,
|
|
||||||
min: minimum,
|
|
||||||
max: canExceedMaximumValue() ? null : maximum,
|
|
||||||
'aria-label': ariaLabel
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p data-bind="visible: costsVisible">
|
|
||||||
<span data-bind="html: requestUnitsUsageCost"></span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- ko if: spendAckVisible -->
|
|
||||||
<p class="pkPadding">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
aria-label="acknowledge spend throughput"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
title: spendAckText,
|
|
||||||
id: spendAckId
|
|
||||||
},
|
|
||||||
checked: spendAckChecked"
|
|
||||||
/>
|
|
||||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
|
||||||
</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<!-- ko if: isFixed -->
|
|
||||||
<p>
|
|
||||||
Choose unlimited storage capacity for more than 10,000 RU/s.
|
|
||||||
</p>
|
|
||||||
<!-- /ko -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
import ThroughputInputComponentTemplate from "./ThroughputInputComponent.html";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throughput Input:
|
|
||||||
*
|
|
||||||
* Creates a set of controls to input, sanitize and increase/decrease throughput
|
|
||||||
*
|
|
||||||
* How to use in your markup:
|
|
||||||
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
|
|
||||||
* </throughput-input>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface ThroughputInputParams {
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput value to
|
|
||||||
*/
|
|
||||||
value: ViewModels.Editable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as id for testing
|
|
||||||
*/
|
|
||||||
testId: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text to use as aria-label
|
|
||||||
*/
|
|
||||||
ariaLabel?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum value in the range
|
|
||||||
*/
|
|
||||||
minimum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum value in the range
|
|
||||||
*/
|
|
||||||
maximum: ko.Observable<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Step value for increase/decrease
|
|
||||||
*/
|
|
||||||
step?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Observable to bind the Throughput enabled status
|
|
||||||
*/
|
|
||||||
isEnabled?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should show pricing controls
|
|
||||||
*/
|
|
||||||
costsVisible: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RU price
|
|
||||||
*/
|
|
||||||
requestUnitsUsageCost: ko.Subscribable<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* State of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckChecked?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* id of the spending acknowledge checkbox
|
|
||||||
*/
|
|
||||||
spendAckId?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spending acknowledge text
|
|
||||||
*/
|
|
||||||
spendAckText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show spending acknowledge controls
|
|
||||||
*/
|
|
||||||
spendAckVisible?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display * to the left of the label
|
|
||||||
*/
|
|
||||||
showAsMandatory: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
|
|
||||||
*/
|
|
||||||
isFixed: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label of the provisioned throughut control
|
|
||||||
*/
|
|
||||||
label: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Text of the info bubble for provisioned throughut control
|
|
||||||
*/
|
|
||||||
infoBubbleText?: ko.Observable<string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computed value that decides if value can exceed maximum allowable value
|
|
||||||
*/
|
|
||||||
canExceedMaximumValue?: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS classes to apply on input element
|
|
||||||
*/
|
|
||||||
cssClass?: string;
|
|
||||||
|
|
||||||
isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
throughputAutoPilotRadioId: string;
|
|
||||||
throughputProvisionedRadioId: string;
|
|
||||||
throughputModeRadioName: string;
|
|
||||||
autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
|
||||||
selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
|
||||||
autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
showAutoPilot?: ko.Observable<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|
||||||
public ariaLabel: ko.Observable<string>;
|
|
||||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
|
||||||
public step: number;
|
|
||||||
public testId: string;
|
|
||||||
public value: ViewModels.Editable<number>;
|
|
||||||
public minimum: ko.Observable<number>;
|
|
||||||
public maximum: ko.Observable<number>;
|
|
||||||
public isEnabled: ko.Observable<boolean>;
|
|
||||||
public cssClass: string;
|
|
||||||
public decreaseButtonAriaLabel: string;
|
|
||||||
public increaseButtonAriaLabel: string;
|
|
||||||
public costsVisible: ko.Observable<boolean>;
|
|
||||||
public requestUnitsUsageCost: ko.Subscribable<string>;
|
|
||||||
public spendAckChecked: ko.Observable<boolean>;
|
|
||||||
public spendAckId: ko.Observable<string>;
|
|
||||||
public spendAckText: ko.Observable<string>;
|
|
||||||
public spendAckVisible: ko.Observable<boolean>;
|
|
||||||
public showAsMandatory: boolean;
|
|
||||||
public infoBubbleText: string | ko.Observable<string>;
|
|
||||||
public label: ko.Observable<string>;
|
|
||||||
public isFixed: boolean;
|
|
||||||
public showAutoPilot: ko.Observable<boolean>;
|
|
||||||
public isAutoPilotSelected: ko.Observable<boolean>;
|
|
||||||
public throughputAutoPilotRadioId: string;
|
|
||||||
public throughputProvisionedRadioId: string;
|
|
||||||
public throughputModeRadioName: string;
|
|
||||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
|
||||||
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
|
|
||||||
public autoPilotUsageCost: ko.Computed<string>;
|
|
||||||
|
|
||||||
public constructor(options: ThroughputInputParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && options.onTemplateReady) {
|
|
||||||
options.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const params: ThroughputInputParams = options;
|
|
||||||
this.testId = params.testId || "ThroughputValue";
|
|
||||||
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
|
|
||||||
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
|
|
||||||
this.step = params.step || ThroughputInputViewModel._defaultStep;
|
|
||||||
this.isEnabled = params.isEnabled || ko.observable(true);
|
|
||||||
this.cssClass = params.cssClass || "textfontclr collid";
|
|
||||||
this.minimum = params.minimum;
|
|
||||||
this.maximum = params.maximum;
|
|
||||||
this.value = params.value;
|
|
||||||
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step.toString();
|
|
||||||
this.increaseButtonAriaLabel = "Increase throughput by " + this.step.toString();
|
|
||||||
this.costsVisible = options.costsVisible;
|
|
||||||
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
|
|
||||||
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
|
|
||||||
this.spendAckId = options.spendAckId || ko.observable<string>();
|
|
||||||
this.spendAckText = options.spendAckText || ko.observable<string>();
|
|
||||||
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
|
|
||||||
this.showAsMandatory = !!options.showAsMandatory;
|
|
||||||
this.isFixed = !!options.isFixed;
|
|
||||||
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
|
|
||||||
this.label = options.label || ko.observable<string>();
|
|
||||||
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
|
|
||||||
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
|
||||||
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
|
||||||
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
|
||||||
this.throughputModeRadioName = options.throughputModeRadioName;
|
|
||||||
this.autoPilotTiersList = options.autoPilotTiersList;
|
|
||||||
this.selectedAutoPilotTier = options.selectedAutoPilotTier;
|
|
||||||
this.autoPilotUsageCost = options.autoPilotUsageCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public decreaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput > this.minimum()) {
|
|
||||||
offerThroughput -= this.step;
|
|
||||||
if (offerThroughput < this.minimum()) {
|
|
||||||
offerThroughput = this.minimum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public increaseThroughput() {
|
|
||||||
let offerThroughput: number = this._getSanitizedValue();
|
|
||||||
|
|
||||||
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput += this.step;
|
|
||||||
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
|
|
||||||
offerThroughput = this.maximum();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value(offerThroughput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.increaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
this.decreaseThroughput();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getSanitizedValue(): number {
|
|
||||||
const throughput = this.value();
|
|
||||||
return isNaN(throughput) ? 0 : Number(throughput);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static _defaultStep: number = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThroughputInputComponent = {
|
|
||||||
viewModel: ThroughputInputViewModel,
|
|
||||||
template: ThroughputInputComponentTemplate
|
|
||||||
};
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
class="throughputModeRadio nonFirstRadio"
|
class="throughputModeRadio nonFirstRadio"
|
||||||
aria-label="Provisioned Throughput mode"
|
aria-label="Manual mode"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -119,6 +119,10 @@
|
|||||||
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
|
||||||
</p>
|
</p>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
|
<!-- ko if: isFixed -->
|
||||||
|
<p>Choose unlimited storage capacity for more than 10,000 RU/s.</p>
|
||||||
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
<div data-bind="visible: !isAutoPilotSelected()">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user