mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-27 12:51:41 +00:00
Compare commits
1 Commits
aad-fix
...
package-up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2568be0fe |
@@ -14,6 +14,7 @@ src/Common/DataAccessUtilityBase.ts
|
|||||||
src/Common/DeleteFeedback.ts
|
src/Common/DeleteFeedback.ts
|
||||||
src/Common/DocumentClientUtilityBase.ts
|
src/Common/DocumentClientUtilityBase.ts
|
||||||
src/Common/EditableUtility.ts
|
src/Common/EditableUtility.ts
|
||||||
|
src/Common/EnvironmentUtility.ts
|
||||||
src/Common/HashMap.test.ts
|
src/Common/HashMap.test.ts
|
||||||
src/Common/HashMap.ts
|
src/Common/HashMap.ts
|
||||||
src/Common/HeadersUtility.test.ts
|
src/Common/HeadersUtility.test.ts
|
||||||
@@ -42,6 +43,7 @@ src/Contracts/ViewModels.ts
|
|||||||
src/Controls/Heatmap/Heatmap.test.ts
|
src/Controls/Heatmap/Heatmap.test.ts
|
||||||
src/Controls/Heatmap/Heatmap.ts
|
src/Controls/Heatmap/Heatmap.ts
|
||||||
src/Controls/Heatmap/HeatmapDatatypes.ts
|
src/Controls/Heatmap/HeatmapDatatypes.ts
|
||||||
|
src/Definitions/adal.d.ts
|
||||||
src/Definitions/datatables.d.ts
|
src/Definitions/datatables.d.ts
|
||||||
src/Definitions/gif.d.ts
|
src/Definitions/gif.d.ts
|
||||||
src/Definitions/globals.d.ts
|
src/Definitions/globals.d.ts
|
||||||
@@ -241,6 +243,9 @@ src/Platform/Hosted/Authorization.ts
|
|||||||
src/Platform/Hosted/DataAccessUtility.ts
|
src/Platform/Hosted/DataAccessUtility.ts
|
||||||
src/Platform/Hosted/ExplorerFactory.ts
|
src/Platform/Hosted/ExplorerFactory.ts
|
||||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
||||||
|
src/Platform/Hosted/Helpers/ConnectionStringParser.ts
|
||||||
|
src/Platform/Hosted/HostedUtils.test.ts
|
||||||
|
src/Platform/Hosted/HostedUtils.ts
|
||||||
src/Platform/Hosted/Main.ts
|
src/Platform/Hosted/Main.ts
|
||||||
src/Platform/Hosted/Maint.test.ts
|
src/Platform/Hosted/Maint.test.ts
|
||||||
src/Platform/Hosted/NotificationsClient.ts
|
src/Platform/Hosted/NotificationsClient.ts
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ module.exports = {
|
|||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["**/*.tsx"],
|
files: ["**/*.tsx"],
|
||||||
extends: ["plugin:react/recommended"], // TODO: Add react-hooks
|
env: {
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
extends: ["plugin:react/recommended"],
|
||||||
plugins: ["react"],
|
plugins: ["react"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -33,7 +36,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
"no-console": ["error", { allow: ["error", "warn", "dir"] }],
|
|
||||||
curly: "error",
|
curly: "error",
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
"@typescript-eslint/no-unused-vars": "error",
|
||||||
"@typescript-eslint/no-extraneous-class": "error",
|
"@typescript-eslint/no-extraneous-class": "error",
|
||||||
@@ -41,7 +43,6 @@ module.exports = {
|
|||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
eqeqeq: "error",
|
eqeqeq: "error",
|
||||||
"react/display-name": "off",
|
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -94,14 +94,13 @@ jobs:
|
|||||||
npm ci
|
npm ci
|
||||||
npm start &
|
npm start &
|
||||||
npm run wait-for-server
|
npm run wait-for-server
|
||||||
npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts
|
npx jest -c ./jest.config.e2e.js --detectOpenHandles sql
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
|
||||||
PLATFORM: "Emulator"
|
PLATFORM: "Emulator"
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: failure()
|
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
@@ -160,14 +159,13 @@ jobs:
|
|||||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: failure()
|
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -191,7 +189,7 @@ jobs:
|
|||||||
nugetmpac:
|
nugetmpac:
|
||||||
name: Publish Nuget MPAC
|
name: Publish Nuget MPAC
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted, accessibility]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
|
|||||||
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
41
README.md
41
README.md
@@ -13,18 +13,29 @@ UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), ht
|
|||||||
|
|
||||||
### Watch mode
|
### Watch mode
|
||||||
|
|
||||||
Run `npm start` to start the development server and automatically rebuild on changes
|
Run `npm run watch` to start the development server and automatically rebuild on changes
|
||||||
|
|
||||||
### Hosted Development (https://cosmos.azure.com)
|
### Specifying Development Platform
|
||||||
|
|
||||||
- Visit: `https://localhost:1234/hostedExplorer.html`
|
Setting the environment variable `PLATFORM` during the build process will force the explorer to load the specified platform. By default in development it will run in `Hosted` mode. Valid options:
|
||||||
- Local sign in via AAD will NOT work. Connection string only in dev mode. Use the Portal if you need AAD auth.
|
|
||||||
- The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
- Hosted
|
||||||
|
- Emulator
|
||||||
|
- Portal
|
||||||
|
|
||||||
|
`PLATFORM=Emulator npm run watch`
|
||||||
|
|
||||||
|
### Hosted Development
|
||||||
|
|
||||||
|
The default webpack dev server configuration will proxy requests to the production portal backend: `https://main.documentdb.ext.azure.com`. This will allow you to use production connection strings on your local machine.
|
||||||
|
|
||||||
|
To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin to use hostedExplorer.html and change entry for index to use HostedExplorer.ts.
|
||||||
|
|
||||||
### Emulator Development
|
### Emulator Development
|
||||||
|
|
||||||
- Start the Cosmos Emulator
|
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.
|
||||||
- Visit: https://localhost:1234/index.html
|
|
||||||
|
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||||
|
|
||||||
#### Setting up a Remote Emulator
|
#### Setting up a Remote Emulator
|
||||||
|
|
||||||
@@ -44,8 +55,16 @@ The Cosmos emulator currently only runs in Windows environments. You can still d
|
|||||||
|
|
||||||
### Portal Development
|
### Portal Development
|
||||||
|
|
||||||
- Visit: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
The Cosmos Portal that consumes this repo is not currently open source. If you have access to this project, `npm run build` will copy the built files over to the portal where they will be loaded by the portal development environment
|
||||||
- You may have to manually visit https://localhost:1234/explorer.html first and click through any SSL certificate warnings
|
|
||||||
|
You can however load a local running instance of data explorer in the production portal.
|
||||||
|
|
||||||
|
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
||||||
|
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
||||||
|
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
||||||
|
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
|
||||||
|
Live reload will occur, but data explorer will not properly integrate again with the parent iframe. You will have to manually reload the page.
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
@@ -69,10 +88,6 @@ Jest and Puppeteer are used for end to end browser based tests and are contained
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### Architechture
|
|
||||||
|
|
||||||
[](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)
|
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Please read the [contribution guidelines](./CONTRIBUTING.md).
|
Please read the [contribution guidelines](./CONTRIBUTING.md).
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
|
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
|
||||||
plugins: [["@babel/plugin-proposal-decorators", { legacy: true }]],
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Why?
|
|
||||||
|
|
||||||
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
|
|
||||||
|
|
||||||
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
|
|
||||||
|
|
||||||
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = {}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "canvas",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC"
|
|
||||||
}
|
|
||||||
1963
externals/adal.js
vendored
Normal file
1963
externals/adal.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -39,10 +39,10 @@ module.exports = {
|
|||||||
// An object that configures minimum threshold enforcement for coverage results
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
branches: 22,
|
branches: 20,
|
||||||
functions: 28,
|
functions: 24,
|
||||||
lines: 33,
|
lines: 30,
|
||||||
statements: 31,
|
statements: 29.0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: wf_segoe-ui_normal;
|
font-family: wf_segoe-ui_normal;
|
||||||
src: url("../../fonts/segoe-ui/west-european/normal/latest.woff");
|
src: url('../../fonts/segoe-ui/west-european/normal/latest.woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||||
@@ -20,26 +20,26 @@
|
|||||||
COLORS
|
COLORS
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@AccentMediumHigh: #0058ad;
|
@AccentMediumHigh: #0058AD;
|
||||||
@AccentMedium: #004e87;
|
@AccentMedium: #004E87;
|
||||||
@AccentHigh: #1ebaed;
|
@AccentHigh: #1EBAED;
|
||||||
@AccentExtraHigh: #55b3ff;
|
@AccentExtraHigh: #55B3FF;
|
||||||
@AccentLow: #edf6ff;
|
@AccentLow: #EDF6FF;
|
||||||
@AccentMediumLow: #ddeefe;
|
@AccentMediumLow: #DDEEFE;
|
||||||
@AccentLight: #eef7ff;
|
@AccentLight: #EEF7FF;
|
||||||
@AccentExtra: #ddf0ff;
|
@AccentExtra: #DDF0FF;
|
||||||
|
|
||||||
@SelectionHigh: #b91f26;
|
@SelectionHigh: #B91F26;
|
||||||
@BaseLight: #ffffff;
|
@BaseLight: #FFFFFF;
|
||||||
@BaseDark: #000000;
|
@BaseDark: #000000;
|
||||||
@NotificationLow: #fff4ce;
|
@NotificationLow: #FFF4CE;
|
||||||
@NotificationHigh: #f9e9b0;
|
@NotificationHigh: #F9E9B0;
|
||||||
@Purple1: #8a2da5;
|
@Purple1: #8A2DA5;
|
||||||
@Dirty: #9b4f96;
|
@Dirty: #9b4f96;
|
||||||
|
|
||||||
@BaseLow: #f2f2f2;
|
@BaseLow: #F2F2F2;
|
||||||
@BaseMediumLow: #e6e6e6;
|
@BaseMediumLow: #E6E6E6;
|
||||||
@BaseMedium: #cccccc;
|
@BaseMedium: #CCCCCC;
|
||||||
@BaseMediumHigh: #767676;
|
@BaseMediumHigh: #767676;
|
||||||
@BaseHigh: #393939;
|
@BaseHigh: #393939;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
@ErrorColor: @SelectionHigh;
|
@ErrorColor: @SelectionHigh;
|
||||||
|
|
||||||
@SelectionColor: #3074b0;
|
@SelectionColor: #3074B0;
|
||||||
|
|
||||||
@FocusColor: #605e5c;
|
@FocusColor: #605e5c;
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
@ImgWidth: 14px;
|
@ImgWidth: 14px;
|
||||||
@ImgHeight: 14px;
|
@ImgHeight: 14px;
|
||||||
|
|
||||||
@toggleFontWeight: 700;
|
@toggleFontWeight:700;
|
||||||
|
|
||||||
//Resource Tree
|
//Resource Tree
|
||||||
@TreeLineHeight: 17px;
|
@TreeLineHeight: 17px;
|
||||||
@@ -144,16 +144,16 @@
|
|||||||
/**********************************************************************************/
|
/**********************************************************************************/
|
||||||
|
|
||||||
.flex-display(@display: flex) {
|
.flex-display(@display: flex) {
|
||||||
display: ~"-webkit-@{display}";
|
display: ~"-webkit-@{display}";
|
||||||
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox
|
display: ~"-ms-@{display}box"; // IE10 uses -ms-flexbox
|
||||||
display: ~"-ms-@{display}"; // IE11
|
display: ~"-ms-@{display}"; // IE11
|
||||||
display: @display;
|
display: @display;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-direction(@direction: column) {
|
.flex-direction(@direction: column) {
|
||||||
-webkit-flex-direction: @direction;
|
-webkit-flex-direction: @direction;
|
||||||
-ms-flex-direction: @direction;
|
-ms-flex-direction: @direction;
|
||||||
flex-direction: @direction;
|
flex-direction: @direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************************
|
/*************************************************************************************
|
||||||
@@ -161,31 +161,32 @@
|
|||||||
**************************************************************************************/
|
**************************************************************************************/
|
||||||
|
|
||||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||||
.selectedRadio,
|
.selectedRadio,
|
||||||
.selectedRadio:hover,
|
.selectedRadio:hover,
|
||||||
.selectedRadio:active,
|
.selectedRadio:active,
|
||||||
.selectedRadio.dirty,
|
.selectedRadio.dirty,
|
||||||
.tab [type="radio"]:checked ~ label,
|
.tab [type=radio]:checked ~ label,
|
||||||
.tab [type="radio"]:checked ~ label:hover {
|
.tab [type=radio]:checked ~ label:hover {
|
||||||
-ms-high-contrast-adjust: none;
|
-ms-high-contrast-adjust: none;
|
||||||
-webkit-text-fill-color: HighlightText;
|
-webkit-text-fill-color: HighlightText;
|
||||||
color: HighlightText;
|
color: HighlightText;
|
||||||
border-color: HighlightText;
|
border-color: HighlightText;
|
||||||
background-color: Highlight;
|
background-color: Highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queryMetricsSummaryTuple {
|
.queryMetricsSummaryTuple {
|
||||||
th,
|
|
||||||
td {
|
th, td {
|
||||||
&:nth-child(2) {
|
|
||||||
width: @IETableDataWidth;
|
&:nth-child(2) {
|
||||||
}
|
width: @IETableDataWidth;
|
||||||
|
}
|
||||||
&:nth-child(3) {
|
|
||||||
width: 50%;
|
&:nth-child(3) {
|
||||||
}
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************************************
|
/********************************************************************************************
|
||||||
@@ -193,15 +194,15 @@
|
|||||||
*********************************************************************************************/
|
*********************************************************************************************/
|
||||||
|
|
||||||
.hover() {
|
.hover() {
|
||||||
background-color: @AccentLight;
|
background-color: @AccentLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active() {
|
.active() {
|
||||||
background-color: @AccentExtra;
|
background-color: @AccentExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus() {
|
.focus() {
|
||||||
outline: 1px dashed @FocusColor;
|
outline: 1px dashed @FocusColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************************************************************************
|
/************************************************************************************************
|
||||||
@@ -211,87 +212,63 @@
|
|||||||
@ToggleWidth: 180px;
|
@ToggleWidth: 180px;
|
||||||
|
|
||||||
.toggleSwitch() {
|
.toggleSwitch() {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin-bottom: @SmallSpace;
|
margin-bottom: @SmallSpace;
|
||||||
padding: @SmallSpace;
|
padding: @SmallSpace;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: @BaseHigh;
|
color: @BaseHigh;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: @mediumFontSize;
|
font-size: @mediumFontSize;
|
||||||
font-family: @DataExplorerFont;
|
font-family: @DataExplorerFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedToggle() {
|
.selectedToggle() {
|
||||||
border-bottom: 2px solid @BaseHigh;
|
border-bottom: 2px solid @BaseHigh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unselectedToggle() {
|
.unselectedToggle() {
|
||||||
color: @AccentMediumHigh;
|
color: @AccentMediumHigh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************************************************
|
/********************************************************************************************************
|
||||||
Common Data Explorer Icons
|
Common Data Explorer Icons
|
||||||
*********************************************************************************************************/
|
*********************************************************************************************************/
|
||||||
.dataExplorerIcons() {
|
.dataExplorerIcons() {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: @ImgWidth;
|
width: @ImgWidth;
|
||||||
height: @ImgHeight;
|
height: @ImgHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************************
|
/*********************************************************************************************************
|
||||||
Info Tooltip
|
Info Tooltip
|
||||||
**********************************************************************************************************/
|
**********************************************************************************************************/
|
||||||
.infoTooltip() {
|
.infoTooltip() {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
|
.tooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: @backgroundColor;
|
background-color: @backgroundColor;
|
||||||
color: @textColor;
|
color: @textColor;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: @MediumSpace;
|
left: @MediumSpace;
|
||||||
padding: @MediumSpace;
|
padding: @MediumSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltipTextAfter(@color: @BaseDark) {
|
.tooltipTextAfter(@color: @BaseDark) {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 100%;
|
right: 100%;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: transparent @color transparent transparent;
|
border-color: transparent @color transparent transparent;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-color: @InfoPointerColor transparent;
|
border-color: @InfoPointerColor transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltipVisible() {
|
.tooltipVisible() {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
|
||||||
|
|
||||||
.inputTooltip() {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputTooltipText(@textColor: @BaseLight, @backgroundColor: @BaseHigh) {
|
|
||||||
background-color: @backgroundColor;
|
|
||||||
color: @textColor;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
padding: @MediumSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputTooltipTextAfter(@color: @BaseDark) {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
right: 100%;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent @color transparent transparent;
|
|
||||||
left: 10px;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-color: @InfoPointerColor transparent;
|
|
||||||
}
|
}
|
||||||
|
|||||||
3768
less/documentDB.less
3768
less/documentDB.less
File diff suppressed because it is too large
Load Diff
@@ -13,11 +13,6 @@
|
|||||||
@NavMediumSpace: 10px;
|
@NavMediumSpace: 10px;
|
||||||
@NavLargeSpace: 15px;
|
@NavLargeSpace: 15px;
|
||||||
|
|
||||||
.skip-link {
|
|
||||||
position: fixed;
|
|
||||||
top: -200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
@import "./Common/Constants";
|
@import "./Common/Constants";
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
transition: all .0s ease-in-out;
|
||||||
|
-ms-transition: all 0s ease-in-out;
|
||||||
|
-webkit-transition: all 0s ease-in-out;
|
||||||
|
-moz-transition: all .0s ease-in-out;
|
||||||
|
height: 100%;
|
||||||
|
background-color: white;
|
||||||
|
border-left: 0px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
.resourceTree {
|
.resourceTree {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
.main {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.resourceTreeScroll {
|
.resourceTreeScroll {
|
||||||
|
|||||||
998
package-lock.json
generated
998
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -6,10 +6,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "3.9.0",
|
"@azure/cosmos": "3.9.0",
|
||||||
|
"@azure/identity": "1.1.0",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.2.1",
|
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
|
||||||
"@jupyterlab/services": "6.0.0-rc.2",
|
"@jupyterlab/services": "6.0.0-rc.2",
|
||||||
"@jupyterlab/terminal": "3.0.0-rc.2",
|
"@jupyterlab/terminal": "3.0.0-rc.2",
|
||||||
"@microsoft/applicationinsights-web": "2.5.9",
|
"@microsoft/applicationinsights-web": "2.5.9",
|
||||||
@@ -38,7 +36,6 @@
|
|||||||
"@nteract/transform-vega": "7.0.6",
|
"@nteract/transform-vega": "7.0.6",
|
||||||
"@octokit/rest": "17.9.2",
|
"@octokit/rest": "17.9.2",
|
||||||
"@phosphor/widgets": "1.9.3",
|
"@phosphor/widgets": "1.9.3",
|
||||||
"@testing-library/jest-dom": "5.11.9",
|
|
||||||
"@types/mkdirp": "1.0.1",
|
"@types/mkdirp": "1.0.1",
|
||||||
"@types/node-fetch": "2.5.7",
|
"@types/node-fetch": "2.5.7",
|
||||||
"@uifabric/react-cards": "0.109.110",
|
"@uifabric/react-cards": "0.109.110",
|
||||||
@@ -47,7 +44,7 @@
|
|||||||
"applicationinsights": "1.8.0",
|
"applicationinsights": "1.8.0",
|
||||||
"babel-polyfill": "6.26.0",
|
"babel-polyfill": "6.26.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"canvas": "file:./canvas",
|
"canvas": "2.6.1",
|
||||||
"clean-webpack-plugin": "0.1.19",
|
"clean-webpack-plugin": "0.1.19",
|
||||||
"copy-webpack-plugin": "6.0.2",
|
"copy-webpack-plugin": "6.0.2",
|
||||||
"crossroads": "0.12.2",
|
"crossroads": "0.12.2",
|
||||||
@@ -72,7 +69,6 @@
|
|||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.18.1",
|
"monaco-editor": "0.18.1",
|
||||||
"msal": "1.4.4",
|
|
||||||
"object.entries": "1.1.0",
|
"object.entries": "1.1.0",
|
||||||
"office-ui-fabric-react": "7.134.1",
|
"office-ui-fabric-react": "7.134.1",
|
||||||
"p-retry": "4.2.0",
|
"p-retry": "4.2.0",
|
||||||
@@ -84,16 +80,14 @@
|
|||||||
"react-animate-height": "2.0.8",
|
"react-animate-height": "2.0.8",
|
||||||
"react-dnd": "9.4.0",
|
"react-dnd": "9.4.0",
|
||||||
"react-dnd-html5-backend": "9.4.0",
|
"react-dnd-html5-backend": "9.4.0",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.9.0",
|
||||||
"react-hotkeys": "2.0.0",
|
"react-hotkeys": "2.0.0",
|
||||||
"react-notification-system": "0.2.17",
|
"react-notification-system": "0.2.17",
|
||||||
"react-redux": "7.1.3",
|
"react-redux": "7.1.3",
|
||||||
"redux": "4.0.4",
|
"redux": "4.0.4",
|
||||||
"reflect-metadata": "0.1.13",
|
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
"rxjs": "6.6.3",
|
"rxjs": "6.6.3",
|
||||||
"styled-components": "4.3.2",
|
"styled-components": "4.3.2",
|
||||||
"swr": "0.4.0",
|
|
||||||
"text-encoding": "0.7.0",
|
"text-encoding": "0.7.0",
|
||||||
"underscore": "1.9.1",
|
"underscore": "1.9.1",
|
||||||
"url-polyfill": "1.1.7",
|
"url-polyfill": "1.1.7",
|
||||||
@@ -107,7 +101,6 @@
|
|||||||
"@babel/preset-env": "7.9.0",
|
"@babel/preset-env": "7.9.0",
|
||||||
"@babel/preset-react": "7.9.4",
|
"@babel/preset-react": "7.9.4",
|
||||||
"@babel/preset-typescript": "7.9.0",
|
"@babel/preset-typescript": "7.9.0",
|
||||||
"@testing-library/react": "11.2.3",
|
|
||||||
"@types/applicationinsights-js": "1.0.7",
|
"@types/applicationinsights-js": "1.0.7",
|
||||||
"@types/codemirror": "0.0.56",
|
"@types/codemirror": "0.0.56",
|
||||||
"@types/crossroads": "0.0.30",
|
"@types/crossroads": "0.0.30",
|
||||||
@@ -116,7 +109,7 @@
|
|||||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||||
"@types/expect-puppeteer": "4.4.3",
|
"@types/expect-puppeteer": "4.4.3",
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "23.3.10",
|
||||||
"@types/jest-environment-puppeteer": "4.3.2",
|
"@types/jest-environment-puppeteer": "4.3.2",
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
@@ -125,7 +118,7 @@
|
|||||||
"@types/puppeteer": "3.0.1",
|
"@types/puppeteer": "3.0.1",
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "16.9.56",
|
"@types/react": "16.9.56",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "16.0.7",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
"@types/sinon": "2.3.3",
|
"@types/sinon": "2.3.3",
|
||||||
@@ -135,6 +128,7 @@
|
|||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||||
"@typescript-eslint/parser": "4.0.1",
|
"@typescript-eslint/parser": "4.0.1",
|
||||||
|
"adal-angular": "1.0.15",
|
||||||
"axe-puppeteer": "1.1.0",
|
"axe-puppeteer": "1.1.0",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
@@ -149,7 +143,6 @@
|
|||||||
"eslint-cli": "1.1.1",
|
"eslint-cli": "1.1.1",
|
||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||||
"eslint-plugin-react-hooks": "4.2.0",
|
|
||||||
"expose-loader": "0.7.5",
|
"expose-loader": "0.7.5",
|
||||||
"file-loader": "2.0.0",
|
"file-loader": "2.0.0",
|
||||||
"fs-extra": "7.0.0",
|
"fs-extra": "7.0.0",
|
||||||
@@ -177,7 +170,7 @@
|
|||||||
"ts-loader": "6.2.2",
|
"ts-loader": "6.2.2",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typescript": "4.0.2",
|
"typescript": "4.1.2",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"wait-on": "4.0.2",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
|
|||||||
@@ -3,5 +3,4 @@ export enum AuthType {
|
|||||||
EncryptedToken = "encryptedtoken",
|
EncryptedToken = "encryptedtoken",
|
||||||
MasterKey = "masterkey",
|
MasterKey = "masterkey",
|
||||||
ResourceToken = "resourcetoken",
|
ResourceToken = "resourcetoken",
|
||||||
ConnectionString = "connectionstring",
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ReactBindingHandler from "./ReactBindingHandler";
|
import * as ReactBindingHandler from "./ReactBindingHandler";
|
||||||
import "../Explorer/Tables/DataTable/DataTableBindingManager";
|
|
||||||
|
|
||||||
export class BindingHandlersRegisterer {
|
export class BindingHandlersRegisterer {
|
||||||
public static registerBindingHandlers() {
|
public static registerBindingHandlers() {
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { HashMap } from "./HashMap";
|
||||||
|
|
||||||
|
export class AuthorizationEndpoints {
|
||||||
|
public static arm: string = "https://management.core.windows.net/";
|
||||||
|
public static common: string = "https://login.windows.net/";
|
||||||
|
}
|
||||||
|
|
||||||
export class CodeOfConductEndpoints {
|
export class CodeOfConductEndpoints {
|
||||||
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
|
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
|
||||||
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
|
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
|
||||||
@@ -106,6 +113,7 @@ export class Features {
|
|||||||
public static readonly enableTtl = "enablettl";
|
public static readonly enableTtl = "enablettl";
|
||||||
public static readonly enableNotebooks = "enablenotebooks";
|
public static readonly enableNotebooks = "enablenotebooks";
|
||||||
public static readonly enableGalleryPublish = "enablegallerypublish";
|
public static readonly enableGalleryPublish = "enablegallerypublish";
|
||||||
|
public static readonly enableCodeOfConduct = "enablecodeofconduct";
|
||||||
public static readonly enableLinkInjection = "enablelinkinjection";
|
public static readonly enableLinkInjection = "enablelinkinjection";
|
||||||
public static readonly enableSpark = "enablespark";
|
public static readonly enableSpark = "enablespark";
|
||||||
public static readonly livyEndpoint = "livyendpoint";
|
public static readonly livyEndpoint = "livyendpoint";
|
||||||
@@ -119,15 +127,12 @@ export class Features {
|
|||||||
public static readonly enableSchema = "enableschema";
|
public static readonly enableSchema = "enableschema";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||||
public static readonly showMinRUSurvey = "showminrusurvey";
|
public static readonly showMinRUSurvey = "showminrusurvey";
|
||||||
public static readonly selfServeType = "selfservetype";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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";
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
public static readonly MongoIndexing = "mongoindexing";
|
|
||||||
public static readonly AutoscaleTest = "autoscaletest";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -136,6 +141,19 @@ export class AfecFeatures {
|
|||||||
public static readonly StorageAnalytics = "storageanalytics-public-preview";
|
public static readonly StorageAnalytics = "storageanalytics-public-preview";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Spark {
|
||||||
|
public static readonly MaxWorkerCount = 10;
|
||||||
|
public static readonly SKUs: HashMap<string> = new HashMap({
|
||||||
|
"Cosmos.Spark.D1s": "D1s / 1 core / 4GB RAM",
|
||||||
|
"Cosmos.Spark.D2s": "D2s / 2 cores / 8GB RAM",
|
||||||
|
"Cosmos.Spark.D4s": "D4s / 4 cores / 16GB RAM",
|
||||||
|
"Cosmos.Spark.D8s": "D8s / 8 cores / 32GB RAM",
|
||||||
|
"Cosmos.Spark.D16s": "D16s / 16 cores / 64GB RAM",
|
||||||
|
"Cosmos.Spark.D32s": "D32s / 32 cores / 128GB RAM",
|
||||||
|
"Cosmos.Spark.D64s": "D64s / 64 cores / 256GB RAM",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export class TagNames {
|
export class TagNames {
|
||||||
public static defaultExperience: string = "defaultExperience";
|
public static defaultExperience: string = "defaultExperience";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
|
|
||||||
export function client(): Cosmos.CosmosClient {
|
export function client(): Cosmos.CosmosClient {
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here
|
||||||
key: userContext.masterKey,
|
key: userContext.masterKey,
|
||||||
tokenProvider,
|
tokenProvider,
|
||||||
connectionPolicy: {
|
connectionPolicy: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getCommonQueryOptions } from "./queryDocuments";
|
import { getCommonQueryOptions } from "./DataAccessUtilityBase";
|
||||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||||
|
|
||||||
describe("getCommonQueryOptions", () => {
|
describe("getCommonQueryOptions", () => {
|
||||||
it("builds the correct default options objects", () => {
|
it("builds the correct default options objects", () => {
|
||||||
155
src/Common/DataAccessUtilityBase.ts
Normal file
155
src/Common/DataAccessUtilityBase.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { ConflictDefinition, FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import Q from "q";
|
||||||
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
|
||||||
|
import * as Constants from "./Constants";
|
||||||
|
import { client } from "./CosmosClient";
|
||||||
|
|
||||||
|
export function getCommonQueryOptions(options: FeedOptions): any {
|
||||||
|
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
||||||
|
options = options || {};
|
||||||
|
options.populateQueryMetrics = true;
|
||||||
|
options.enableScanInQuery = options.enableScanInQuery || true;
|
||||||
|
if (!options.partitionKey) {
|
||||||
|
options.forceQueryPlan = true;
|
||||||
|
}
|
||||||
|
options.maxItemCount =
|
||||||
|
options.maxItemCount ||
|
||||||
|
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
||||||
|
Constants.Queries.itemsPerPage;
|
||||||
|
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queryDocuments(
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||||
|
options = getCommonQueryOptions(options);
|
||||||
|
const documentsIterator = client().database(databaseId).container(containerId).items.query(query, options);
|
||||||
|
return Q(documentsIterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
|
||||||
|
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
|
||||||
|
const partitionKeyValue: any = conflictId.partitionKeyValue;
|
||||||
|
|
||||||
|
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
|
||||||
|
if (!partitionKeyDefinition) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partitionKeyValue === undefined) {
|
||||||
|
return [{}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [partitionKeyValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDocument(
|
||||||
|
collection: ViewModels.CollectionBase,
|
||||||
|
documentId: DocumentId,
|
||||||
|
newDocument: any
|
||||||
|
): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), partitionKey)
|
||||||
|
.replace(newDocument)
|
||||||
|
.then((response) => response.resource)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeStoredProcedure(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
storedProcedure: StoredProcedure,
|
||||||
|
partitionKeyValue: any,
|
||||||
|
params: any[]
|
||||||
|
): Q.Promise<any> {
|
||||||
|
// TODO remove this deferred. Kept it because of timeout code at bottom of function
|
||||||
|
const deferred = Q.defer<any>();
|
||||||
|
|
||||||
|
client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.scripts.storedProcedure(storedProcedure.id())
|
||||||
|
.execute(partitionKeyValue, params, { enableScriptLogging: true })
|
||||||
|
.then((response) =>
|
||||||
|
deferred.resolve({
|
||||||
|
result: response.resource,
|
||||||
|
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch((error) => deferred.reject(error));
|
||||||
|
|
||||||
|
return deferred.promise.timeout(
|
||||||
|
Constants.ClientDefaults.requestTimeoutMs,
|
||||||
|
`Request timed out while executing stored procedure ${storedProcedure.id()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.items.create(newDocument)
|
||||||
|
.then((response) => response.resource)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.item(documentId.id(), partitionKey)
|
||||||
|
.read()
|
||||||
|
.then((response) => response.resource)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
|
const partitionKey = documentId.partitionKeyValue;
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client().database(collection.databaseId).container(collection.id()).item(documentId.id(), partitionKey).delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteConflict(
|
||||||
|
collection: ViewModels.CollectionBase,
|
||||||
|
conflictId: ConflictId,
|
||||||
|
options: any = {}
|
||||||
|
): Q.Promise<any> {
|
||||||
|
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
|
||||||
|
|
||||||
|
return Q(
|
||||||
|
client().database(collection.databaseId).container(collection.id()).conflict(conflictId.id()).delete(options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queryConflicts(
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||||
|
const documentsIterator = client().database(databaseId).container(containerId).conflicts.query(query, options);
|
||||||
|
return Q(documentsIterator);
|
||||||
|
}
|
||||||
217
src/Common/DocumentClientUtilityBase.ts
Normal file
217
src/Common/DocumentClientUtilityBase.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
|
import Q from "q";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import * as Constants from "./Constants";
|
||||||
|
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||||
|
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||||
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
|
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||||
|
export function queryDocuments(
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
|
||||||
|
return DataAccessUtilityBase.queryDocuments(databaseId, containerId, query, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queryConflicts(
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
query: string,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
|
||||||
|
return DataAccessUtilityBase.queryConflicts(databaseId, containerId, query, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEntityName() {
|
||||||
|
const defaultExperience =
|
||||||
|
window.dataExplorer && window.dataExplorer.defaultExperience && window.dataExplorer.defaultExperience();
|
||||||
|
if (defaultExperience === Constants.DefaultAccountExperience.MongoDB) {
|
||||||
|
return "document";
|
||||||
|
}
|
||||||
|
return "item";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function executeStoredProcedure(
|
||||||
|
collection: ViewModels.Collection,
|
||||||
|
storedProcedure: StoredProcedure,
|
||||||
|
partitionKeyValue: any,
|
||||||
|
params: any[]
|
||||||
|
): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
||||||
|
DataAccessUtilityBase.executeStoredProcedure(collection, storedProcedure, partitionKeyValue, params)
|
||||||
|
.then(
|
||||||
|
(response: any) => {
|
||||||
|
deferred.resolve(response);
|
||||||
|
logConsoleInfo(
|
||||||
|
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(
|
||||||
|
error,
|
||||||
|
"ExecuteStoredProcedure",
|
||||||
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
|
);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function queryDocumentsPage(
|
||||||
|
resourceName: string,
|
||||||
|
documentsIterator: MinimalQueryIterator,
|
||||||
|
firstItemIndex: number,
|
||||||
|
options: any
|
||||||
|
): Q.Promise<ViewModels.QueryResults> {
|
||||||
|
var deferred = Q.defer<ViewModels.QueryResults>();
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
||||||
|
Q(nextPage(documentsIterator, firstItemIndex))
|
||||||
|
.then(
|
||||||
|
(result: ViewModels.QueryResults) => {
|
||||||
|
const itemCount = (result.documents && result.documents.length) || 0;
|
||||||
|
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
||||||
|
deferred.resolve(result);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
||||||
|
DataAccessUtilityBase.readDocument(collection, documentId)
|
||||||
|
.then(
|
||||||
|
(document: any) => {
|
||||||
|
deferred.resolve(document);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDocument(
|
||||||
|
collection: ViewModels.CollectionBase,
|
||||||
|
documentId: DocumentId,
|
||||||
|
newDocument: any
|
||||||
|
): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
||||||
|
DataAccessUtilityBase.updateDocument(collection, documentId, newDocument)
|
||||||
|
.then(
|
||||||
|
(updatedDocument: any) => {
|
||||||
|
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
||||||
|
deferred.resolve(updatedDocument);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
DataAccessUtilityBase.createDocument(collection, newDocument)
|
||||||
|
.then(
|
||||||
|
(savedDocument: any) => {
|
||||||
|
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
||||||
|
deferred.resolve(savedDocument);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
const entityName = getEntityName();
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
||||||
|
DataAccessUtilityBase.deleteDocument(collection, documentId)
|
||||||
|
.then(
|
||||||
|
(response: any) => {
|
||||||
|
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
||||||
|
deferred.resolve(response);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteConflict(
|
||||||
|
collection: ViewModels.CollectionBase,
|
||||||
|
conflictId: ConflictId,
|
||||||
|
options?: any
|
||||||
|
): Q.Promise<any> {
|
||||||
|
var deferred = Q.defer<any>();
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
||||||
|
DataAccessUtilityBase.deleteConflict(collection, conflictId, options)
|
||||||
|
.then(
|
||||||
|
(response: any) => {
|
||||||
|
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
||||||
|
deferred.resolve(response);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
clearMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
|
|
||||||
export const getEntityName = (): string => {
|
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
|
|
||||||
return "document";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "item";
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
export function normalizeArmEndpoint(uri: string): string {
|
export default class EnvironmentUtility {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
public static normalizeArmEndpointUri(uri: string): string {
|
||||||
return `${uri}/`;
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
|
return `${uri}/`;
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
return uri;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const handleError = (error: string | ARMError | Error, area: string, cons
|
|||||||
sendNotificationForError(errorMessage, errorCode);
|
sendNotificationForError(errorMessage, errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getErrorMessage = (error: string | Error = ""): string => {
|
export const getErrorMessage = (error: string | Error): string => {
|
||||||
const errorMessage = typeof error === "string" ? error : error.message;
|
const errorMessage = typeof error === "string" ? error : error.message;
|
||||||
return replaceKnownError(errorMessage);
|
return replaceKnownError(errorMessage);
|
||||||
};
|
};
|
||||||
@@ -45,10 +45,10 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
|
|||||||
const replaceKnownError = (errorMessage: string): string => {
|
const replaceKnownError = (errorMessage: string): string => {
|
||||||
if (
|
if (
|
||||||
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||||
errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
|
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||||
) {
|
) {
|
||||||
return "Database throughput is not supported for internal subscriptions.";
|
return "Database throughput is not supported for internal subscriptions.";
|
||||||
} else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) {
|
} 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 "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition,
|
offerDefinition: mockOfferDefinition,
|
||||||
offerReplacePending: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
@@ -56,7 +55,6 @@ describe("parseSDKOfferResponse", () => {
|
|||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerDefinition: mockOfferDefinition,
|
offerDefinition: mockOfferDefinition,
|
||||||
offerReplacePending: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
expect(OfferUtility.parseSDKOfferResponse(mockResponse)).toEqual(expectedResult);
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
|
||||||
import { OfferResponse } from "@azure/cosmos";
|
import { OfferResponse } from "@azure/cosmos";
|
||||||
import { HttpHeaders } from "./Constants";
|
|
||||||
|
|
||||||
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer | undefined => {
|
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
|
||||||
const offerDefinition: SDKOfferDefinition | undefined = offerResponse?.resource;
|
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
|
||||||
if (!offerDefinition) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const offerContent = offerDefinition.content;
|
const offerContent = offerDefinition.content;
|
||||||
if (!offerContent) {
|
if (!offerContent) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -15,14 +11,14 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer | und
|
|||||||
const minimumThroughput = offerContent.collectionThroughputInfo?.minimumRUForCollection;
|
const minimumThroughput = offerContent.collectionThroughputInfo?.minimumRUForCollection;
|
||||||
const autopilotSettings = offerContent.offerAutopilotSettings;
|
const autopilotSettings = offerContent.offerAutopilotSettings;
|
||||||
|
|
||||||
if (autopilotSettings && autopilotSettings.maxThroughput && minimumThroughput) {
|
if (autopilotSettings) {
|
||||||
return {
|
return {
|
||||||
id: offerDefinition.id,
|
id: offerDefinition.id,
|
||||||
autoscaleMaxThroughput: autopilotSettings.maxThroughput,
|
autoscaleMaxThroughput: autopilotSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true",
|
headers: offerResponse.headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +28,6 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer | und
|
|||||||
manualThroughput: offerContent.offerThroughput,
|
manualThroughput: offerContent.offerThroughput,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerDefinition,
|
offerDefinition,
|
||||||
offerReplacePending: offerResponse.headers?.[HttpHeaders.offerReplacePending] === "true",
|
headers: offerResponse.headers,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ import * as _ from "underscore";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
|
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { QueryUtils } from "../Utils/QueryUtils";
|
import { QueryUtils } from "../Utils/QueryUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { queryDocumentsPage } from "./dataAccess/queryDocumentsPage";
|
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import { handleError } from "./ErrorHandlingUtils";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
import { createDocument } from "./dataAccess/createDocument";
|
|
||||||
import { deleteDocument } from "./dataAccess/deleteDocument";
|
|
||||||
import { queryDocuments } from "./dataAccess/queryDocuments";
|
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -33,7 +31,10 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queriesCollection.rawDataModel);
|
return Promise.resolve(queriesCollection.rawDataModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
"Setting up account for saving queries"
|
||||||
|
);
|
||||||
return createCollection({
|
return createCollection({
|
||||||
collectionId: SavedQueries.CollectionName,
|
collectionId: SavedQueries.CollectionName,
|
||||||
createNewDatabase: true,
|
createNewDatabase: true,
|
||||||
@@ -44,7 +45,10 @@ export class QueriesClient {
|
|||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
(collection: DataModels.Collection) => {
|
(collection: DataModels.Collection) => {
|
||||||
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
"Successfully set up account for saving queries"
|
||||||
|
);
|
||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -52,14 +56,17 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => clearMessage());
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveQuery(query: DataModels.Query): Promise<void> {
|
public async saveQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to save query ${query.queryName}: ${errorMessage}`
|
||||||
|
);
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,16 +74,25 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to save query ${query.queryName}: ${errorMessage}`
|
||||||
|
);
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Saving query ${query.queryName}`);
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Saving query ${query.queryName}`
|
||||||
|
);
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
return createDocument(queriesCollection, query)
|
return createDocument(queriesCollection, query)
|
||||||
.then(
|
.then(
|
||||||
(savedQuery: DataModels.Query) => {
|
(savedQuery: DataModels.Query) => {
|
||||||
NotificationConsoleUtils.logConsoleInfo(`Successfully saved query ${query.queryName}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
`Successfully saved query ${query.queryName}`
|
||||||
|
);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -87,65 +103,74 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => clearMessage());
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getQueries(): Promise<DataModels.Query[]> {
|
public async getQueries(): Promise<DataModels.Query[]> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to fetch saved queries: ${errorMessage}`
|
||||||
|
);
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: any = { enableCrossPartitionQuery: true };
|
const options: any = { enableCrossPartitionQuery: true };
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
|
||||||
const queryIterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
|
||||||
SavedQueries.DatabaseName,
|
|
||||||
SavedQueries.CollectionName,
|
|
||||||
this.fetchQueriesQuery(),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
const fetchQueries = async (firstItemIndex: number): Promise<ViewModels.QueryResults> =>
|
|
||||||
await queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex);
|
|
||||||
return QueryUtils.queryAllPages(fetchQueries)
|
|
||||||
.then(
|
.then(
|
||||||
(results: ViewModels.QueryResults) => {
|
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
|
||||||
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
|
||||||
if (!document) {
|
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
|
||||||
return undefined;
|
return QueryUtils.queryAllPages(fetchQueries).then(
|
||||||
|
(results: ViewModels.QueryResults) => {
|
||||||
|
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
|
||||||
|
if (!document) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { id, resourceId, query, queryName } = document;
|
||||||
|
const parsedQuery: DataModels.Query = {
|
||||||
|
resourceId: resourceId,
|
||||||
|
queryName: queryName,
|
||||||
|
query: query,
|
||||||
|
id: id,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
this.validateQuery(parsedQuery);
|
||||||
|
return parsedQuery;
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
||||||
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries");
|
||||||
|
return Promise.resolve(queries);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
const { id, resourceId, query, queryName } = document;
|
);
|
||||||
const parsedQuery: DataModels.Query = {
|
|
||||||
resourceId: resourceId,
|
|
||||||
queryName: queryName,
|
|
||||||
query: query,
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
this.validateQuery(parsedQuery);
|
|
||||||
return parsedQuery;
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
|
||||||
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
|
||||||
return Promise.resolve(queries);
|
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
|
// should never get into this state but we handle this regardless
|
||||||
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => clearMessage());
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
||||||
const queriesCollection = this.findQueriesCollection();
|
const queriesCollection = this.findQueriesCollection();
|
||||||
if (!queriesCollection) {
|
if (!queriesCollection) {
|
||||||
const errorMessage: string = "Account not set up to perform saved query operations";
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to fetch saved queries: ${errorMessage}`
|
||||||
|
);
|
||||||
return Promise.reject(errorMessage);
|
return Promise.reject(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,10 +178,16 @@ export class QueriesClient {
|
|||||||
this.validateQuery(query);
|
this.validateQuery(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage: string = "Invalid query specified";
|
const errorMessage: string = "Invalid query specified";
|
||||||
NotificationConsoleUtils.logConsoleError(`Failed to delete query ${query.queryName}: ${errorMessage}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Error,
|
||||||
|
`Failed to delete query ${query.queryName}: ${errorMessage}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting query ${query.queryName}`);
|
const id = NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Deleting query ${query.queryName}`
|
||||||
|
);
|
||||||
query.id = query.queryName;
|
query.id = query.queryName;
|
||||||
const documentId = new DocumentId(
|
const documentId = new DocumentId(
|
||||||
{
|
{
|
||||||
@@ -170,7 +201,10 @@ export class QueriesClient {
|
|||||||
return deleteDocument(queriesCollection, documentId)
|
return deleteDocument(queriesCollection, documentId)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted query ${query.queryName}`);
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
|
ConsoleDataType.Info,
|
||||||
|
`Successfully deleted query ${query.queryName}`
|
||||||
|
);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
@@ -178,7 +212,7 @@ export class QueriesClient {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => clearMessage());
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getResourceId(): string {
|
public getResourceId(): string {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export class Splitter {
|
|||||||
public splitterId: string;
|
public splitterId: string;
|
||||||
public leftSideId: string;
|
public leftSideId: string;
|
||||||
|
|
||||||
public splitter!: HTMLElement;
|
public splitter: HTMLElement;
|
||||||
public leftSide!: HTMLElement;
|
public leftSide: HTMLElement;
|
||||||
public lastX!: number;
|
public lastX: number;
|
||||||
public lastWidth!: number;
|
public lastWidth: number;
|
||||||
|
|
||||||
private isCollapsed: ko.Observable<boolean>;
|
private isCollapsed: ko.Observable<boolean>;
|
||||||
private bounds: SplitterBounds;
|
private bounds: SplitterBounds;
|
||||||
@@ -42,10 +42,9 @@ export class Splitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public initialize() {
|
public initialize() {
|
||||||
if (document.getElementById(this.splitterId) !== null && document.getElementById(this.leftSideId) != null) {
|
this.splitter = document.getElementById(this.splitterId);
|
||||||
this.splitter = <HTMLElement>document.getElementById(this.splitterId);
|
this.leftSide = document.getElementById(this.leftSideId);
|
||||||
this.leftSide = <HTMLElement>document.getElementById(this.leftSideId);
|
|
||||||
}
|
|
||||||
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
|
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
|
||||||
const splitterOptions: JQueryUI.ResizableOptions = {
|
const splitterOptions: JQueryUI.ResizableOptions = {
|
||||||
animate: true,
|
animate: true,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
jest.mock("../../Utils/arm/request");
|
jest.mock("../../Utils/arm/request");
|
||||||
jest.mock("../CosmosClient");
|
jest.mock("../CosmosClient");
|
||||||
|
jest.mock("../DataAccessUtilityBase");
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
|
|
||||||
export const createDocument = async (collection: CollectionBase, newDocument: unknown): Promise<unknown> => {
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.items.create(newDocument);
|
|
||||||
|
|
||||||
logConsoleInfo(`Successfully created new ${entityName} for container ${collection.id()}`);
|
|
||||||
return response?.resource;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import ConflictId from "../../Explorer/Tree/ConflictId";
|
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
|
||||||
import { RequestOptions } from "@azure/cosmos";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
|
|
||||||
export const deleteConflict = async (collection: CollectionBase, conflictId: ConflictId): Promise<void> => {
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting conflict ${conflictId.id()}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
partitionKey: getPartitionKeyHeaderForConflict(conflictId),
|
|
||||||
};
|
|
||||||
|
|
||||||
await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.conflict(conflictId.id())
|
|
||||||
.delete(options as RequestOptions);
|
|
||||||
logConsoleInfo(`Successfully deleted conflict ${conflictId.id()}`);
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPartitionKeyHeaderForConflict = (conflictId: ConflictId): unknown => {
|
|
||||||
if (!conflictId.partitionKey) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return conflictId.partitionKeyValue === undefined ? [{}] : [conflictId.partitionKeyValue];
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
|
||||||
|
|
||||||
export const deleteDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<void> => {
|
|
||||||
const entityName: string = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Deleting ${entityName} ${documentId.id()}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), documentId.partitionKeyValue)
|
|
||||||
.delete();
|
|
||||||
logConsoleInfo(`Successfully deleted ${entityName} ${documentId.id()}`);
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { Collection } from "../../Contracts/ViewModels";
|
|
||||||
import { ClientDefaults, HttpHeaders } from "../Constants";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import StoredProcedure from "../../Explorer/Tree/StoredProcedure";
|
|
||||||
|
|
||||||
export interface ExecuteSprocResult {
|
|
||||||
result: StoredProcedure;
|
|
||||||
scriptLogs: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const executeStoredProcedure = async (
|
|
||||||
collection: Collection,
|
|
||||||
storedProcedure: StoredProcedure,
|
|
||||||
partitionKeyValue: string,
|
|
||||||
params: string[]
|
|
||||||
): Promise<ExecuteSprocResult> => {
|
|
||||||
const clearMessage = logConsoleProgress(`Executing stored procedure ${storedProcedure.id()}`);
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
throw Error(`Request timed out while executing stored procedure ${storedProcedure.id()}`);
|
|
||||||
}, ClientDefaults.requestTimeoutMs);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.scripts.storedProcedure(storedProcedure.id())
|
|
||||||
.execute(partitionKeyValue, params, { enableScriptLogging: true });
|
|
||||||
clearTimeout(timeout);
|
|
||||||
logConsoleInfo(
|
|
||||||
`Finished executing stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
result: response.resource,
|
|
||||||
scriptLogs: response.headers[HttpHeaders.scriptLogResults] as string,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"ExecuteStoredProcedure",
|
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AuthType } from "../../AuthType";
|
|
||||||
import { armRequest } from "../../Utils/arm/request";
|
import { armRequest } from "../../Utils/arm/request";
|
||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
@@ -41,10 +40,6 @@ interface MetricsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
|
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
|
||||||
if (window.authType !== AuthType.AAD) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const resourceGroup = userContext.resourceGroup;
|
const resourceGroup = userContext.resourceGroup;
|
||||||
const accountName = userContext.databaseAccount.name;
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { ConflictDefinition, FeedOptions, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
|
|
||||||
export const queryConflicts = (
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: FeedOptions
|
|
||||||
): QueryIterator<ConflictDefinition & Resource> => {
|
|
||||||
return client().database(databaseId).container(containerId).conflicts.query(query, options);
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Queries } from "../Constants";
|
|
||||||
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
|
|
||||||
export const queryDocuments = (
|
|
||||||
databaseId: string,
|
|
||||||
containerId: string,
|
|
||||||
query: string,
|
|
||||||
options: FeedOptions
|
|
||||||
): QueryIterator<ItemDefinition & Resource> => {
|
|
||||||
options = getCommonQueryOptions(options);
|
|
||||||
return client().database(databaseId).container(containerId).items.query(query, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
|
|
||||||
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
|
|
||||||
options = options || {};
|
|
||||||
options.populateQueryMetrics = true;
|
|
||||||
options.enableScanInQuery = options.enableScanInQuery || true;
|
|
||||||
if (!options.partitionKey) {
|
|
||||||
options.forceQueryPlan = true;
|
|
||||||
}
|
|
||||||
options.maxItemCount =
|
|
||||||
options.maxItemCount ||
|
|
||||||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
|
|
||||||
Queries.itemsPerPage;
|
|
||||||
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { QueryResults } from "../../Contracts/ViewModels";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { MinimalQueryIterator, nextPage } from "../IteratorUtilities";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
|
||||||
|
|
||||||
export const queryDocumentsPage = async (
|
|
||||||
resourceName: string,
|
|
||||||
documentsIterator: MinimalQueryIterator,
|
|
||||||
firstItemIndex: number
|
|
||||||
): Promise<QueryResults> => {
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex);
|
|
||||||
const itemCount = (result.documents && result.documents.length) || 0;
|
|
||||||
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -106,7 +106,6 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerReplacePending: resource.offerReplacePending === "true",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +114,6 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerReplacePending: resource.offerReplacePending === "true",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
autoscaleMaxThroughput: autoscaleSettings.maxThroughput,
|
||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerReplacePending: resource.offerReplacePending === "true",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ const readDatabaseOfferWithARM = async (databaseId: string): Promise<Offer> => {
|
|||||||
autoscaleMaxThroughput: undefined,
|
autoscaleMaxThroughput: undefined,
|
||||||
manualThroughput: resource.throughput,
|
manualThroughput: resource.throughput,
|
||||||
minimumThroughput,
|
minimumThroughput,
|
||||||
offerReplacePending: resource.offerReplacePending === "true",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Item } from "@azure/cosmos";
|
|
||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
|
||||||
|
|
||||||
export const readDocument = async (collection: CollectionBase, documentId: DocumentId): Promise<Item> => {
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Reading ${entityName} ${documentId.id()}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), documentId.partitionKeyValue)
|
|
||||||
.read();
|
|
||||||
|
|
||||||
return response?.resource;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { CollectionBase } from "../../Contracts/ViewModels";
|
|
||||||
import { Item } from "@azure/cosmos";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { getEntityName } from "../DocumentUtility";
|
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import DocumentId from "../../Explorer/Tree/DocumentId";
|
|
||||||
|
|
||||||
export const updateDocument = async (
|
|
||||||
collection: CollectionBase,
|
|
||||||
documentId: DocumentId,
|
|
||||||
newDocument: Item
|
|
||||||
): Promise<Item> => {
|
|
||||||
const entityName = getEntityName();
|
|
||||||
const clearMessage = logConsoleProgress(`Updating ${entityName} ${documentId.id()}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await client()
|
|
||||||
.database(collection.databaseId)
|
|
||||||
.container(collection.id())
|
|
||||||
.item(documentId.id(), documentId.partitionKeyValue)
|
|
||||||
.replace(newDocument);
|
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated ${entityName} ${documentId.id()}`);
|
|
||||||
return response?.resource;
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -104,7 +104,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
const platform = params.get("platform");
|
const platform = params.get("platform");
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
default:
|
default:
|
||||||
console.error(`Invalid platform query parameter: ${platform}`);
|
console.log("Invalid platform query parameter given, ignoring");
|
||||||
break;
|
break;
|
||||||
case Platform.Portal:
|
case Platform.Portal:
|
||||||
case Platform.Hosted:
|
case Platform.Hosted:
|
||||||
@@ -113,7 +113,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("No configuration file found using defaults");
|
console.log("No configuration file found using defaults");
|
||||||
}
|
}
|
||||||
return configContext;
|
return configContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,11 +210,11 @@ export interface QueryMetrics {
|
|||||||
|
|
||||||
export interface Offer {
|
export interface Offer {
|
||||||
id: string;
|
id: string;
|
||||||
autoscaleMaxThroughput: number | undefined;
|
autoscaleMaxThroughput: number;
|
||||||
manualThroughput: number | undefined;
|
manualThroughput: number;
|
||||||
minimumThroughput: number | undefined;
|
minimumThroughput: number;
|
||||||
offerDefinition?: SDKOfferDefinition;
|
offerDefinition?: SDKOfferDefinition;
|
||||||
offerReplacePending: boolean;
|
headers?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDKOfferDefinition extends Resource {
|
export interface SDKOfferDefinition extends Resource {
|
||||||
@@ -587,3 +587,11 @@ export interface MemoryUsageInfo {
|
|||||||
freeKB: number;
|
freeKB: number;
|
||||||
totalKB: number;
|
totalKB: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface resourceTokenConnectionStringProperties {
|
||||||
|
accountEndpoint: string;
|
||||||
|
collectionId: string;
|
||||||
|
databaseId: string;
|
||||||
|
partitionKey?: string;
|
||||||
|
resourceToken: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import DocumentId from "../Explorer/Tree/DocumentId";
|
|||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import Trigger from "../Explorer/Tree/Trigger";
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
|
||||||
import { UploadDetails } from "../workers/upload/definitions";
|
import { UploadDetails } from "../workers/upload/definitions";
|
||||||
import * as DataModels from "./DataModels";
|
import * as DataModels from "./DataModels";
|
||||||
import { SubscriptionType } from "./SubscriptionType";
|
import { SubscriptionType } from "./SubscriptionType";
|
||||||
@@ -363,7 +362,7 @@ export enum CollectionTabKind {
|
|||||||
Gallery = 17,
|
Gallery = 17,
|
||||||
NotebookViewer = 18,
|
NotebookViewer = 18,
|
||||||
Schema = 19,
|
Schema = 19,
|
||||||
SettingsV2 = 20,
|
SettingsV2 = 19,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
@@ -396,7 +395,6 @@ export interface DataExplorerInputsFrame {
|
|||||||
isAuthWithresourceToken?: boolean;
|
isAuthWithresourceToken?: boolean;
|
||||||
defaultCollectionThroughput?: CollectionCreationDefaults;
|
defaultCollectionThroughput?: CollectionCreationDefaults;
|
||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
selfServeType?: SelfServeType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionCreationDefaults {
|
export interface CollectionCreationDefaults {
|
||||||
|
|||||||
383
src/Definitions/adal.d.ts
vendored
Normal file
383
src/Definitions/adal.d.ts
vendored
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
// Type definitions for adal-angular 1.0.1.1
|
||||||
|
// Project: https://github.com/AzureAD/azure-activedirectory-library-for-js#readme
|
||||||
|
// Definitions by: Daniel Perez Alvarez <https://github.com/unindented>
|
||||||
|
// Anthony Ciccarello <https://github.com/aciccarello>
|
||||||
|
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||||
|
|
||||||
|
//This is a customized version of adal on top of version 1.0.1 which does not support multi tenant
|
||||||
|
// Customized version add tenantId to stored tokens so when tenant change, adal will refetch instead of read from sessionStorage
|
||||||
|
|
||||||
|
// In module contexts the class constructor function is the exported object
|
||||||
|
// export = AuthenticationContext;
|
||||||
|
|
||||||
|
// This class is defined globally in not in a module context
|
||||||
|
declare class AuthenticationContext {
|
||||||
|
instance: string;
|
||||||
|
config: AuthenticationContext.Options;
|
||||||
|
callback: AuthenticationContext.TokenCallback;
|
||||||
|
popUp: boolean;
|
||||||
|
isAngular: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for request type
|
||||||
|
*/
|
||||||
|
REQUEST_TYPE: AuthenticationContext.RequestType;
|
||||||
|
RESPONSE_TYPE: AuthenticationContext.ResponseType;
|
||||||
|
CONSTANTS: AuthenticationContext.Constants;
|
||||||
|
|
||||||
|
constructor(options: AuthenticationContext.Options);
|
||||||
|
/**
|
||||||
|
* Initiates the login process by redirecting the user to Azure AD authorization endpoint.
|
||||||
|
*/
|
||||||
|
login(): void;
|
||||||
|
/**
|
||||||
|
* Returns whether a login is in progress.
|
||||||
|
*/
|
||||||
|
loginInProgress(): boolean;
|
||||||
|
/**
|
||||||
|
* Gets token for the specified resource from the cache.
|
||||||
|
* @param resource A URI that identifies the resource for which the token is requested.
|
||||||
|
* @param tenantId tenant Id.
|
||||||
|
*/
|
||||||
|
getCachedToken(resource: string, tenantId: string): string;
|
||||||
|
/**
|
||||||
|
* If user object exists, returns it. Else creates a new user object by decoding `id_token` from the cache.
|
||||||
|
*/
|
||||||
|
getCachedUser(): AuthenticationContext.UserInfo;
|
||||||
|
/**
|
||||||
|
* Adds the passed callback to the array of callbacks for the specified resource.
|
||||||
|
* @param resource A URI that identifies the resource for which the token is requested.
|
||||||
|
* @param expectedState A unique identifier (guid).
|
||||||
|
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||||
|
*/
|
||||||
|
registerCallback(
|
||||||
|
expectedState: string,
|
||||||
|
resource: string,
|
||||||
|
callback: AuthenticationContext.TokenCallback,
|
||||||
|
tenantId: string
|
||||||
|
): void;
|
||||||
|
/**
|
||||||
|
* Acquires token from the cache if it is not expired. Otherwise sends request to AAD to obtain a new token.
|
||||||
|
* @param resource Resource URI identifying the target resource.
|
||||||
|
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||||
|
*/
|
||||||
|
acquireToken(resource: string, tenantId: string, callback: AuthenticationContext.TokenCallback): void;
|
||||||
|
/**
|
||||||
|
* Acquires token (interactive flow using a popup window) by sending request to AAD to obtain a new token.
|
||||||
|
* @param resource Resource URI identifying the target resource.
|
||||||
|
* @param extraQueryParameters Query parameters to add to the authentication request.
|
||||||
|
* @param claims Claims to add to the authentication request.
|
||||||
|
* @param callback The callback provided by the caller. It will be called with token or error.
|
||||||
|
*/
|
||||||
|
acquireTokenPopup(
|
||||||
|
resource: string,
|
||||||
|
tenantId: string,
|
||||||
|
extraQueryParameters: string | null | undefined,
|
||||||
|
claims: string | null | undefined,
|
||||||
|
callback: AuthenticationContext.TokenCallback
|
||||||
|
): void;
|
||||||
|
/**
|
||||||
|
* Acquires token (interactive flow using a redirect) by sending request to AAD to obtain a new token. In this case the callback passed in the authentication request constructor will be called.
|
||||||
|
* @param resource Resource URI identifying the target resource.
|
||||||
|
* @param extraQueryParameters Query parameters to add to the authentication request.
|
||||||
|
* @param claims Claims to add to the authentication request.
|
||||||
|
*/
|
||||||
|
acquireTokenRedirect(
|
||||||
|
resource: string,
|
||||||
|
tenantId: string,
|
||||||
|
extraQueryParameters?: string | null,
|
||||||
|
claims?: string | null
|
||||||
|
): void;
|
||||||
|
/**
|
||||||
|
* Redirects the browser to Azure AD authorization endpoint.
|
||||||
|
* @param urlNavigate URL of the authorization endpoint.
|
||||||
|
*/
|
||||||
|
promptUser(urlNavigate: string): void;
|
||||||
|
/**
|
||||||
|
* Clears cache items.
|
||||||
|
*/
|
||||||
|
clearCache(): void;
|
||||||
|
/**
|
||||||
|
* Clears cache items for a given resource.
|
||||||
|
* @param resource Resource URI identifying the target resource.
|
||||||
|
*/
|
||||||
|
clearCacheForResource(resource: string): void;
|
||||||
|
/**
|
||||||
|
* Redirects user to logout endpoint. After logout, it will redirect to `postLogoutRedirectUri` if added as a property on the config object.
|
||||||
|
*/
|
||||||
|
logOut(): void;
|
||||||
|
/**
|
||||||
|
* Calls the passed in callback with the user object or error message related to the user.
|
||||||
|
* @param callback The callback provided by the caller. It will be called with user or error.
|
||||||
|
*/
|
||||||
|
getUser(callback: AuthenticationContext.UserCallback): void;
|
||||||
|
/**
|
||||||
|
* Checks if the URL fragment contains access token, id token or error description.
|
||||||
|
* @param hash Hash passed from redirect page.
|
||||||
|
*/
|
||||||
|
isCallback(hash: string): boolean;
|
||||||
|
/**
|
||||||
|
* Gets login error.
|
||||||
|
*/
|
||||||
|
getLoginError(): string;
|
||||||
|
/**
|
||||||
|
* Creates a request info object from the URL fragment and returns it.
|
||||||
|
*/
|
||||||
|
getRequestInfo(hash: string): AuthenticationContext.RequestInfo;
|
||||||
|
/**
|
||||||
|
* Saves token or error received in the response from AAD in the cache. In case of `id_token`, it also creates the user object.
|
||||||
|
*/
|
||||||
|
saveTokenFromHash(requestInfo: AuthenticationContext.RequestInfo): void;
|
||||||
|
/**
|
||||||
|
* Gets resource for given endpoint if mapping is provided with config.
|
||||||
|
* @param endpoint Resource URI identifying the target resource.
|
||||||
|
*/
|
||||||
|
getResourceForEndpoint(resource: string): string;
|
||||||
|
/**
|
||||||
|
* This method must be called for processing the response received from AAD. It extracts the hash, processes the token or error, saves it in the cache and calls the callbacks with the result.
|
||||||
|
* @param hash Hash fragment of URL. Defaults to `window.location.hash`.
|
||||||
|
*/
|
||||||
|
handleWindowCallback(hash?: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the logging Level, constructs the log message and logs it. Users need to implement/override this method to turn on logging.
|
||||||
|
* @param level Level can be set 0, 1, 2 and 3 which turns on 'error', 'warning', 'info' or 'verbose' level logging respectively.
|
||||||
|
* @param message Message to log.
|
||||||
|
* @param error Error to log.
|
||||||
|
*/
|
||||||
|
log(level: AuthenticationContext.LoggingLevel, message: string, error: any): void;
|
||||||
|
/**
|
||||||
|
* Logs messages when logging level is set to 0.
|
||||||
|
* @param message Message to log.
|
||||||
|
* @param error Error to log.
|
||||||
|
*/
|
||||||
|
error(message: string, error: any): void;
|
||||||
|
/**
|
||||||
|
* Logs messages when logging level is set to 1.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
warn(message: string): void;
|
||||||
|
/**
|
||||||
|
* Logs messages when logging level is set to 2.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
info(message: string): void;
|
||||||
|
/**
|
||||||
|
* Logs messages when logging level is set to 3.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
verbose(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs Pii messages when Logging Level is set to 0 and window.piiLoggingEnabled is set to true.
|
||||||
|
* @param message Message to log.
|
||||||
|
* @param error Error to log.
|
||||||
|
*/
|
||||||
|
errorPii(message: string, error: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs Pii messages when Logging Level is set to 1 and window.piiLoggingEnabled is set to true.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
warnPii(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs messages when Logging Level is set to 2 and window.piiLoggingEnabled is set to true.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
infoPii(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs messages when Logging Level is set to 3 and window.piiLoggingEnabled is set to true.
|
||||||
|
* @param message Message to log.
|
||||||
|
*/
|
||||||
|
verbosePii(message: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace AuthenticationContext {
|
||||||
|
function inject(config: Options): AuthenticationContext;
|
||||||
|
|
||||||
|
type LoggingLevel = 0 | 1 | 2 | 3;
|
||||||
|
|
||||||
|
type RequestType = "LOGIN" | "RENEW_TOKEN" | "UNKNOWN";
|
||||||
|
|
||||||
|
type ResponseType = "id_token token" | "token";
|
||||||
|
|
||||||
|
interface RequestInfo {
|
||||||
|
/**
|
||||||
|
* Object comprising of fields such as id_token/error, session_state, state, e.t.c.
|
||||||
|
*/
|
||||||
|
parameters: any;
|
||||||
|
/**
|
||||||
|
* Request type.
|
||||||
|
*/
|
||||||
|
requestType: RequestType;
|
||||||
|
/**
|
||||||
|
* Whether state is valid.
|
||||||
|
*/
|
||||||
|
stateMatch: boolean;
|
||||||
|
/**
|
||||||
|
* Unique guid used to match the response with the request.
|
||||||
|
*/
|
||||||
|
stateResponse: string;
|
||||||
|
/**
|
||||||
|
* Whether `requestType` contains `id_token`, `access_token` or error.
|
||||||
|
*/
|
||||||
|
valid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserInfo {
|
||||||
|
/**
|
||||||
|
* Username assigned from UPN or email.
|
||||||
|
*/
|
||||||
|
userName: string;
|
||||||
|
/**
|
||||||
|
* Properties parsed from `id_token`.
|
||||||
|
*/
|
||||||
|
profile: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenCallback = (errorDesc: string | null, token: string | null, error: any) => void;
|
||||||
|
|
||||||
|
type UserCallback = (errorDesc: string | null, user: UserInfo | null) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for Authentication Context
|
||||||
|
*/
|
||||||
|
interface Options {
|
||||||
|
/**
|
||||||
|
* Client ID assigned to your app by Azure Active Directory.
|
||||||
|
*/
|
||||||
|
clientId: string;
|
||||||
|
/**
|
||||||
|
* Endpoint at which you expect to receive tokens.Defaults to `window.location.href`.
|
||||||
|
*/
|
||||||
|
redirectUri?: string;
|
||||||
|
/**
|
||||||
|
* Azure Active Directory instance. Defaults to `https://login.microsoftonline.com/`.
|
||||||
|
*/
|
||||||
|
instance?: string;
|
||||||
|
/**
|
||||||
|
* Your target tenant. Defaults to `common`.
|
||||||
|
*/
|
||||||
|
tenant?: string;
|
||||||
|
/**
|
||||||
|
* Query parameters to add to the authentication request.
|
||||||
|
*/
|
||||||
|
extraQueryParameter?: string;
|
||||||
|
/**
|
||||||
|
* Unique identifier used to map the request with the response. Defaults to RFC4122 version 4 guid (128 bits).
|
||||||
|
*/
|
||||||
|
correlationId?: string;
|
||||||
|
/**
|
||||||
|
* User defined function of handling the navigation to Azure AD authorization endpoint in case of login.
|
||||||
|
*/
|
||||||
|
displayCall?: (url: string) => void;
|
||||||
|
/**
|
||||||
|
* Set this to true to enable login in a popup winodow instead of a full redirect. Defaults to `false`.
|
||||||
|
*/
|
||||||
|
popUp?: boolean;
|
||||||
|
/**
|
||||||
|
* Set this to the resource to request on login. Defaults to `clientId`.
|
||||||
|
*/
|
||||||
|
loginResource?: string;
|
||||||
|
/**
|
||||||
|
* Set this to redirect the user to a custom login page.
|
||||||
|
*/
|
||||||
|
localLoginUrl?: string;
|
||||||
|
/**
|
||||||
|
* Redirects to start page after login. Defaults to `true`.
|
||||||
|
*/
|
||||||
|
navigateToLoginRequestUrl?: boolean;
|
||||||
|
/**
|
||||||
|
* Set this to redirect the user to a custom logout page.
|
||||||
|
*/
|
||||||
|
logOutUri?: string;
|
||||||
|
/**
|
||||||
|
* Redirects the user to postLogoutRedirectUri after logout. Defaults to `redirectUri`.
|
||||||
|
*/
|
||||||
|
postLogoutRedirectUri?: string;
|
||||||
|
/**
|
||||||
|
* Sets browser storage to either 'localStorage' or sessionStorage'. Defaults to `sessionStorage`.
|
||||||
|
*/
|
||||||
|
cacheLocation?: "localStorage" | "sessionStorage";
|
||||||
|
/**
|
||||||
|
* Array of keywords or URIs. Adal will attach a token to outgoing requests that have these keywords or URIs.
|
||||||
|
*/
|
||||||
|
endpoints?: { [resource: string]: string };
|
||||||
|
/**
|
||||||
|
* Array of keywords or URIs. Adal will not attach a token to outgoing requests that have these keywords or URIs.
|
||||||
|
*/
|
||||||
|
anonymousEndpoints?: string[];
|
||||||
|
/**
|
||||||
|
* If the cached token is about to be expired in the expireOffsetSeconds (in seconds), Adal will renew the token instead of using the cached token. Defaults to 300 seconds.
|
||||||
|
*/
|
||||||
|
expireOffsetSeconds?: number;
|
||||||
|
/**
|
||||||
|
* The number of milliseconds of inactivity before a token renewal response from AAD should be considered timed out. Defaults to 6 seconds.
|
||||||
|
*/
|
||||||
|
loadFrameTimeout?: number;
|
||||||
|
/**
|
||||||
|
* Callback to be invoked when a token is acquired.
|
||||||
|
*/
|
||||||
|
callback?: TokenCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoggingConfig {
|
||||||
|
level: LoggingLevel;
|
||||||
|
log: (message: string) => void;
|
||||||
|
piiLoggingEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for storage constants
|
||||||
|
*/
|
||||||
|
interface Constants {
|
||||||
|
ACCESS_TOKEN: "access_token";
|
||||||
|
EXPIRES_IN: "expires_in";
|
||||||
|
ID_TOKEN: "id_token";
|
||||||
|
ERROR_DESCRIPTION: "error_description";
|
||||||
|
SESSION_STATE: "session_state";
|
||||||
|
STORAGE: {
|
||||||
|
TOKEN_KEYS: "adal.token.keys";
|
||||||
|
ACCESS_TOKEN_KEY: "adal.access.token.key";
|
||||||
|
EXPIRATION_KEY: "adal.expiration.key";
|
||||||
|
STATE_LOGIN: "adal.state.login";
|
||||||
|
STATE_RENEW: "adal.state.renew";
|
||||||
|
NONCE_IDTOKEN: "adal.nonce.idtoken";
|
||||||
|
SESSION_STATE: "adal.session.state";
|
||||||
|
USERNAME: "adal.username";
|
||||||
|
IDTOKEN: "adal.idtoken";
|
||||||
|
ERROR: "adal.error";
|
||||||
|
ERROR_DESCRIPTION: "adal.error.description";
|
||||||
|
LOGIN_REQUEST: "adal.login.request";
|
||||||
|
LOGIN_ERROR: "adal.login.error";
|
||||||
|
RENEW_STATUS: "adal.token.renew.status";
|
||||||
|
};
|
||||||
|
RESOURCE_DELIMETER: "|";
|
||||||
|
LOADFRAME_TIMEOUT: "6000";
|
||||||
|
TOKEN_RENEW_STATUS_CANCELED: "Canceled";
|
||||||
|
TOKEN_RENEW_STATUS_COMPLETED: "Completed";
|
||||||
|
TOKEN_RENEW_STATUS_IN_PROGRESS: "In Progress";
|
||||||
|
LOGGING_LEVEL: {
|
||||||
|
ERROR: 0;
|
||||||
|
WARN: 1;
|
||||||
|
INFO: 2;
|
||||||
|
VERBOSE: 3;
|
||||||
|
};
|
||||||
|
LEVEL_STRING_MAP: {
|
||||||
|
0: "ERROR:";
|
||||||
|
1: "WARNING:";
|
||||||
|
2: "INFO:";
|
||||||
|
3: "VERBOSE:";
|
||||||
|
};
|
||||||
|
POPUP_WIDTH: 483;
|
||||||
|
POPUP_HEIGHT: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare global {
|
||||||
|
// interface Window {
|
||||||
|
// Logging: AuthenticationContext.LoggingConfig;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { shallow, mount } from "enzyme";
|
||||||
|
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
||||||
|
import { AuthType } from "../../../AuthType";
|
||||||
|
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
||||||
|
import { AccountKind } from "../../../Common/Constants";
|
||||||
|
|
||||||
|
const createBlankProps = (): AccountSwitchComponentProps => {
|
||||||
|
return {
|
||||||
|
authType: null,
|
||||||
|
displayText: "",
|
||||||
|
accounts: [],
|
||||||
|
selectedAccountName: null,
|
||||||
|
isLoadingAccounts: false,
|
||||||
|
onAccountChange: jest.fn(),
|
||||||
|
subscriptions: [],
|
||||||
|
selectedSubscriptionId: null,
|
||||||
|
isLoadingSubscriptions: false,
|
||||||
|
onSubscriptionChange: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBlankAccount = (): DatabaseAccount => {
|
||||||
|
return {
|
||||||
|
id: "",
|
||||||
|
kind: AccountKind.Default,
|
||||||
|
name: "",
|
||||||
|
properties: null,
|
||||||
|
location: "",
|
||||||
|
tags: null,
|
||||||
|
type: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBlankSubscription = (): Subscription => {
|
||||||
|
return {
|
||||||
|
subscriptionId: "",
|
||||||
|
displayName: "",
|
||||||
|
authorizationSource: "",
|
||||||
|
state: "",
|
||||||
|
subscriptionPolicies: null,
|
||||||
|
tenantId: "",
|
||||||
|
uniqueDisplayName: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFullProps = (): AccountSwitchComponentProps => {
|
||||||
|
const props = createBlankProps();
|
||||||
|
props.authType = AuthType.AAD;
|
||||||
|
const account1 = createBlankAccount();
|
||||||
|
account1.name = "account1";
|
||||||
|
const account2 = createBlankAccount();
|
||||||
|
account2.name = "account2";
|
||||||
|
const account3 = createBlankAccount();
|
||||||
|
account3.name = "superlongaccountnamestringtest";
|
||||||
|
props.accounts = [account1, account2, account3];
|
||||||
|
props.selectedAccountName = "account2";
|
||||||
|
|
||||||
|
const sub1 = createBlankSubscription();
|
||||||
|
sub1.displayName = "sub1";
|
||||||
|
sub1.subscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
||||||
|
const sub2 = createBlankSubscription();
|
||||||
|
sub2.displayName = "subsubsubsubsubsubsub2";
|
||||||
|
sub2.subscriptionId = "b20b3e93-0185-4326-8a9c-d44bac276b6b";
|
||||||
|
props.subscriptions = [sub1, sub2];
|
||||||
|
props.selectedSubscriptionId = "a6062a74-5d53-4b20-9545-000b95f22297";
|
||||||
|
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("test render", () => {
|
||||||
|
it("renders no auth type -> handle error in code", () => {
|
||||||
|
const props = createBlankProps();
|
||||||
|
|
||||||
|
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Encrypted Token
|
||||||
|
it("renders auth security token, with selected account name", () => {
|
||||||
|
const props = createBlankProps();
|
||||||
|
props.authType = AuthType.EncryptedToken;
|
||||||
|
props.selectedAccountName = "testaccount";
|
||||||
|
|
||||||
|
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// AAD
|
||||||
|
it("renders auth aad, with all information", () => {
|
||||||
|
const props = createFullProps();
|
||||||
|
const wrapper = shallow(<AccountSwitchComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders auth aad all dropdown menus", () => {
|
||||||
|
const props = createFullProps();
|
||||||
|
const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||||
|
|
||||||
|
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
||||||
|
wrapper.find("button.accountSwitchButton").simulate("click");
|
||||||
|
expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(true);
|
||||||
|
|
||||||
|
expect(wrapper.exists("div.accountSwitchSubscriptionDropdown")).toBe(true);
|
||||||
|
wrapper.find("DropdownBase.accountSwitchSubscriptionDropdown").simulate("click");
|
||||||
|
// Click will dismiss the first contextual menu in enzyme. Need to dig deeper to achieve below test
|
||||||
|
|
||||||
|
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(true);
|
||||||
|
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(2);
|
||||||
|
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
||||||
|
// expect(wrapper.exists("div.accountSwitchSubscriptionDropdownMenu")).toBe(false);
|
||||||
|
|
||||||
|
// expect(wrapper.exists("div.accountSwitchAccountDropdown")).toBe(true);
|
||||||
|
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||||
|
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(true);
|
||||||
|
// expect(wrapper.find("button.ms-Dropdown-item").length).toBe(3);
|
||||||
|
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||||
|
// expect(wrapper.exists("div.accountSwitchAccountDropdownMenu")).toBe(false);
|
||||||
|
|
||||||
|
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||||
|
// expect(wrapper.exists("div.accountSwitchContextualMenu")).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe("test function", () => {
|
||||||
|
// it("switch subscription function", () => {
|
||||||
|
// const props = createFullProps();
|
||||||
|
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||||
|
|
||||||
|
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||||
|
// wrapper.find("div.accountSwitchSubscriptionDropdown").simulate("click");
|
||||||
|
// wrapper
|
||||||
|
// .find("button.ms-Dropdown-item")
|
||||||
|
// .at(1)
|
||||||
|
// .simulate("click");
|
||||||
|
// expect(props.onSubscriptionChange).toBeCalled();
|
||||||
|
// expect(props.onSubscriptionChange).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// wrapper.unmount();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it("switch account", () => {
|
||||||
|
// const props = createFullProps();
|
||||||
|
// const wrapper = mount(<AccountSwitchComponent {...props} />);
|
||||||
|
|
||||||
|
// wrapper.find("button.accountSwitchButton").simulate("click");
|
||||||
|
// wrapper.find("div.accountSwitchAccountDropdown").simulate("click");
|
||||||
|
// wrapper
|
||||||
|
// .find("button.ms-Dropdown-item")
|
||||||
|
// .at(0)
|
||||||
|
// .simulate("click");
|
||||||
|
// expect(props.onAccountChange).toBeCalled();
|
||||||
|
// expect(props.onAccountChange).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// wrapper.unmount();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
177
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
Normal file
177
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import { AuthType } from "../../../AuthType";
|
||||||
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
|
import { DatabaseAccount, Subscription } from "../../../Contracts/DataModels";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { DefaultButton, IButtonStyles, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||||
|
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
|
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
||||||
|
|
||||||
|
export interface AccountSwitchComponentProps {
|
||||||
|
authType: AuthType;
|
||||||
|
selectedAccountName: string;
|
||||||
|
accounts: DatabaseAccount[];
|
||||||
|
isLoadingAccounts: boolean;
|
||||||
|
onAccountChange: (newAccount: DatabaseAccount) => void;
|
||||||
|
selectedSubscriptionId: string;
|
||||||
|
subscriptions: Subscription[];
|
||||||
|
isLoadingSubscriptions: boolean;
|
||||||
|
onSubscriptionChange: (newSubscription: Subscription) => void;
|
||||||
|
displayText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AccountSwitchComponent extends React.Component<AccountSwitchComponentProps> {
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return this.props.authType === AuthType.AAD ? this._renderSwitchDropDown() : this._renderAccountName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSwitchDropDown(): JSX.Element {
|
||||||
|
const { displayText, selectedAccountName } = this.props;
|
||||||
|
|
||||||
|
const menuProps: IContextualMenuProps = {
|
||||||
|
directionalHintFixed: true,
|
||||||
|
className: "accountSwitchContextualMenu",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: "switchSubscription",
|
||||||
|
onRender: this._renderSubscriptionDropdown.bind(this),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "switchAccount",
|
||||||
|
onRender: this._renderAccountDropDown.bind(this),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonStyles: IButtonStyles = {
|
||||||
|
root: {
|
||||||
|
fontSize: StyleConstants.DefaultFontSize,
|
||||||
|
height: 40,
|
||||||
|
padding: 0,
|
||||||
|
paddingLeft: 10,
|
||||||
|
marginRight: 5,
|
||||||
|
backgroundColor: StyleConstants.BaseDark,
|
||||||
|
color: StyleConstants.BaseLight,
|
||||||
|
},
|
||||||
|
rootHovered: {
|
||||||
|
backgroundColor: StyleConstants.BaseHigh,
|
||||||
|
color: StyleConstants.BaseLight,
|
||||||
|
},
|
||||||
|
rootFocused: {
|
||||||
|
backgroundColor: StyleConstants.BaseHigh,
|
||||||
|
color: StyleConstants.BaseLight,
|
||||||
|
},
|
||||||
|
rootPressed: {
|
||||||
|
backgroundColor: StyleConstants.BaseHigh,
|
||||||
|
color: StyleConstants.BaseLight,
|
||||||
|
},
|
||||||
|
rootExpanded: {
|
||||||
|
backgroundColor: StyleConstants.BaseHigh,
|
||||||
|
color: StyleConstants.BaseLight,
|
||||||
|
},
|
||||||
|
textContainer: {
|
||||||
|
flexGrow: "initial",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonProps: IButtonProps = {
|
||||||
|
text: displayText || selectedAccountName,
|
||||||
|
menuProps: menuProps,
|
||||||
|
styles: buttonStyles,
|
||||||
|
className: "accountSwitchButton",
|
||||||
|
id: "accountSwitchButton",
|
||||||
|
};
|
||||||
|
|
||||||
|
return <DefaultButton {...buttonProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSubscriptionDropdown(): JSX.Element {
|
||||||
|
const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props;
|
||||||
|
const options: IDropdownOption[] = subscriptions.map((sub) => {
|
||||||
|
return {
|
||||||
|
key: sub.subscriptionId,
|
||||||
|
text: sub.displayName,
|
||||||
|
data: sub,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const placeHolderText = isLoadingSubscriptions
|
||||||
|
? "Loading subscriptions"
|
||||||
|
: !options || !options.length
|
||||||
|
? "No subscriptions found in current directory"
|
||||||
|
: "Select subscription from list";
|
||||||
|
|
||||||
|
const dropdownProps: IDropdownProps = {
|
||||||
|
label: "Subscription",
|
||||||
|
className: "accountSwitchSubscriptionDropdown",
|
||||||
|
options: options,
|
||||||
|
onChange: this._onSubscriptionDropdownChange,
|
||||||
|
defaultSelectedKey: selectedSubscriptionId,
|
||||||
|
placeholder: placeHolderText,
|
||||||
|
styles: {
|
||||||
|
callout: "accountSwitchSubscriptionDropdownMenu",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Dropdown {...dropdownProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onSubscriptionDropdownChange = (e: React.FormEvent<HTMLDivElement>, option: IDropdownOption): void => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onSubscriptionChange(option.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _renderAccountDropDown(): JSX.Element {
|
||||||
|
const { accounts, selectedAccountName, isLoadingAccounts } = this.props;
|
||||||
|
const options: IDropdownOption[] = accounts.map((account) => {
|
||||||
|
return {
|
||||||
|
key: account.name,
|
||||||
|
text: account.name,
|
||||||
|
data: account,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// Fabric UI will also try to select the first non-disabled option from dropdown.
|
||||||
|
// Add a option to prevent pop the message when user click on dropdown on first time.
|
||||||
|
options.unshift({
|
||||||
|
key: "select from list",
|
||||||
|
text: "Select Cosmos DB account from list",
|
||||||
|
data: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const placeHolderText = isLoadingAccounts
|
||||||
|
? "Loading Cosmos DB accounts"
|
||||||
|
: !options || !options.length
|
||||||
|
? "No Cosmos DB accounts found"
|
||||||
|
: "Select Cosmos DB account from list";
|
||||||
|
|
||||||
|
const dropdownProps: IDropdownProps = {
|
||||||
|
label: "Cosmos DB Account Name",
|
||||||
|
className: "accountSwitchAccountDropdown",
|
||||||
|
options: options,
|
||||||
|
onChange: this._onAccountDropdownChange,
|
||||||
|
defaultSelectedKey: selectedAccountName,
|
||||||
|
placeholder: placeHolderText,
|
||||||
|
styles: {
|
||||||
|
callout: "accountSwitchAccountDropdownMenu",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Dropdown {...dropdownProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onAccountDropdownChange = (e: React.FormEvent<HTMLDivElement>, option: IDropdownOption): void => {
|
||||||
|
if (!option) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onAccountChange(option.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _renderAccountName(): JSX.Element {
|
||||||
|
const { displayText, selectedAccountName } = this.props;
|
||||||
|
return <span className="accountNameHeader">{displayText || selectedAccountName}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import { AccountSwitchComponent, AccountSwitchComponentProps } from "./AccountSwitchComponent";
|
||||||
|
|
||||||
|
export class AccountSwitchComponentAdapter implements ReactAdapter {
|
||||||
|
public parameters: ko.Observable<AccountSwitchComponentProps>;
|
||||||
|
|
||||||
|
public renderComponent(): JSX.Element {
|
||||||
|
return <AccountSwitchComponent {...this.parameters()} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`test render renders auth aad, with all information 1`] = `
|
||||||
|
<CustomizedDefaultButton
|
||||||
|
className="accountSwitchButton"
|
||||||
|
id="accountSwitchButton"
|
||||||
|
menuProps={
|
||||||
|
Object {
|
||||||
|
"className": "accountSwitchContextualMenu",
|
||||||
|
"directionalHintFixed": true,
|
||||||
|
"items": Array [
|
||||||
|
Object {
|
||||||
|
"key": "switchSubscription",
|
||||||
|
"onRender": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "switchAccount",
|
||||||
|
"onRender": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"backgroundColor": undefined,
|
||||||
|
"color": undefined,
|
||||||
|
"fontSize": undefined,
|
||||||
|
"height": 40,
|
||||||
|
"marginRight": 5,
|
||||||
|
"padding": 0,
|
||||||
|
"paddingLeft": 10,
|
||||||
|
},
|
||||||
|
"rootExpanded": Object {
|
||||||
|
"backgroundColor": undefined,
|
||||||
|
"color": undefined,
|
||||||
|
},
|
||||||
|
"rootFocused": Object {
|
||||||
|
"backgroundColor": undefined,
|
||||||
|
"color": undefined,
|
||||||
|
},
|
||||||
|
"rootHovered": Object {
|
||||||
|
"backgroundColor": undefined,
|
||||||
|
"color": undefined,
|
||||||
|
},
|
||||||
|
"rootPressed": Object {
|
||||||
|
"backgroundColor": undefined,
|
||||||
|
"color": undefined,
|
||||||
|
},
|
||||||
|
"textContainer": Object {
|
||||||
|
"flexGrow": "initial",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text="account2"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`test render renders auth security token, with selected account name 1`] = `
|
||||||
|
<span
|
||||||
|
className="accountNameHeader"
|
||||||
|
>
|
||||||
|
testaccount
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`test render renders no auth type -> handle error in code 1`] = `
|
||||||
|
<span
|
||||||
|
className="accountNameHeader"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
@@ -42,7 +42,7 @@ interface CollapsiblePanelParams {
|
|||||||
* Use the optional "collapseToLeft" parameter to collapse to the left.
|
* Use the optional "collapseToLeft" parameter to collapse to the left.
|
||||||
*/
|
*/
|
||||||
class CollapsiblePanelViewModel {
|
class CollapsiblePanelViewModel {
|
||||||
public params: CollapsiblePanelParams;
|
private params: CollapsiblePanelParams;
|
||||||
private isCollapsed: ko.Observable<boolean>;
|
private isCollapsed: ko.Observable<boolean>;
|
||||||
|
|
||||||
public constructor(params: CollapsiblePanelParams) {
|
public constructor(params: CollapsiblePanelParams) {
|
||||||
@@ -50,7 +50,7 @@ class CollapsiblePanelViewModel {
|
|||||||
this.isCollapsed = params.isCollapsed || ko.observable(false);
|
this.isCollapsed = params.isCollapsed || ko.observable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleCollapse(): void {
|
private toggleCollapse(): void {
|
||||||
this.isCollapsed(!this.isCollapsed());
|
this.isCollapsed(!this.isCollapsed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export interface CommandButtonComponentProps {
|
|||||||
/**
|
/**
|
||||||
* Label for the button
|
* Label for the button
|
||||||
*/
|
*/
|
||||||
commandButtonLabel?: string;
|
commandButtonLabel: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this button opens a tab or pane, false otherwise.
|
* True if this button opens a tab or pane, false otherwise.
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
|||||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||||
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
|
||||||
{ key: "feature.selfServeType", label: "Self serve feature", value: "sample" },
|
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
|
||||||
{
|
{
|
||||||
key: "feature.enableLinkInjection",
|
key: "feature.enableLinkInjection",
|
||||||
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
label: "Enable Injecting Notebook Viewer Link into the first cell",
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ exports[`Feature panel renders all flags 1`] = `
|
|||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
key="feature.selfServeType"
|
key="feature.enablecodeofconduct"
|
||||||
label="Self serve feature"
|
label="Enable Code Of Conduct Acknowledgement"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
/>
|
/>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ interface InputTypeaheadParams {
|
|||||||
/**
|
/**
|
||||||
* This function gets called when pressing ENTER on the input box
|
* This function gets called when pressing ENTER on the input box
|
||||||
*/
|
*/
|
||||||
submitFct?: (inputValue: string | null, selection: Item | null) => void;
|
submitFct?: (inputValue: string, selection: Item) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typehead comes with a Search button that we normally remove.
|
* Typehead comes with a Search button that we normally remove.
|
||||||
@@ -88,8 +88,8 @@ interface OnClickItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Cache {
|
interface Cache {
|
||||||
inputValue: string | null;
|
inputValue: string;
|
||||||
selection: Item | null;
|
selection: Item;
|
||||||
}
|
}
|
||||||
|
|
||||||
class InputTypeaheadViewModel {
|
class InputTypeaheadViewModel {
|
||||||
@@ -98,12 +98,15 @@ class InputTypeaheadViewModel {
|
|||||||
private params: InputTypeaheadParams;
|
private params: InputTypeaheadParams;
|
||||||
|
|
||||||
private cache: Cache;
|
private cache: Cache;
|
||||||
|
private inputValue: string;
|
||||||
|
private selection: Item;
|
||||||
|
|
||||||
public constructor(params: InputTypeaheadParams) {
|
public constructor(params: InputTypeaheadParams) {
|
||||||
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
|
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
|
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
|
||||||
|
|
||||||
this.cache = {
|
this.cache = {
|
||||||
inputValue: null,
|
inputValue: null,
|
||||||
selection: null,
|
selection: null,
|
||||||
@@ -158,7 +161,7 @@ class InputTypeaheadViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
($ as any).typeahead(options);
|
$.typeahead(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -174,11 +177,11 @@ class InputTypeaheadViewModel {
|
|||||||
* Use ko's "template: afterRender" callback to do that without actually using any template.
|
* Use ko's "template: afterRender" callback to do that without actually using any template.
|
||||||
* Another way is to call it within setTimeout() in constructor.
|
* Another way is to call it within setTimeout() in constructor.
|
||||||
*/
|
*/
|
||||||
public afterRender(): void {
|
private afterRender(): void {
|
||||||
this.initializeTypeahead();
|
this.initializeTypeahead();
|
||||||
}
|
}
|
||||||
|
|
||||||
public submit(): void {
|
private submit(): void {
|
||||||
if (this.params.submitFct) {
|
if (this.params.submitFct) {
|
||||||
this.params.submitFct(this.cache.inputValue, this.cache.selection);
|
this.params.submitFct(this.cache.inputValue, this.cache.selection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,10 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
|
|||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
this.params.content.subscribe((newValue: string) => {
|
this.params.content.subscribe((newValue: string) => {
|
||||||
if (newValue) {
|
if (!!this.editor) {
|
||||||
if (!!this.editor) {
|
this.editor.getModel().setValue(newValue);
|
||||||
this.editor.getModel().setValue(newValue);
|
} else {
|
||||||
} else {
|
this.createEditor(newValue, this.configureEditor.bind(this));
|
||||||
this.createEditor(newValue, this.configureEditor.bind(this));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
FontIcon,
|
|
||||||
FontWeights,
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
@@ -17,7 +16,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
@@ -137,7 +136,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}
|
}
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const tabs: GalleryTabInfo[] = [this.createSamplesTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
|
||||||
|
|
||||||
if (this.props.container?.isGalleryPublishEnabled()) {
|
if (this.props.container?.isGalleryPublishEnabled()) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
@@ -147,7 +146,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
this.state.isCodeOfConductAccepted
|
this.state.isCodeOfConductAccepted
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
tabs.push(this.createFavoritesTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
|
||||||
|
|
||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
@@ -184,27 +183,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEmptyData = (data: IGalleryItem[]): boolean => {
|
|
||||||
return !data || data.length === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
private createEmptyTabContent = (iconName: string, line1: string, line2: string): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<Stack horizontalAlign="center" tokens={{ childrenGap: 10 }}>
|
|
||||||
<FontIcon iconName={iconName} style={{ fontSize: 100, color: "lightgray", marginTop: 20 }} />
|
|
||||||
<Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{line1}</Text>
|
|
||||||
<Text>{line2}</Text>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
|
||||||
return {
|
|
||||||
tab,
|
|
||||||
content: this.createSearchBarHeader(this.createCardsTabContent(data)),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private createPublicGalleryTab(
|
private createPublicGalleryTab(
|
||||||
tab: GalleryTab,
|
tab: GalleryTab,
|
||||||
data: IGalleryItem[],
|
data: IGalleryItem[],
|
||||||
@@ -216,29 +194,17 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFavoritesTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.isEmptyData(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data)),
|
||||||
? this.createEmptyTabContent(
|
|
||||||
"ContactHeart",
|
|
||||||
"You have not liked anything",
|
|
||||||
"Like any notebook from Official Samples or Public gallery"
|
|
||||||
)
|
|
||||||
: this.createSearchBarHeader(this.createCardsTabContent(data)),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.isEmptyData(data)
|
content: this.createPublishedNotebooksTabContent(data),
|
||||||
? this.createEmptyTabContent(
|
|
||||||
"Contact",
|
|
||||||
"You have not published anything",
|
|
||||||
"Publish your sample notebooks to share your published work with others"
|
|
||||||
)
|
|
||||||
: this.createPublishedNotebooksTabContent(data),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -398,9 +364,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
private async loadPublicNotebooks(searchText: string, sortBy: SortBy, offline: boolean): Promise<void> {
|
||||||
if (!offline) {
|
if (!offline) {
|
||||||
try {
|
try {
|
||||||
let response: IJunoResponse<IGalleryItem[]> | IJunoResponse<IPublicGalleryData>;
|
let response: IJunoResponse<IPublicGalleryData> | IJunoResponse<IGalleryItem[]>;
|
||||||
if (this.props.container) {
|
if (this.props.container.isCodeOfConductEnabled()) {
|
||||||
response = await this.props.junoClient.getPublicGalleryData();
|
response = await this.props.junoClient.fetchPublicNotebooks();
|
||||||
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
this.isCodeOfConductAccepted = response.data?.metadata.acceptedCodeOfConduct;
|
||||||
this.publicNotebooks = response.data?.notebooksData;
|
this.publicNotebooks = response.data?.notebooksData;
|
||||||
} else {
|
} else {
|
||||||
@@ -604,7 +570,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
private deleteItem = async (data: IGalleryItem): Promise<void> => {
|
||||||
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => {
|
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => {
|
||||||
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
|
this.publishedNotebooks = this.publishedNotebooks.filter((notebook) => item.id !== notebook.id);
|
||||||
this.refreshSelectedTab(item);
|
this.refreshSelectedTab(item);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ describe("SettingsComponent", () => {
|
|||||||
manualThroughput: undefined,
|
manualThroughput: undefined,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "test",
|
id: "test",
|
||||||
offerReplacePending: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = { ...baseProps };
|
const props = { ...baseProps };
|
||||||
@@ -231,7 +230,7 @@ describe("SettingsComponent", () => {
|
|||||||
|
|
||||||
it("getUpdatedConflictResolutionPolicy", () => {
|
it("getUpdatedConflictResolutionPolicy", () => {
|
||||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
const conflictResolutionPolicyPath = "/_ts";
|
const conflictResolutionPolicyPath = "_ts";
|
||||||
const conflictResolutionPolicyProcedure = "sample_sproc";
|
const conflictResolutionPolicyProcedure = "sample_sproc";
|
||||||
const expectSprocPath =
|
const expectSprocPath =
|
||||||
"/dbs/" + collection.databaseId + "/colls/" + collection.id() + "/sprocs/" + conflictResolutionPolicyProcedure;
|
"/dbs/" + collection.databaseId + "/colls/" + collection.id() + "/sprocs/" + conflictResolutionPolicyProcedure;
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
this.container.isPreferredApiMongoDB() &&
|
!this.collection.partitionKey ||
|
||||||
(!this.collection.partitionKey || this.collection.partitionKey.systemKey);
|
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
throughput: undefined,
|
throughput: undefined,
|
||||||
@@ -295,7 +295,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
!!this.collection.conflictResolutionPolicy();
|
!!this.collection.conflictResolutionPolicy();
|
||||||
|
|
||||||
public isOfferReplacePending = (): boolean => {
|
public isOfferReplacePending = (): boolean => {
|
||||||
return this.collection?.offer()?.offerReplacePending;
|
return !!this.collection?.offer()?.headers?.[Constants.HttpHeaders.offerReplacePending];
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<void> => {
|
public onSaveClick = async (): Promise<void> => {
|
||||||
@@ -684,7 +684,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
if (policy.mode === DataModels.ConflictResolutionMode.LastWriterWins) {
|
if (policy.mode === DataModels.ConflictResolutionMode.LastWriterWins) {
|
||||||
policy.conflictResolutionPath = this.state.conflictResolutionPolicyPath;
|
policy.conflictResolutionPath = this.state.conflictResolutionPolicyPath;
|
||||||
if (!policy.conflictResolutionPath?.startsWith("/")) {
|
if (policy.conflictResolutionPath?.startsWith("/")) {
|
||||||
policy.conflictResolutionPath = "/" + policy.conflictResolutionPath;
|
policy.conflictResolutionPath = "/" + policy.conflictResolutionPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IColumn, Text } from "office-ui-fabric-react";
|
|
||||||
import {
|
import {
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
getEstimatedSpendingElement,
|
getEstimatedSpendElement,
|
||||||
|
getEstimatedAutoscaleSpendElement,
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
@@ -20,36 +20,10 @@ import {
|
|||||||
mongoIndexingPolicyAADError,
|
mongoIndexingPolicyAADError,
|
||||||
mongoIndexTransformationRefreshingMessage,
|
mongoIndexTransformationRefreshingMessage,
|
||||||
renderMongoIndexTransformationRefreshMessage,
|
renderMongoIndexTransformationRefreshMessage,
|
||||||
ManualEstimatedSpendingDisplayProps,
|
|
||||||
PriceBreakdown,
|
|
||||||
getRuPriceBreakdown,
|
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const estimatedSpendingColumns: IColumn[] = [
|
|
||||||
{ key: "costType", name: "", fieldName: "costType", minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
{ key: "hourly", name: "Hourly", fieldName: "hourly", minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
{ key: "daily", name: "Daily", fieldName: "daily", minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true },
|
|
||||||
];
|
|
||||||
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
|
||||||
{
|
|
||||||
costType: <Text>Current Cost</Text>,
|
|
||||||
hourly: <Text>$ 1.02</Text>,
|
|
||||||
daily: <Text>$ 24.48</Text>,
|
|
||||||
monthly: <Text>$ 744.6</Text>,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const priceBreakdown: PriceBreakdown = {
|
|
||||||
hourlyPrice: 1.02,
|
|
||||||
dailyPrice: 24.48,
|
|
||||||
monthlyPrice: 744.6,
|
|
||||||
pricePerRu: 0.00051,
|
|
||||||
currency: "RMB",
|
|
||||||
currencySign: "¥",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{getAutoPilotV3SpendElement(1000, false)}
|
{getAutoPilotV3SpendElement(1000, false)}
|
||||||
@@ -57,7 +31,9 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{getAutoPilotV3SpendElement(1000, true)}
|
{getAutoPilotV3SpendElement(1000, true)}
|
||||||
{getAutoPilotV3SpendElement(undefined, true)}
|
{getAutoPilotV3SpendElement(undefined, true)}
|
||||||
|
|
||||||
{getEstimatedSpendingElement(estimatedSpendingColumns, estimatedSpendingItems, 1000, 2, priceBreakdown, false)}
|
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
||||||
|
|
||||||
|
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
@@ -93,14 +69,4 @@ describe("SettingsUtils functions", () => {
|
|||||||
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
const wrapper = shallow(<SettingsRenderUtilsTestComponent />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return correct price breakdown for a manual RU setting of 500, 1 region, multimaster disabled", () => {
|
|
||||||
const prices = getRuPriceBreakdown(500, "", 1, false, false);
|
|
||||||
expect(prices.hourlyPrice).toBe(0.04);
|
|
||||||
expect(prices.dailyPrice).toBe(0.96);
|
|
||||||
expect(prices.monthlyPrice).toBe(29.2);
|
|
||||||
expect(prices.pricePerRu).toBe(0.00008);
|
|
||||||
expect(prices.currency).toBe("USD");
|
|
||||||
expect(prices.currencySign).toBe("$");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
|||||||
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
import { AutopilotDocumentation, hoursInAMonth } from "../../../Shared/Constants";
|
||||||
import { Urls, StyleConstants } from "../../../Common/Constants";
|
import { Urls, StyleConstants } from "../../../Common/Constants";
|
||||||
import {
|
import {
|
||||||
|
computeAutoscaleUsagePriceHourly,
|
||||||
getPriceCurrency,
|
getPriceCurrency,
|
||||||
getCurrencySign,
|
getCurrencySign,
|
||||||
getAutoscalePricePerRu,
|
getAutoscalePricePerRu,
|
||||||
getMultimasterMultiplier,
|
getMultimasterMultiplier,
|
||||||
computeRUUsagePriceHourly,
|
computeRUUsagePriceHourly,
|
||||||
getPricePerRu,
|
getPricePerRu,
|
||||||
estimatedCostDisclaimer,
|
calculateEstimateNumber,
|
||||||
} from "../../../Utils/PricingUtils";
|
} from "../../../Utils/PricingUtils";
|
||||||
import {
|
import {
|
||||||
ITextFieldStyles,
|
ITextFieldStyles,
|
||||||
@@ -32,41 +33,10 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
DetailsList,
|
|
||||||
IColumn,
|
|
||||||
SelectionMode,
|
|
||||||
DetailsListLayoutMode,
|
|
||||||
IDetailsRowProps,
|
|
||||||
DetailsRow,
|
|
||||||
IDetailsColumnStyles,
|
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
export interface EstimatedSpendingDisplayProps {
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||||
costType: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ManualEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
|
||||||
hourly: JSX.Element;
|
|
||||||
daily: JSX.Element;
|
|
||||||
monthly: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AutoscaleEstimatedSpendingDisplayProps extends EstimatedSpendingDisplayProps {
|
|
||||||
minPerMonth: JSX.Element;
|
|
||||||
maxPerMonth: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PriceBreakdown {
|
|
||||||
hourlyPrice: number;
|
|
||||||
dailyPrice: number;
|
|
||||||
monthlyPrice: number;
|
|
||||||
pricePerRu: number;
|
|
||||||
currency: string;
|
|
||||||
currencySign: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
|
|
||||||
|
|
||||||
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
@@ -134,16 +104,6 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
|
|
||||||
root: {
|
|
||||||
selectors: {
|
|
||||||
":hover": {
|
|
||||||
background: "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
root: {
|
root: {
|
||||||
selectors: {
|
selectors: {
|
||||||
@@ -166,17 +126,10 @@ export const separatorStyles: Partial<ISeparatorStyles> = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = {
|
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||||
root: { marginTop: "5px", backgroundColor: "white" },
|
|
||||||
text: { fontSize: 14 },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const throughputUnit = "RU/s";
|
export const throughputUnit = "RU/s";
|
||||||
|
|
||||||
export function onRenderRow(props: IDetailsRowProps): JSX.Element {
|
|
||||||
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAutoPilotV3SpendElement = (
|
export const getAutoPilotV3SpendElement = (
|
||||||
maxAutoPilotThroughputSet: number,
|
maxAutoPilotThroughputSet: number,
|
||||||
isDatabaseThroughput: boolean,
|
isDatabaseThroughput: boolean,
|
||||||
@@ -212,61 +165,63 @@ export const getAutoPilotV3SpendElement = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRuPriceBreakdown = (
|
export const getEstimatedAutoscaleSpendElement = (
|
||||||
throughput: number,
|
throughput: number,
|
||||||
serverId: string,
|
serverId: string,
|
||||||
numberOfRegions: number,
|
regions: number,
|
||||||
isMultimaster: boolean,
|
multimaster: boolean
|
||||||
isAutoscale: boolean
|
): JSX.Element => {
|
||||||
): PriceBreakdown => {
|
const hourlyPrice: number = computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||||
const hourlyPrice: number = computeRUUsagePriceHourly({
|
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||||
serverId: serverId,
|
const currency: string = getPriceCurrency(serverId);
|
||||||
requestUnits: throughput,
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
numberOfRegions: numberOfRegions,
|
const pricePerRu =
|
||||||
multimasterEnabled: isMultimaster,
|
getAutoscalePricePerRu(serverId, getMultimasterMultiplier(regions, multimaster)) *
|
||||||
isAutoscale: isAutoscale,
|
getMultimasterMultiplier(regions, multimaster);
|
||||||
});
|
|
||||||
const basePricePerRu: number = isAutoscale
|
return (
|
||||||
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
|
<Text id="autoscaleSpendElement">
|
||||||
: getPricePerRu(serverId);
|
Estimated monthly cost ({currency}) is{" "}
|
||||||
return {
|
<b>
|
||||||
hourlyPrice: hourlyPrice,
|
{currencySign}
|
||||||
dailyPrice: hourlyPrice * 24,
|
{calculateEstimateNumber(monthlyPrice / 10)}
|
||||||
monthlyPrice: hourlyPrice * hoursInAMonth,
|
{` - `}
|
||||||
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
|
{currencySign}
|
||||||
currency: getPriceCurrency(serverId),
|
{calculateEstimateNumber(monthlyPrice)}{" "}
|
||||||
currencySign: getCurrencySign(serverId),
|
</b>
|
||||||
};
|
({"regions: "} {regions}, {throughput / 10} - {throughput} RU/s, {currencySign}
|
||||||
|
{pricePerRu}/RU)
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEstimatedSpendingElement = (
|
export const getEstimatedSpendElement = (
|
||||||
estimatedSpendingColumns: IColumn[],
|
|
||||||
estimatedSpendingItems: EstimatedSpendingDisplayProps[],
|
|
||||||
throughput: number,
|
throughput: number,
|
||||||
numberOfRegions: number,
|
serverId: string,
|
||||||
priceBreakdown: PriceBreakdown,
|
regions: number,
|
||||||
isAutoscale: boolean
|
multimaster: boolean
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const ruRange: string = isAutoscale ? throughput / 10 + " RU/s - " : "";
|
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||||
|
const dailyPrice: number = hourlyPrice * 24;
|
||||||
|
const monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||||
|
const currency: string = getPriceCurrency(serverId);
|
||||||
|
const currencySign: string = getCurrencySign(serverId);
|
||||||
|
const pricePerRu = getPricePerRu(serverId) * getMultimasterMultiplier(regions, multimaster);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack {...addMongoIndexStackProps} styles={mediumWidthStackStyles}>
|
<Text id="throughputSpendElement">
|
||||||
<DetailsList
|
Estimated cost ({currency}):{" "}
|
||||||
disableSelectionZone
|
<b>
|
||||||
items={estimatedSpendingItems}
|
{currencySign}
|
||||||
columns={estimatedSpendingColumns}
|
{calculateEstimateNumber(hourlyPrice)} hourly {` / `}
|
||||||
selectionMode={SelectionMode.none}
|
{currencySign}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
{calculateEstimateNumber(dailyPrice)} daily {` / `}
|
||||||
onRenderRow={onRenderRow}
|
{currencySign}
|
||||||
/>
|
{calculateEstimateNumber(monthlyPrice)} monthly{" "}
|
||||||
<Text id="throughputSpendElement">
|
</b>
|
||||||
({"regions: "} {numberOfRegions}, {ruRange}
|
({"regions: "} {regions}, {throughput}RU/s, {currencySign}
|
||||||
{throughput} RU/s, {priceBreakdown.currencySign}
|
{pricePerRu}/RU)
|
||||||
{priceBreakdown.pricePerRu}/RU)
|
</Text>
|
||||||
</Text>
|
|
||||||
<Text>
|
|
||||||
<em>{estimatedCostDisclaimer}</em>
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -310,13 +265,6 @@ export const updateThroughputDelayedApplyWarningMessage: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const saveThroughputWarningMessage: JSX.Element = (
|
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
|
||||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below
|
|
||||||
before saving your changes
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
|
|
||||||
const getCurrentThroughput = (
|
const getCurrentThroughput = (
|
||||||
isAutoscale: boolean,
|
isAutoscale: boolean,
|
||||||
throughput: number,
|
throughput: number,
|
||||||
@@ -441,13 +389,6 @@ export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const mongoCompoundIndexNotSupportedMessage: JSX.Element = (
|
|
||||||
<Text>
|
|
||||||
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this
|
|
||||||
collection, use the Mongo Shell.
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const mongoIndexingPolicyAADError: JSX.Element = (
|
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||||
<MessageBar messageBarType={MessageBarType.error}>
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
<Text>
|
<Text>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ describe("MongoIndexingPolicyComponent", () => {
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("error shown for collection with compound indexes", () => {
|
|
||||||
const props = { ...baseProps, mongoIndexes: [{ key: { keys: ["prop1", "prop2"] } }] };
|
|
||||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
|
||||||
expect(mongoIndexingPolicyComponent.hasCompoundIndex()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AddMongoIndexProps test", () => {
|
describe("AddMongoIndexProps test", () => {
|
||||||
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
const wrapper = shallow(<MongoIndexingPolicyComponent {...baseProps} />);
|
||||||
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Text,
|
Text,
|
||||||
SelectionMode,
|
SelectionMode,
|
||||||
|
IDetailsRowProps,
|
||||||
|
DetailsRow,
|
||||||
IColumn,
|
IColumn,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarType,
|
MessageBarType,
|
||||||
@@ -19,12 +21,11 @@ import {
|
|||||||
mongoIndexingPolicyDisclaimer,
|
mongoIndexingPolicyDisclaimer,
|
||||||
mediumWidthStackStyles,
|
mediumWidthStackStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
|
transparentDetailsRowStyles,
|
||||||
createAndAddMongoIndexStackProps,
|
createAndAddMongoIndexStackProps,
|
||||||
separatorStyles,
|
separatorStyles,
|
||||||
indexingPolicynUnsavedWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
infoAndToolTipTextStyle,
|
infoAndToolTipTextStyle,
|
||||||
onRenderRow,
|
|
||||||
mongoCompoundIndexNotSupportedMessage,
|
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import {
|
import {
|
||||||
@@ -139,6 +140,10 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
||||||
|
return <DetailsRow {...props} styles={transparentDetailsRowStyles} />;
|
||||||
|
};
|
||||||
|
|
||||||
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
private getActionButton = (arrayPosition: number, isCurrentIndex: boolean): JSX.Element => {
|
||||||
return isCurrentIndex ? (
|
return isCurrentIndex ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -248,7 +253,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={initialIndexes}
|
items={initialIndexes}
|
||||||
columns={this.initialIndexesColumns}
|
columns={this.initialIndexesColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={onRenderRow}
|
onRenderRow={this.onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
{this.renderIndexesToBeAdded()}
|
{this.renderIndexesToBeAdded()}
|
||||||
@@ -274,7 +279,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
items={indexesToBeDropped}
|
items={indexesToBeDropped}
|
||||||
columns={this.indexesToBeDroppedColumns}
|
columns={this.indexesToBeDroppedColumns}
|
||||||
selectionMode={SelectionMode.none}
|
selectionMode={SelectionMode.none}
|
||||||
onRenderRow={onRenderRow}
|
onRenderRow={this.onRenderRow}
|
||||||
layoutMode={DetailsListLayoutMode.justified}
|
layoutMode={DetailsListLayoutMode.justified}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -283,15 +288,6 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
public hasCompoundIndex = (): boolean => {
|
|
||||||
for (let index = 0; index < this.props.mongoIndexes.length; index++) {
|
|
||||||
if (this.props.mongoIndexes[index].key?.keys?.length > 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
private renderWarningMessage = (): JSX.Element => {
|
private renderWarningMessage = (): JSX.Element => {
|
||||||
let warningMessage: JSX.Element;
|
let warningMessage: JSX.Element;
|
||||||
if (this.getMongoWarningNotificationMessage()) {
|
if (this.getMongoWarningNotificationMessage()) {
|
||||||
@@ -313,9 +309,6 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
|
|||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
if (this.props.mongoIndexes) {
|
if (this.props.mongoIndexes) {
|
||||||
if (this.hasCompoundIndex()) {
|
|
||||||
return mongoCompoundIndexNotSupportedMessage;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Stack {...subComponentStackProps}>
|
<Stack {...subComponentStackProps}>
|
||||||
{this.renderWarningMessage()}
|
{this.renderWarningMessage()}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`MongoIndexingPolicyComponent error shown for collection with compound indexes 1`] = `
|
|
||||||
<Text>
|
|
||||||
Collections with compound indexes are not yet supported in the indexing editor. To modify indexing policy for this collection, use the Mongo Shell.
|
|
||||||
</Text>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
exports[`MongoIndexingPolicyComponent renders 1`] = `
|
||||||
<Stack
|
<Stack
|
||||||
tokens={
|
tokens={
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe("ScaleComponent", () => {
|
|||||||
autoscaleMaxThroughput: maxThroughput,
|
autoscaleMaxThroughput: maxThroughput,
|
||||||
minimumThroughput: 400,
|
minimumThroughput: 400,
|
||||||
id: "offer",
|
id: "offer",
|
||||||
offerReplacePending: true,
|
headers: { "x-ms-offer-replace-pending": true },
|
||||||
});
|
});
|
||||||
const newProps = {
|
const newProps = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
|
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
|
||||||
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import { Link, Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||||
import { configContext, Platform } from "../../../../ConfigContext";
|
import { configContext, Platform } from "../../../../ConfigContext";
|
||||||
|
|
||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
@@ -116,7 +116,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.props.collection?.offer();
|
const offer = this.props.collection?.offer();
|
||||||
if (offer?.offerReplacePending) {
|
if (offer?.headers?.[Constants.HttpHeaders.offerReplacePending]) {
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
||||||
return getThroughputApplyShortDelayMessage(
|
return getThroughputApplyShortDelayMessage(
|
||||||
this.props.isAutoPilotSelected,
|
this.props.isAutoPilotSelected,
|
||||||
@@ -165,8 +165,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
private getThroughputInputComponent = (): JSX.Element => (
|
private getThroughputInputComponent = (): JSX.Element => (
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
databaseAccount={this.props.container.databaseAccount()}
|
databaseAccount={this.props.container.databaseAccount()}
|
||||||
databaseName={this.props.collection.databaseId}
|
|
||||||
collectionName={this.props.collection.id()}
|
|
||||||
serverId={this.props.container.serverId()}
|
serverId={this.props.container.serverId()}
|
||||||
throughput={this.props.throughput}
|
throughput={this.props.throughput}
|
||||||
throughputBaseline={this.props.throughputBaseline}
|
throughputBaseline={this.props.throughputBaseline}
|
||||||
@@ -178,7 +176,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
label={this.getThroughputTitle()}
|
label={this.getThroughputTitle()}
|
||||||
isEmulator={this.isEmulator}
|
isEmulator={this.isEmulator}
|
||||||
isFixed={this.props.isFixedContainer}
|
isFixed={this.props.isFixedContainer}
|
||||||
isFreeTierAccount={this.isFreeTierAccount()}
|
|
||||||
isAutoPilotSelected={this.props.isAutoPilotSelected}
|
isAutoPilotSelected={this.props.isAutoPilotSelected}
|
||||||
onAutoPilotSelected={this.props.onAutoPilotSelected}
|
onAutoPilotSelected={this.props.onAutoPilotSelected}
|
||||||
wasAutopilotOriginallySet={this.props.wasAutopilotOriginallySet}
|
wasAutopilotOriginallySet={this.props.wasAutopilotOriginallySet}
|
||||||
@@ -193,37 +190,9 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
private isFreeTierAccount(): boolean {
|
|
||||||
const databaseAccount = this.props.container?.databaseAccount();
|
|
||||||
return databaseAccount?.properties?.enableFreeTier;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFreeTierInfoMessage(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Text>
|
|
||||||
With free tier, you will get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
|
|
||||||
account free, keep the total RU/s across all resources in the account to 400 RU/s.
|
|
||||||
<Link
|
|
||||||
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Learn more.
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...subComponentStackProps}>
|
<Stack {...subComponentStackProps}>
|
||||||
{this.isFreeTierAccount() && (
|
|
||||||
<MessageBar
|
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
|
||||||
styles={{ text: { fontSize: 14 } }}
|
|
||||||
>
|
|
||||||
{this.getFreeTierInfoMessage()}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
{this.getInitialNotificationElement() && (
|
{this.getInitialNotificationElement() && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{this.getInitialNotificationElement()}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{this.getInitialNotificationElement()}</MessageBar>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,7 +13,16 @@ import {
|
|||||||
} from "../SettingsUtils";
|
} from "../SettingsUtils";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import { Label, Text, TextField, Stack, IChoiceGroupOption, ChoiceGroup, MessageBar } from "office-ui-fabric-react";
|
import {
|
||||||
|
Label,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
Stack,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
ChoiceGroup,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
@@ -181,10 +190,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
||||||
/>
|
/>
|
||||||
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
||||||
<MessageBar
|
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
|
||||||
styles={messageBarStyles}
|
|
||||||
>
|
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import * as DataModels from "../../../../../Contracts/DataModels";
|
|||||||
describe("ThroughputInputAutoPilotV3Component", () => {
|
describe("ThroughputInputAutoPilotV3Component", () => {
|
||||||
const baseProps: ThroughputInputAutoPilotV3Props = {
|
const baseProps: ThroughputInputAutoPilotV3Props = {
|
||||||
databaseAccount: {} as DataModels.DatabaseAccount,
|
databaseAccount: {} as DataModels.DatabaseAccount,
|
||||||
databaseName: "test",
|
|
||||||
collectionName: "test",
|
|
||||||
serverId: undefined,
|
serverId: undefined,
|
||||||
wasAutopilotOriginallySet: false,
|
wasAutopilotOriginallySet: false,
|
||||||
throughput: 100,
|
throughput: 100,
|
||||||
@@ -28,7 +26,6 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
spendAckVisible: false,
|
spendAckVisible: false,
|
||||||
showAsMandatory: true,
|
showAsMandatory: true,
|
||||||
isFixed: false,
|
isFixed: false,
|
||||||
isFreeTierAccount: false,
|
|
||||||
label: "label",
|
label: "label",
|
||||||
infoBubbleText: "infoBubbleText",
|
infoBubbleText: "infoBubbleText",
|
||||||
canExceedMaximumValue: true,
|
canExceedMaximumValue: true,
|
||||||
@@ -57,6 +54,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
expect(wrapper.exists("#throughputInput")).toEqual(true);
|
||||||
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
expect(wrapper.exists("#autopilotInput")).toEqual(false);
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
||||||
|
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("autopilot input visible", () => {
|
it("autopilot input visible", () => {
|
||||||
@@ -74,7 +72,8 @@ describe("ThroughputInputAutoPilotV3Component", () => {
|
|||||||
|
|
||||||
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
wrapper.setProps({ wasAutopilotOriginallySet: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.exists("#throughputSpendElement")).toEqual(true);
|
expect(wrapper.exists("#autoscaleSpendElement")).toEqual(true);
|
||||||
|
expect(wrapper.exists("#throughputSpendElement")).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("spendAck checkbox visible", () => {
|
it("spendAck checkbox visible", () => {
|
||||||
|
|||||||
@@ -8,15 +8,10 @@ import {
|
|||||||
checkBoxAndInputStackProps,
|
checkBoxAndInputStackProps,
|
||||||
getChoiceGroupStyles,
|
getChoiceGroupStyles,
|
||||||
messageBarStyles,
|
messageBarStyles,
|
||||||
getEstimatedSpendingElement,
|
getEstimatedSpendElement,
|
||||||
|
getEstimatedAutoscaleSpendElement,
|
||||||
getAutoPilotV3SpendElement,
|
getAutoPilotV3SpendElement,
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
saveThroughputWarningMessage,
|
|
||||||
ManualEstimatedSpendingDisplayProps,
|
|
||||||
AutoscaleEstimatedSpendingDisplayProps,
|
|
||||||
PriceBreakdown,
|
|
||||||
getRuPriceBreakdown,
|
|
||||||
transparentDetailsHeaderStyle,
|
|
||||||
} from "../../SettingsRenderUtils";
|
} from "../../SettingsRenderUtils";
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
@@ -28,8 +23,7 @@ import {
|
|||||||
Label,
|
Label,
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
FontIcon,
|
MessageBarType,
|
||||||
IColumn,
|
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
|
||||||
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
|
||||||
@@ -38,16 +32,11 @@ import * as DataModels from "../../../../../Contracts/DataModels";
|
|||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||||
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
import { usageInGB } from "../../../../../Utils/PricingUtils";
|
||||||
import { Features } from "../../../../../Common/Constants";
|
import { Features } from "../../../../../Common/Constants";
|
||||||
|
|
||||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
|
|
||||||
export interface ThroughputInputAutoPilotV3Props {
|
export interface ThroughputInputAutoPilotV3Props {
|
||||||
databaseAccount: DataModels.DatabaseAccount;
|
databaseAccount: DataModels.DatabaseAccount;
|
||||||
databaseName: string;
|
|
||||||
collectionName: string;
|
|
||||||
serverId: string;
|
serverId: string;
|
||||||
throughput: number;
|
throughput: number;
|
||||||
throughputBaseline: number;
|
throughputBaseline: number;
|
||||||
@@ -62,7 +51,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
spendAckVisible?: boolean;
|
spendAckVisible?: boolean;
|
||||||
showAsMandatory?: boolean;
|
showAsMandatory?: boolean;
|
||||||
isFixed: boolean;
|
isFixed: boolean;
|
||||||
isFreeTierAccount: boolean;
|
|
||||||
isEmulator: boolean;
|
isEmulator: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
infoBubbleText?: string;
|
infoBubbleText?: string;
|
||||||
@@ -81,7 +69,6 @@ export interface ThroughputInputAutoPilotV3Props {
|
|||||||
|
|
||||||
interface ThroughputInputAutoPilotV3State {
|
interface ThroughputInputAutoPilotV3State {
|
||||||
spendAckChecked: boolean;
|
spendAckChecked: boolean;
|
||||||
exceedFreeTierThroughput: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ThroughputInputAutoPilotV3Component extends React.Component<
|
export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||||
@@ -156,8 +143,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
spendAckChecked: this.props.spendAckChecked,
|
spendAckChecked: this.props.spendAckChecked,
|
||||||
exceedFreeTierThroughput:
|
|
||||||
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
|
||||||
@@ -180,243 +165,33 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDirty: boolean = this.IsComponentDirty().isDiscardable;
|
|
||||||
const serverId: string = this.props.serverId;
|
const serverId: string = this.props.serverId;
|
||||||
|
const offerThroughput: number = this.props.throughput;
|
||||||
|
|
||||||
const regions = account?.properties?.readLocations?.length || 1;
|
const regions = account?.properties?.readLocations?.length || 1;
|
||||||
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
|
||||||
|
|
||||||
let estimatedSpend: JSX.Element;
|
let estimatedSpend: JSX.Element;
|
||||||
|
|
||||||
if (!this.props.isAutoPilotSelected) {
|
if (!this.props.isAutoPilotSelected) {
|
||||||
estimatedSpend = this.getEstimatedManualSpendElement(
|
estimatedSpend = getEstimatedSpendElement(
|
||||||
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
// if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set...
|
||||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : this.props.throughputBaseline,
|
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster
|
||||||
isDirty ? this.props.throughput : undefined
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
estimatedSpend = this.getEstimatedAutoscaleSpendElement(
|
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
||||||
this.props.maxAutoPilotThroughputBaseline,
|
this.props.maxAutoPilotThroughput,
|
||||||
serverId,
|
serverId,
|
||||||
regions,
|
regions,
|
||||||
multimaster,
|
multimaster
|
||||||
isDirty ? this.props.maxAutoPilotThroughput : undefined
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return estimatedSpend;
|
return estimatedSpend;
|
||||||
};
|
};
|
||||||
|
|
||||||
private getEstimatedAutoscaleSpendElement = (
|
|
||||||
throughput: number,
|
|
||||||
serverId: string,
|
|
||||||
numberOfRegions: number,
|
|
||||||
isMultimaster: boolean,
|
|
||||||
newThroughput?: number
|
|
||||||
): JSX.Element => {
|
|
||||||
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
|
|
||||||
const estimatedSpendingColumns: IColumn[] = [
|
|
||||||
{
|
|
||||||
key: "costType",
|
|
||||||
name: "",
|
|
||||||
fieldName: "costType",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "minPerMonth",
|
|
||||||
name: "Min Per Month",
|
|
||||||
fieldName: "minPerMonth",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "maxPerMonth",
|
|
||||||
name: "Max Per Month",
|
|
||||||
fieldName: "maxPerMonth",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
|
|
||||||
{
|
|
||||||
costType: <Text>Current Cost</Text>,
|
|
||||||
minPerMonth: (
|
|
||||||
<Text>
|
|
||||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice / 10)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
maxPerMonth: (
|
|
||||||
<Text>
|
|
||||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (newThroughput) {
|
|
||||||
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
|
||||||
newThroughput,
|
|
||||||
serverId,
|
|
||||||
numberOfRegions,
|
|
||||||
isMultimaster,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
estimatedSpendingItems.unshift({
|
|
||||||
costType: (
|
|
||||||
<Text>
|
|
||||||
<b>Updated Cost</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
minPerMonth: (
|
|
||||||
<Text>
|
|
||||||
<b>
|
|
||||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice / 10)}
|
|
||||||
</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
maxPerMonth: (
|
|
||||||
<Text>
|
|
||||||
<b>
|
|
||||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
|
||||||
</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getEstimatedSpendingElement(
|
|
||||||
estimatedSpendingColumns,
|
|
||||||
estimatedSpendingItems,
|
|
||||||
newThroughput ?? throughput,
|
|
||||||
numberOfRegions,
|
|
||||||
prices,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private getEstimatedManualSpendElement = (
|
|
||||||
throughput: number,
|
|
||||||
serverId: string,
|
|
||||||
numberOfRegions: number,
|
|
||||||
isMultimaster: boolean,
|
|
||||||
newThroughput?: number
|
|
||||||
): JSX.Element => {
|
|
||||||
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
|
|
||||||
const estimatedSpendingColumns: IColumn[] = [
|
|
||||||
{
|
|
||||||
key: "costType",
|
|
||||||
name: "",
|
|
||||||
fieldName: "costType",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "hourly",
|
|
||||||
name: "Hourly",
|
|
||||||
fieldName: "hourly",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "daily",
|
|
||||||
name: "Daily",
|
|
||||||
fieldName: "daily",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "monthly",
|
|
||||||
name: "Monthly",
|
|
||||||
fieldName: "monthly",
|
|
||||||
minWidth: 100,
|
|
||||||
maxWidth: 200,
|
|
||||||
isResizable: true,
|
|
||||||
styles: transparentDetailsHeaderStyle,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
|
|
||||||
{
|
|
||||||
costType: <Text>Current Cost</Text>,
|
|
||||||
hourly: (
|
|
||||||
<Text>
|
|
||||||
{prices.currencySign} {calculateEstimateNumber(prices.hourlyPrice)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
daily: (
|
|
||||||
<Text>
|
|
||||||
{prices.currencySign} {calculateEstimateNumber(prices.dailyPrice)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
monthly: (
|
|
||||||
<Text>
|
|
||||||
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (newThroughput) {
|
|
||||||
const newPrices: PriceBreakdown = getRuPriceBreakdown(
|
|
||||||
newThroughput,
|
|
||||||
serverId,
|
|
||||||
numberOfRegions,
|
|
||||||
isMultimaster,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
estimatedSpendingItems.unshift({
|
|
||||||
costType: (
|
|
||||||
<Text>
|
|
||||||
<b>Updated Cost</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
hourly: (
|
|
||||||
<Text>
|
|
||||||
<b>
|
|
||||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.hourlyPrice)}
|
|
||||||
</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
daily: (
|
|
||||||
<Text>
|
|
||||||
<b>
|
|
||||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.dailyPrice)}
|
|
||||||
</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
monthly: (
|
|
||||||
<Text>
|
|
||||||
<b>
|
|
||||||
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
|
|
||||||
</b>
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getEstimatedSpendingElement(
|
|
||||||
estimatedSpendingColumns,
|
|
||||||
estimatedSpendingItems,
|
|
||||||
newThroughput ?? throughput,
|
|
||||||
numberOfRegions,
|
|
||||||
prices,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private getAutoPilotUsageCost = (): JSX.Element => {
|
private getAutoPilotUsageCost = (): JSX.Element => {
|
||||||
if (!this.props.maxAutoPilotThroughput) {
|
if (!this.props.maxAutoPilotThroughput) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -432,7 +207,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
const newThroughput = getSanitizedInputValue(newValue);
|
const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -440,11 +215,10 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
newValue?: string
|
newValue?: string
|
||||||
): void => {
|
): void => {
|
||||||
const newThroughput = getSanitizedInputValue(newValue);
|
const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
|
||||||
if (this.overrideWithAutoPilotSettings()) {
|
if (this.overrideWithAutoPilotSettings()) {
|
||||||
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
this.props.onMaxAutoPilotThroughputChange(newThroughput);
|
||||||
} else {
|
} else {
|
||||||
this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
|
|
||||||
this.props.onThroughputChange(newThroughput);
|
this.props.onThroughputChange(newThroughput);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -452,19 +226,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
private onChoiceGroupChange = (
|
private onChoiceGroupChange = (
|
||||||
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
|
||||||
option?: IChoiceGroupOption
|
option?: IChoiceGroupOption
|
||||||
): void => {
|
): void => this.props.onAutoPilotSelected(option.key === "true");
|
||||||
this.props.onAutoPilotSelected(option.key === "true");
|
|
||||||
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
|
|
||||||
changedSelectedValueTo:
|
|
||||||
option.key === "true" ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
databaseAccountName: this.props.databaseAccount?.name,
|
|
||||||
databaseName: this.props.databaseName,
|
|
||||||
collectionName: this.props.collectionName,
|
|
||||||
apiKind: userContext.defaultExperience,
|
|
||||||
dataExplorerArea: "Scale Tab V2",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private minRUperGBSurvey = (): JSX.Element => {
|
private minRUperGBSurvey = (): JSX.Element => {
|
||||||
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
|
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
|
||||||
@@ -500,10 +262,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
{this.overrideWithProvisionedThroughputSettings() && (
|
{this.overrideWithProvisionedThroughputSettings() && (
|
||||||
<MessageBar
|
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
|
||||||
styles={messageBarStyles}
|
|
||||||
>
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
@@ -559,12 +318,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
|
|
||||||
private renderThroughputInput = (): JSX.Element => (
|
private renderThroughputInput = (): JSX.Element => (
|
||||||
<Stack {...titleAndInputStackProps}>
|
<Stack {...titleAndInputStackProps}>
|
||||||
<Text>
|
|
||||||
Estimate your required throughput with
|
|
||||||
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/">
|
|
||||||
{` capacity calculator`} <FontIcon iconName="NavigateExternalInline" />
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
type="number"
|
type="number"
|
||||||
@@ -580,21 +333,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
}
|
}
|
||||||
onChange={this.onThroughputChange}
|
onChange={this.onThroughputChange}
|
||||||
/>
|
/>
|
||||||
{this.state.exceedFreeTierThroughput && (
|
|
||||||
<MessageBar
|
|
||||||
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
|
|
||||||
styles={messageBarStyles}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
"Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale."
|
|
||||||
}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
{this.props.getThroughputWarningMessage() && (
|
{this.props.getThroughputWarningMessage() && (
|
||||||
<MessageBar
|
<MessageBar messageBarType={MessageBarType.warning} styles={messageBarStyles}>
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
|
||||||
styles={messageBarStyles}
|
|
||||||
>
|
|
||||||
{this.props.getThroughputWarningMessage()}
|
{this.props.getThroughputWarningMessage()}
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
)}
|
)}
|
||||||
@@ -609,32 +349,13 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
onChange={this.onSpendAckChecked}
|
onChange={this.onSpendAckChecked}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<br />
|
|
||||||
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
{this.props.isFixed && <p>When using a collection with fixed storage capacity, you can set up to 10,000 RU/s.</p>}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
private renderWarningMessage = (): JSX.Element => {
|
|
||||||
let warningMessage: JSX.Element;
|
|
||||||
if (this.IsComponentDirty().isDiscardable) {
|
|
||||||
warningMessage = saveThroughputWarningMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{warningMessage && (
|
|
||||||
<MessageBar messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}>
|
|
||||||
{warningMessage}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...checkBoxAndInputStackProps}>
|
<Stack {...checkBoxAndInputStackProps}>
|
||||||
{this.renderWarningMessage()}
|
|
||||||
{this.renderThroughputModeChoices()}
|
{this.renderThroughputModeChoices()}
|
||||||
|
|
||||||
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
{this.props.isAutoPilotSelected ? this.renderAutoPilotInput() : this.renderThroughputInput()}
|
||||||
|
|||||||
@@ -8,26 +8,6 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledMessageBarBase
|
|
||||||
messageBarIconProps={
|
|
||||||
Object {
|
|
||||||
"className": "messageBarWarningIcon",
|
|
||||||
"iconName": "WarningSolid",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"fontSize": 14,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Your bill will be affected as you update your throughput settings. Please review the updated cost estimate below before saving your changes
|
|
||||||
</Text>
|
|
||||||
</StyledMessageBarBase>
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<StyledLabelBase
|
<StyledLabelBase
|
||||||
id="settingsV2RadioButtonLabelId"
|
id="settingsV2RadioButtonLabelId"
|
||||||
@@ -39,7 +19,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,21 +30,12 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
/>
|
/>
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
<StyledMessageBarBase
|
<StyledMessageBarBase
|
||||||
messageBarIconProps={
|
messageBarType={5}
|
||||||
Object {
|
|
||||||
"className": "messageBarInfoIcon",
|
|
||||||
"iconName": "InfoSolid",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"backgroundColor": "white",
|
|
||||||
"marginTop": "5px",
|
"marginTop": "5px",
|
||||||
},
|
},
|
||||||
"text": Object {
|
|
||||||
"fontSize": 14,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -73,7 +44,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +156,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,19 +214,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text>
|
|
||||||
Estimate your required throughput with
|
|
||||||
<StyledLinkBase
|
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
capacity calculator
|
|
||||||
|
|
||||||
<Component
|
|
||||||
iconName="NavigateExternalInline"
|
|
||||||
/>
|
|
||||||
</StyledLinkBase>
|
|
||||||
</Text>
|
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -281,142 +239,38 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Text
|
||||||
styles={
|
id="throughputSpendElement"
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"width": 600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens={
|
|
||||||
Object {
|
|
||||||
"childrenGap": 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<StyledWithViewportComponent
|
Estimated cost (
|
||||||
columns={
|
USD
|
||||||
Array [
|
):
|
||||||
Object {
|
|
||||||
"fieldName": "costType",
|
<b>
|
||||||
"isResizable": true,
|
|
||||||
"key": "costType",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "hourly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "hourly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Hourly",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "daily",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "daily",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Daily",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "monthly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "monthly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Monthly",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
disableSelectionZone={true}
|
|
||||||
items={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"costType": <Text>
|
|
||||||
Current Cost
|
|
||||||
</Text>,
|
|
||||||
"daily": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
0.19
|
|
||||||
</Text>,
|
|
||||||
"hourly": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
0.0080
|
|
||||||
</Text>,
|
|
||||||
"monthly": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
5.84
|
|
||||||
</Text>,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
layoutMode={1}
|
|
||||||
onRenderRow={[Function]}
|
|
||||||
selectionMode={0}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
id="throughputSpendElement"
|
|
||||||
>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
1
|
|
||||||
,
|
|
||||||
100
|
|
||||||
RU/s,
|
|
||||||
$
|
$
|
||||||
0.00008
|
0.0080
|
||||||
/RU)
|
hourly
|
||||||
</Text>
|
/
|
||||||
<Text>
|
$
|
||||||
<em>
|
0.19
|
||||||
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
daily
|
||||||
</em>
|
/
|
||||||
</Text>
|
$
|
||||||
</Stack>
|
5.84
|
||||||
|
monthly
|
||||||
|
|
||||||
|
</b>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={false}
|
checked={false}
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
@@ -434,7 +288,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<br />
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
@@ -458,7 +311,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,19 +369,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Text>
|
|
||||||
Estimate your required throughput with
|
|
||||||
<StyledLinkBase
|
|
||||||
href="https://cosmos.azure.com/capacitycalculator/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
capacity calculator
|
|
||||||
|
|
||||||
<Component
|
|
||||||
iconName="NavigateExternalInline"
|
|
||||||
/>
|
|
||||||
</StyledLinkBase>
|
|
||||||
</Text>
|
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
@@ -554,143 +394,38 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
type="number"
|
type="number"
|
||||||
value="100"
|
value="100"
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Text
|
||||||
styles={
|
id="throughputSpendElement"
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"width": 600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens={
|
|
||||||
Object {
|
|
||||||
"childrenGap": 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<StyledWithViewportComponent
|
Estimated cost (
|
||||||
columns={
|
USD
|
||||||
Array [
|
):
|
||||||
Object {
|
|
||||||
"fieldName": "costType",
|
<b>
|
||||||
"isResizable": true,
|
|
||||||
"key": "costType",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "hourly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "hourly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Hourly",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "daily",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "daily",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Daily",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "monthly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "monthly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Monthly",
|
|
||||||
"styles": Object {
|
|
||||||
"root": Object {
|
|
||||||
"selectors": Object {
|
|
||||||
":hover": Object {
|
|
||||||
"background": "transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
disableSelectionZone={true}
|
|
||||||
items={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"costType": <Text>
|
|
||||||
Current Cost
|
|
||||||
</Text>,
|
|
||||||
"daily": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
0.19
|
|
||||||
</Text>,
|
|
||||||
"hourly": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
0.0080
|
|
||||||
</Text>,
|
|
||||||
"monthly": <Text>
|
|
||||||
$
|
|
||||||
|
|
||||||
5.84
|
|
||||||
</Text>,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
layoutMode={1}
|
|
||||||
onRenderRow={[Function]}
|
|
||||||
selectionMode={0}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
id="throughputSpendElement"
|
|
||||||
>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
1
|
|
||||||
,
|
|
||||||
100
|
|
||||||
RU/s,
|
|
||||||
$
|
$
|
||||||
0.00008
|
0.0080
|
||||||
/RU)
|
hourly
|
||||||
</Text>
|
/
|
||||||
<Text>
|
$
|
||||||
<em>
|
0.19
|
||||||
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
daily
|
||||||
</em>
|
/
|
||||||
</Text>
|
$
|
||||||
</Stack>
|
5.84
|
||||||
<br />
|
monthly
|
||||||
|
|
||||||
|
</b>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
1
|
||||||
|
,
|
||||||
|
100
|
||||||
|
RU/s,
|
||||||
|
$
|
||||||
|
0.00008
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,6 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
>
|
>
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
canExceedMaximumValue={true}
|
canExceedMaximumValue={true}
|
||||||
collectionName="test"
|
|
||||||
databaseName="test"
|
|
||||||
getThroughputWarningMessage={[Function]}
|
getThroughputWarningMessage={[Function]}
|
||||||
isAutoPilotSelected={false}
|
isAutoPilotSelected={false}
|
||||||
isEmulator={false}
|
isEmulator={false}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,7 +412,7 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,7 +952,7 @@ exports[`SubSettingsComponent renders 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1228,7 +1228,7 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
getSanitizedInputValue,
|
getSanitizedInputValue,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
isDirty,
|
isDirty,
|
||||||
|
isDirtyTypes,
|
||||||
MongoIndexTypes,
|
MongoIndexTypes,
|
||||||
MongoNotificationType,
|
MongoNotificationType,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
@@ -70,18 +71,17 @@ describe("SettingsUtils", () => {
|
|||||||
excludedPaths: [],
|
excludedPaths: [],
|
||||||
} as DataModels.IndexingPolicy;
|
} as DataModels.IndexingPolicy;
|
||||||
|
|
||||||
it("works on all types", () => {
|
const cases = [
|
||||||
expect(isDirty("baseline", "baseline")).toEqual(false);
|
["baseline", "current"],
|
||||||
expect(isDirty(0, 0)).toEqual(false);
|
[0, 1],
|
||||||
expect(isDirty(true, true)).toEqual(false);
|
[true, false],
|
||||||
expect(isDirty(undefined, undefined)).toEqual(false);
|
[undefined, indexingPolicy],
|
||||||
expect(isDirty(indexingPolicy, indexingPolicy)).toEqual(false);
|
[indexingPolicy, { ...indexingPolicy, automatic: false }],
|
||||||
|
];
|
||||||
|
|
||||||
expect(isDirty("baseline", "current")).toEqual(true);
|
test.each(cases)("", (baseline: isDirtyTypes, current: isDirtyTypes) => {
|
||||||
expect(isDirty(0, 1)).toEqual(true);
|
expect(isDirty(baseline, baseline)).toEqual(false);
|
||||||
expect(isDirty(true, false)).toEqual(true);
|
expect(isDirty(baseline, current)).toEqual(true);
|
||||||
expect(isDirty(undefined, indexingPolicy)).toEqual(true);
|
|
||||||
expect(isDirty(indexingPolicy, { ...indexingPolicy, automatic: false })).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
|
|||||||
return procedureFromBackEnd;
|
return procedureFromBackEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSanitizedInputValue = (newValueString: string, max?: number): number => {
|
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
|
||||||
const newValue = parseInt(newValueString);
|
const newValue = parseInt(newValueString);
|
||||||
if (isNaN(newValue)) {
|
if (isNaN(newValue)) {
|
||||||
return zeroValue;
|
return zeroValue;
|
||||||
}
|
}
|
||||||
// make sure new value does not exceed the maximum throughput
|
// make sure new value does not exceed the maximum throughput
|
||||||
return max ? Math.min(newValue, max) : newValue;
|
return Math.min(newValue, max);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export const collection = ({
|
|||||||
manualThroughput: 10000,
|
manualThroughput: 10000,
|
||||||
minimumThroughput: 6000,
|
minimumThroughput: 6000,
|
||||||
id: "offer",
|
id: "offer",
|
||||||
offerReplacePending: false,
|
|
||||||
}),
|
}),
|
||||||
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
|
||||||
{} as DataModels.ConflictResolutionPolicy
|
{} as DataModels.ConflictResolutionPolicy
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -105,7 +104,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -593,7 +591,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -668,7 +665,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -735,6 +731,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -946,7 +943,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isAutoscaleDefaultEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -955,7 +952,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
"isMongoIndexingEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -1028,6 +1024,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -1053,6 +1050,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -1120,14 +1118,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
"selectedDatabaseId": [Function],
|
||||||
"selectedNode": [Function],
|
"selectedNode": [Function],
|
||||||
"selfServeComponentAdapter": SelfServeComponentAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeType": [Function],
|
|
||||||
"serverId": [Function],
|
"serverId": [Function],
|
||||||
"settingsPane": SettingsPane {
|
"settingsPane": SettingsPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -1189,9 +1179,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
"isCollapsed": [Function],
|
"isCollapsed": [Function],
|
||||||
|
"leftSide": null,
|
||||||
"leftSideId": "resourcetree",
|
"leftSideId": "resourcetree",
|
||||||
"onResizeStart": [Function],
|
"onResizeStart": [Function],
|
||||||
"onResizeStop": [Function],
|
"onResizeStop": [Function],
|
||||||
|
"splitter": null,
|
||||||
"splitterId": "h_splitter1",
|
"splitterId": "h_splitter1",
|
||||||
},
|
},
|
||||||
"stringInputPane": StringInputPane {
|
"stringInputPane": StringInputPane {
|
||||||
@@ -1338,7 +1330,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -1388,7 +1379,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -1876,7 +1866,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -1951,7 +1940,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -2018,6 +2006,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2229,7 +2218,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isAutoscaleDefaultEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -2238,7 +2227,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
"isMongoIndexingEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -2311,6 +2299,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2336,6 +2325,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -2403,14 +2393,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
"selectedDatabaseId": [Function],
|
||||||
"selectedNode": [Function],
|
"selectedNode": [Function],
|
||||||
"selfServeComponentAdapter": SelfServeComponentAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeType": [Function],
|
|
||||||
"serverId": [Function],
|
"serverId": [Function],
|
||||||
"settingsPane": SettingsPane {
|
"settingsPane": SettingsPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -2472,9 +2454,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
"isCollapsed": [Function],
|
"isCollapsed": [Function],
|
||||||
|
"leftSide": null,
|
||||||
"leftSideId": "resourcetree",
|
"leftSideId": "resourcetree",
|
||||||
"onResizeStart": [Function],
|
"onResizeStart": [Function],
|
||||||
"onResizeStop": [Function],
|
"onResizeStop": [Function],
|
||||||
|
"splitter": null,
|
||||||
"splitterId": "h_splitter1",
|
"splitterId": "h_splitter1",
|
||||||
},
|
},
|
||||||
"stringInputPane": StringInputPane {
|
"stringInputPane": StringInputPane {
|
||||||
@@ -2634,7 +2618,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -2684,7 +2667,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -3172,7 +3154,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -3247,7 +3228,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -3314,6 +3294,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3525,7 +3506,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isAutoscaleDefaultEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -3534,7 +3515,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
"isMongoIndexingEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -3607,6 +3587,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3632,6 +3613,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -3699,14 +3681,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
"selectedDatabaseId": [Function],
|
||||||
"selectedNode": [Function],
|
"selectedNode": [Function],
|
||||||
"selfServeComponentAdapter": SelfServeComponentAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeType": [Function],
|
|
||||||
"serverId": [Function],
|
"serverId": [Function],
|
||||||
"settingsPane": SettingsPane {
|
"settingsPane": SettingsPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -3768,9 +3742,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
"isCollapsed": [Function],
|
"isCollapsed": [Function],
|
||||||
|
"leftSide": null,
|
||||||
"leftSideId": "resourcetree",
|
"leftSideId": "resourcetree",
|
||||||
"onResizeStart": [Function],
|
"onResizeStart": [Function],
|
||||||
"onResizeStop": [Function],
|
"onResizeStop": [Function],
|
||||||
|
"splitter": null,
|
||||||
"splitterId": "h_splitter1",
|
"splitterId": "h_splitter1",
|
||||||
},
|
},
|
||||||
"stringInputPane": StringInputPane {
|
"stringInputPane": StringInputPane {
|
||||||
@@ -3917,7 +3893,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -3967,7 +3942,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -4455,7 +4429,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"formWarnings": [Function],
|
"formWarnings": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "addcollectionpane",
|
"id": "addcollectionpane",
|
||||||
"isAnalyticalStorageOn": [Function],
|
"isAnalyticalStorageOn": [Function],
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
@@ -4530,7 +4503,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"firstFieldHasFocus": [Function],
|
"firstFieldHasFocus": [Function],
|
||||||
"formErrors": [Function],
|
"formErrors": [Function],
|
||||||
"formErrorsDetails": [Function],
|
"formErrorsDetails": [Function],
|
||||||
"freeTierExceedThroughputTooltip": [Function],
|
|
||||||
"id": "adddatabasepane",
|
"id": "adddatabasepane",
|
||||||
"isAutoPilotSelected": [Function],
|
"isAutoPilotSelected": [Function],
|
||||||
"isExecuting": [Function],
|
"isExecuting": [Function],
|
||||||
@@ -4597,6 +4569,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
|
"armEndpoint": [Function],
|
||||||
"browseQueriesPane": BrowseQueriesPane {
|
"browseQueriesPane": BrowseQueriesPane {
|
||||||
"canSaveQueries": [Function],
|
"canSaveQueries": [Function],
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4808,7 +4781,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"hasWriteAccess": [Function],
|
"hasWriteAccess": [Function],
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isAuthWithResourceToken": [Function],
|
"isAuthWithResourceToken": [Function],
|
||||||
"isAutoscaleDefaultEnabled": [Function],
|
"isCodeOfConductEnabled": [Function],
|
||||||
"isCopyNotebookPaneEnabled": [Function],
|
"isCopyNotebookPaneEnabled": [Function],
|
||||||
"isEnableMongoCapabilityPresent": [Function],
|
"isEnableMongoCapabilityPresent": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
@@ -4817,7 +4790,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"isHostedDataExplorerEnabled": [Function],
|
"isHostedDataExplorerEnabled": [Function],
|
||||||
"isLeftPaneExpanded": [Function],
|
"isLeftPaneExpanded": [Function],
|
||||||
"isLinkInjectionEnabled": [Function],
|
"isLinkInjectionEnabled": [Function],
|
||||||
"isMongoIndexingEnabled": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
"isNotebooksEnabledForAccount": [Function],
|
||||||
"isNotificationConsoleExpanded": [Function],
|
"isNotificationConsoleExpanded": [Function],
|
||||||
@@ -4890,6 +4862,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"onRefreshResourcesClick": [Function],
|
"onRefreshResourcesClick": [Function],
|
||||||
"onSwitchToConnectionString": [Function],
|
"onSwitchToConnectionString": [Function],
|
||||||
"onToggleKeyDown": [Function],
|
"onToggleKeyDown": [Function],
|
||||||
|
"parentFrameDataExplorerVersion": [Function],
|
||||||
"provideFeedbackEmail": [Function],
|
"provideFeedbackEmail": [Function],
|
||||||
"queriesClient": QueriesClient {
|
"queriesClient": QueriesClient {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -4915,6 +4888,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"titleLabel": "Select Columns",
|
"titleLabel": "Select Columns",
|
||||||
"visible": [Function],
|
"visible": [Function],
|
||||||
},
|
},
|
||||||
|
"quotaId": [Function],
|
||||||
"refreshDatabaseAccount": [Function],
|
"refreshDatabaseAccount": [Function],
|
||||||
"refreshNotebookList": [Function],
|
"refreshNotebookList": [Function],
|
||||||
"refreshTreeTitle": [Function],
|
"refreshTreeTitle": [Function],
|
||||||
@@ -4982,14 +4956,6 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"selectedDatabaseId": [Function],
|
"selectedDatabaseId": [Function],
|
||||||
"selectedNode": [Function],
|
"selectedNode": [Function],
|
||||||
"selfServeComponentAdapter": SelfServeComponentAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeLoadingComponentAdapter": SelfServeLoadingComponentAdapter {
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selfServeType": [Function],
|
|
||||||
"serverId": [Function],
|
"serverId": [Function],
|
||||||
"settingsPane": SettingsPane {
|
"settingsPane": SettingsPane {
|
||||||
"container": [Circular],
|
"container": [Circular],
|
||||||
@@ -5051,9 +5017,11 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
"isCollapsed": [Function],
|
"isCollapsed": [Function],
|
||||||
|
"leftSide": null,
|
||||||
"leftSideId": "resourcetree",
|
"leftSideId": "resourcetree",
|
||||||
"onResizeStart": [Function],
|
"onResizeStart": [Function],
|
||||||
"onResizeStop": [Function],
|
"onResizeStop": [Function],
|
||||||
|
"splitter": null,
|
||||||
"splitterId": "h_splitter1",
|
"splitterId": "h_splitter1",
|
||||||
},
|
},
|
||||||
"stringInputPane": StringInputPane {
|
"stringInputPane": StringInputPane {
|
||||||
|
|||||||
@@ -60,106 +60,72 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
</StyledLinkBase>
|
</StyledLinkBase>
|
||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
<Stack
|
<Text
|
||||||
styles={
|
id="throughputSpendElement"
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"width": 600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens={
|
|
||||||
Object {
|
|
||||||
"childrenGap": 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<StyledWithViewportComponent
|
Estimated cost (
|
||||||
columns={
|
RMB
|
||||||
Array [
|
):
|
||||||
Object {
|
|
||||||
"fieldName": "costType",
|
<b>
|
||||||
"isResizable": true,
|
|
||||||
"key": "costType",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "hourly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "hourly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Hourly",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "daily",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "daily",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Daily",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"fieldName": "monthly",
|
|
||||||
"isResizable": true,
|
|
||||||
"key": "monthly",
|
|
||||||
"maxWidth": 200,
|
|
||||||
"minWidth": 100,
|
|
||||||
"name": "Monthly",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
disableSelectionZone={true}
|
|
||||||
items={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"costType": <Text>
|
|
||||||
Current Cost
|
|
||||||
</Text>,
|
|
||||||
"daily": <Text>
|
|
||||||
$ 24.48
|
|
||||||
</Text>,
|
|
||||||
"hourly": <Text>
|
|
||||||
$ 1.02
|
|
||||||
</Text>,
|
|
||||||
"monthly": <Text>
|
|
||||||
$ 744.6
|
|
||||||
</Text>,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
layoutMode={1}
|
|
||||||
onRenderRow={[Function]}
|
|
||||||
selectionMode={0}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
id="throughputSpendElement"
|
|
||||||
>
|
|
||||||
(
|
|
||||||
regions:
|
|
||||||
|
|
||||||
2
|
|
||||||
,
|
|
||||||
1000
|
|
||||||
RU/s,
|
|
||||||
¥
|
¥
|
||||||
0.00051
|
1.02
|
||||||
/RU)
|
hourly
|
||||||
</Text>
|
/
|
||||||
<Text>
|
¥
|
||||||
<em>
|
24.48
|
||||||
*This cost is an estimate and may vary based on the regions where your account is deployed and potential discounts applied to your account
|
daily
|
||||||
</em>
|
/
|
||||||
</Text>
|
¥
|
||||||
</Stack>
|
744.60
|
||||||
|
monthly
|
||||||
|
|
||||||
|
</b>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
2
|
||||||
|
,
|
||||||
|
1000
|
||||||
|
RU/s,
|
||||||
|
¥
|
||||||
|
0.00051
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
id="autoscaleSpendElement"
|
||||||
|
>
|
||||||
|
Estimated monthly cost (
|
||||||
|
RMB
|
||||||
|
) is
|
||||||
|
|
||||||
|
<b>
|
||||||
|
¥
|
||||||
|
111.69
|
||||||
|
-
|
||||||
|
¥
|
||||||
|
1116.90
|
||||||
|
|
||||||
|
</b>
|
||||||
|
(
|
||||||
|
regions:
|
||||||
|
|
||||||
|
2
|
||||||
|
,
|
||||||
|
100
|
||||||
|
-
|
||||||
|
1000
|
||||||
|
RU/s,
|
||||||
|
¥
|
||||||
|
0.000765
|
||||||
|
/RU)
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
id="manualToAutoscaleDisclaimerElement"
|
id="manualToAutoscaleDisclaimerElement"
|
||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +142,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +161,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +173,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +185,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +196,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +215,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +234,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +252,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +265,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +276,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +295,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,7 +337,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +352,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,7 +368,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
styles={
|
styles={
|
||||||
Object {
|
Object {
|
||||||
"root": Object {
|
"root": Object {
|
||||||
"fontSize": 14,
|
"fontSize": 12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import { SmartUiComponent, SmartUiDescriptor, UiType } from "./SmartUiComponent";
|
import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
|
||||||
|
|
||||||
describe("SmartUiComponent", () => {
|
describe("SmartUiComponent", () => {
|
||||||
const exampleData: SmartUiDescriptor = {
|
const exampleData: Descriptor = {
|
||||||
root: {
|
root: {
|
||||||
id: "root",
|
id: "root",
|
||||||
info: {
|
info: {
|
||||||
@@ -24,7 +24,7 @@ describe("SmartUiComponent", () => {
|
|||||||
max: 500,
|
max: 500,
|
||||||
step: 10,
|
step: 10,
|
||||||
defaultValue: 400,
|
defaultValue: 400,
|
||||||
uiType: UiType.Spinner,
|
inputType: "spin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,21 +37,7 @@ describe("SmartUiComponent", () => {
|
|||||||
max: 500,
|
max: 500,
|
||||||
step: 10,
|
step: 10,
|
||||||
defaultValue: 400,
|
defaultValue: 400,
|
||||||
uiType: UiType.Slider,
|
inputType: "slider",
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "throughput3",
|
|
||||||
input: {
|
|
||||||
label: "Throughput (invalid)",
|
|
||||||
dataFieldName: "throughput3",
|
|
||||||
type: "boolean",
|
|
||||||
min: 400,
|
|
||||||
max: 500,
|
|
||||||
step: 10,
|
|
||||||
defaultValue: 400,
|
|
||||||
uiType: UiType.Spinner,
|
|
||||||
errorMessage: "label, truelabel and falselabel are required for boolean input 'throughput3'",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -78,11 +64,11 @@ describe("SmartUiComponent", () => {
|
|||||||
input: {
|
input: {
|
||||||
label: "Database",
|
label: "Database",
|
||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "object",
|
type: "enum",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "Database 1", key: "db1" },
|
{ label: "Database 1", key: "db1", value: "database1" },
|
||||||
{ label: "Database 2", key: "db2" },
|
{ label: "Database 2", key: "db2", value: "database2" },
|
||||||
{ label: "Database 3", key: "db3" },
|
{ label: "Database 3", key: "db3", value: "database3" },
|
||||||
],
|
],
|
||||||
defaultKey: "db2",
|
defaultKey: "db2",
|
||||||
},
|
},
|
||||||
@@ -91,11 +77,12 @@ describe("SmartUiComponent", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should render", async () => {
|
const exampleCallbacks = (newValues: Map<string, InputType>): void => {
|
||||||
const wrapper = shallow(
|
console.log("New values:", newValues);
|
||||||
<SmartUiComponent descriptor={exampleData} currentValues={new Map()} onInputChange={undefined} />
|
};
|
||||||
);
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
it("should render", () => {
|
||||||
|
const wrapper = shallow(<SmartUiComponent descriptor={exampleData} onChange={exampleCallbacks} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
|
|||||||
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
|
||||||
import { TextField } from "office-ui-fabric-react/lib/TextField";
|
import { TextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
import { Text } from "office-ui-fabric-react/lib/Text";
|
import { Text } from "office-ui-fabric-react/lib/Text";
|
||||||
|
import { InputType } from "../../Tables/Constants";
|
||||||
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
|
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
|
||||||
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
|
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
|
||||||
import { Link, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
import { Link, MessageBar, MessageBarType } from "office-ui-fabric-react";
|
||||||
|
|
||||||
import * as InputUtils from "./InputUtils";
|
import * as InputUtils from "./InputUtils";
|
||||||
import "./SmartUiComponent.less";
|
import "./SmartUiComponent.less";
|
||||||
|
|
||||||
@@ -19,16 +21,45 @@ import "./SmartUiComponent.less";
|
|||||||
* - a descriptor of the UX.
|
* - a descriptor of the UX.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type InputTypeValue = "number" | "string" | "boolean" | "object";
|
export type InputTypeValue = "number" | "string" | "boolean" | "enum";
|
||||||
|
|
||||||
export enum UiType {
|
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||||
Spinner = "Spinner",
|
export type EnumItem = { label: string; key: string; value: any };
|
||||||
Slider = "Slider",
|
|
||||||
|
export type InputType = number | string | boolean | EnumItem;
|
||||||
|
|
||||||
|
interface BaseInput {
|
||||||
|
label: string;
|
||||||
|
dataFieldName: string;
|
||||||
|
type: InputTypeValue;
|
||||||
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChoiceItem = { label: string; key: string };
|
/**
|
||||||
|
* For now, this only supports integers
|
||||||
|
*/
|
||||||
|
export interface NumberInput extends BaseInput {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step: number;
|
||||||
|
defaultValue: number;
|
||||||
|
inputType: "spin" | "slider";
|
||||||
|
}
|
||||||
|
|
||||||
export type InputType = number | string | boolean | ChoiceItem;
|
export interface BooleanInput extends BaseInput {
|
||||||
|
trueLabel: string;
|
||||||
|
falseLabel: string;
|
||||||
|
defaultValue: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringInput extends BaseInput {
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnumInput extends BaseInput {
|
||||||
|
choices: EnumItem[];
|
||||||
|
defaultKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Info {
|
export interface Info {
|
||||||
message: string;
|
message: string;
|
||||||
@@ -38,62 +69,28 @@ export interface Info {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseInput {
|
export type AnyInput = NumberInput | BooleanInput | StringInput | EnumInput;
|
||||||
label: string;
|
|
||||||
dataFieldName: string;
|
|
||||||
type: InputTypeValue;
|
|
||||||
placeholder?: string;
|
|
||||||
errorMessage?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
export interface Node {
|
||||||
* For now, this only supports integers
|
|
||||||
*/
|
|
||||||
interface NumberInput extends BaseInput {
|
|
||||||
min: number;
|
|
||||||
max: number;
|
|
||||||
step: number;
|
|
||||||
defaultValue?: number;
|
|
||||||
uiType: UiType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BooleanInput extends BaseInput {
|
|
||||||
trueLabel: string;
|
|
||||||
falseLabel: string;
|
|
||||||
defaultValue?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StringInput extends BaseInput {
|
|
||||||
defaultValue?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChoiceInput extends BaseInput {
|
|
||||||
choices: ChoiceItem[];
|
|
||||||
defaultKey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnyInput = NumberInput | BooleanInput | StringInput | ChoiceInput;
|
|
||||||
|
|
||||||
interface Node {
|
|
||||||
id: string;
|
id: string;
|
||||||
info?: Info;
|
info?: Info;
|
||||||
input?: AnyInput;
|
input?: AnyInput;
|
||||||
children?: Node[];
|
children?: Node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SmartUiDescriptor {
|
export interface Descriptor {
|
||||||
root: Node;
|
root: Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** Component implementation starts here ************************************* */
|
/************************** Component implementation starts here ************************************* */
|
||||||
|
|
||||||
export interface SmartUiComponentProps {
|
export interface SmartUiComponentProps {
|
||||||
descriptor: SmartUiDescriptor;
|
descriptor: Descriptor;
|
||||||
currentValues: Map<string, InputType>;
|
onChange: (newValues: Map<string, InputType>) => void;
|
||||||
onInputChange: (input: AnyInput, newValue: InputType) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SmartUiComponentState {
|
interface SmartUiComponentState {
|
||||||
|
currentValues: Map<string, InputType>;
|
||||||
errors: Map<string, string>;
|
errors: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +104,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
constructor(props: SmartUiComponentProps) {
|
constructor(props: SmartUiComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
currentValues: new Map(),
|
||||||
errors: new Map(),
|
errors: new Map(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -115,37 +113,42 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return (
|
return (
|
||||||
<MessageBar>
|
<MessageBar>
|
||||||
{info.message}
|
{info.message}
|
||||||
{info.link && (
|
<Link href={info.link.href} target="_blank">
|
||||||
<Link href={info.link.href} target="_blank">
|
{info.link.text}
|
||||||
{info.link.text}
|
</Link>
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</MessageBar>
|
</MessageBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTextInput(input: StringInput): JSX.Element {
|
private onInputChange = (newValue: string | number | boolean, dataFieldName: string) => {
|
||||||
const value = this.props.currentValues.get(input.dataFieldName) as string;
|
const { currentValues } = this.state;
|
||||||
|
currentValues.set(dataFieldName, newValue);
|
||||||
|
this.setState({ currentValues }, () => this.props.onChange(this.state.currentValues));
|
||||||
|
};
|
||||||
|
|
||||||
|
private renderStringInput(input: StringInput): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="stringInputContainer">
|
<div className="stringInputContainer">
|
||||||
<TextField
|
<div>
|
||||||
id={`${input.dataFieldName}-textBox-input`}
|
<TextField
|
||||||
label={input.label}
|
id={`${input.dataFieldName}-input`}
|
||||||
type="text"
|
label={input.label}
|
||||||
value={value}
|
type="text"
|
||||||
placeholder={input.placeholder}
|
value={input.defaultValue}
|
||||||
onChange={(_, newValue) => this.props.onInputChange(input, newValue)}
|
placeholder={input.placeholder}
|
||||||
styles={{
|
onChange={(_, newValue) => this.onInputChange(newValue, input.dataFieldName)}
|
||||||
subComponentStyles: {
|
styles={{
|
||||||
label: {
|
subComponentStyles: {
|
||||||
root: {
|
label: {
|
||||||
...SmartUiComponent.labelStyle,
|
root: {
|
||||||
fontWeight: 600,
|
...SmartUiComponent.labelStyle,
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -156,11 +159,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onValidate = (input: AnyInput, value: string, min: number, max: number): string => {
|
private onValidate = (value: string, min: number, max: number, dataFieldName: string): string => {
|
||||||
const newValue = InputUtils.onValidateValueChange(value, min, max);
|
const newValue = InputUtils.onValidateValueChange(value, min, max);
|
||||||
const dataFieldName = input.dataFieldName;
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.props.onInputChange(input, newValue);
|
this.onInputChange(newValue, dataFieldName);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
} else {
|
} else {
|
||||||
@@ -171,22 +173,20 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onIncrement = (input: AnyInput, value: string, step: number, max: number): string => {
|
private onIncrement = (value: string, step: number, max: number, dataFieldName: string): string => {
|
||||||
const newValue = InputUtils.onIncrementValue(value, step, max);
|
const newValue = InputUtils.onIncrementValue(value, step, max);
|
||||||
const dataFieldName = input.dataFieldName;
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.props.onInputChange(input, newValue);
|
this.onInputChange(newValue, dataFieldName);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onDecrement = (input: AnyInput, value: string, step: number, min: number): string => {
|
private onDecrement = (value: string, step: number, min: number, dataFieldName: string): string => {
|
||||||
const newValue = InputUtils.onDecrementValue(value, step, min);
|
const newValue = InputUtils.onDecrementValue(value, step, min);
|
||||||
const dataFieldName = input.dataFieldName;
|
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.props.onInputChange(input, newValue);
|
this.onInputChange(newValue, dataFieldName);
|
||||||
this.clearError(dataFieldName);
|
this.clearError(dataFieldName);
|
||||||
return newValue.toString();
|
return newValue.toString();
|
||||||
}
|
}
|
||||||
@@ -194,26 +194,18 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
};
|
};
|
||||||
|
|
||||||
private renderNumberInput(input: NumberInput): JSX.Element {
|
private renderNumberInput(input: NumberInput): JSX.Element {
|
||||||
const { label, min, max, dataFieldName, step } = input;
|
const { label, min, max, defaultValue, dataFieldName, step } = input;
|
||||||
const props = {
|
const props = { label, min, max, ariaLabel: label, step };
|
||||||
label: label,
|
|
||||||
min: min,
|
|
||||||
max: max,
|
|
||||||
ariaLabel: label,
|
|
||||||
step: step,
|
|
||||||
};
|
|
||||||
|
|
||||||
const value = this.props.currentValues.get(dataFieldName) as number;
|
if (input.inputType === "spin") {
|
||||||
if (input.uiType === UiType.Spinner) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<SpinButton
|
<SpinButton
|
||||||
{...props}
|
{...props}
|
||||||
id={`${input.dataFieldName}-spinner-input`}
|
defaultValue={defaultValue.toString()}
|
||||||
value={value?.toString()}
|
onValidate={(newValue) => this.onValidate(newValue, min, max, dataFieldName)}
|
||||||
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
|
onIncrement={(newValue) => this.onIncrement(newValue, step, max, dataFieldName)}
|
||||||
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
|
onDecrement={(newValue) => this.onDecrement(newValue, step, min, dataFieldName)}
|
||||||
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
|
|
||||||
labelPosition={Position.top}
|
labelPosition={Position.top}
|
||||||
styles={{
|
styles={{
|
||||||
label: {
|
label: {
|
||||||
@@ -225,35 +217,34 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
{this.state.errors.has(dataFieldName) && (
|
{this.state.errors.has(dataFieldName) && (
|
||||||
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (input.uiType === UiType.Slider) {
|
|
||||||
return (
|
|
||||||
<div id={`${input.dataFieldName}-slider-input`}>
|
|
||||||
<Slider
|
|
||||||
{...props}
|
|
||||||
value={value}
|
|
||||||
onChange={(newValue) => this.props.onInputChange(input, newValue)}
|
|
||||||
styles={{
|
|
||||||
titleLabel: {
|
|
||||||
...SmartUiComponent.labelStyle,
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
valueLabel: SmartUiComponent.labelStyle,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (input.inputType === "slider") {
|
||||||
|
return (
|
||||||
|
<Slider
|
||||||
|
// showValue={true}
|
||||||
|
// valueFormat={}
|
||||||
|
{...props}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
onChange={(newValue) => this.onInputChange(newValue, dataFieldName)}
|
||||||
|
styles={{
|
||||||
|
titleLabel: {
|
||||||
|
...SmartUiComponent.labelStyle,
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
valueLabel: SmartUiComponent.labelStyle,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <>Unsupported number UI type {input.uiType}</>;
|
return <>Unsupported number input type {input.inputType}</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderBooleanInput(input: BooleanInput): JSX.Element {
|
private renderBooleanInput(input: BooleanInput): JSX.Element {
|
||||||
const value = this.props.currentValues.get(input.dataFieldName) as boolean;
|
const { dataFieldName } = input;
|
||||||
const selectedKey = value || input.defaultValue ? "true" : "false";
|
|
||||||
return (
|
return (
|
||||||
<div id={`${input.dataFieldName}-radioSwitch-input`}>
|
<div>
|
||||||
<div className="inputLabelContainer">
|
<div className="inputLabelContainer">
|
||||||
<Text variant="small" nowrap className="inputLabel">
|
<Text variant="small" nowrap className="inputLabel">
|
||||||
{input.label}
|
{input.label}
|
||||||
@@ -264,33 +255,43 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
{
|
{
|
||||||
label: input.falseLabel,
|
label: input.falseLabel,
|
||||||
key: "false",
|
key: "false",
|
||||||
onSelect: () => this.props.onInputChange(input, false),
|
onSelect: () => this.onInputChange(false, dataFieldName),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: input.trueLabel,
|
label: input.trueLabel,
|
||||||
key: "true",
|
key: "true",
|
||||||
onSelect: () => this.props.onInputChange(input, true),
|
onSelect: () => this.onInputChange(true, dataFieldName),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
selectedKey={selectedKey}
|
selectedKey={
|
||||||
|
(
|
||||||
|
this.state.currentValues.has(dataFieldName)
|
||||||
|
? (this.state.currentValues.get(dataFieldName) as boolean)
|
||||||
|
: input.defaultValue
|
||||||
|
)
|
||||||
|
? "true"
|
||||||
|
: "false"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderChoiceInput(input: ChoiceInput): JSX.Element {
|
private renderEnumInput(input: EnumInput): JSX.Element {
|
||||||
const { label, defaultKey: defaultKey, dataFieldName, choices, placeholder } = input;
|
const { label, defaultKey, dataFieldName, choices, placeholder } = input;
|
||||||
const value = this.props.currentValues.get(dataFieldName) as string;
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
id={`${input.dataFieldName}-dropown-input`}
|
|
||||||
label={label}
|
label={label}
|
||||||
selectedKey={value ? value : defaultKey}
|
selectedKey={
|
||||||
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
|
this.state.currentValues.has(dataFieldName)
|
||||||
|
? (this.state.currentValues.get(dataFieldName) as string)
|
||||||
|
: defaultKey
|
||||||
|
}
|
||||||
|
onChange={(_, item: IDropdownOption) => this.onInputChange(item.key.toString(), dataFieldName)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
options={choices.map((c) => ({
|
options={choices.map((c) => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
text: c.label,
|
text: c.value,
|
||||||
}))}
|
}))}
|
||||||
styles={{
|
styles={{
|
||||||
label: {
|
label: {
|
||||||
@@ -303,48 +304,34 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderError(input: AnyInput): JSX.Element {
|
|
||||||
return <MessageBar messageBarType={MessageBarType.error}>Error: {input.errorMessage}</MessageBar>;
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderInput(input: AnyInput): JSX.Element {
|
private renderInput(input: AnyInput): JSX.Element {
|
||||||
if (input.errorMessage) {
|
|
||||||
return this.renderError(input);
|
|
||||||
}
|
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case "string":
|
case "string":
|
||||||
return this.renderTextInput(input as StringInput);
|
return this.renderStringInput(input as StringInput);
|
||||||
case "number":
|
case "number":
|
||||||
return this.renderNumberInput(input as NumberInput);
|
return this.renderNumberInput(input as NumberInput);
|
||||||
case "boolean":
|
case "boolean":
|
||||||
return this.renderBooleanInput(input as BooleanInput);
|
return this.renderBooleanInput(input as BooleanInput);
|
||||||
case "object":
|
case "enum":
|
||||||
return this.renderChoiceInput(input as ChoiceInput);
|
return this.renderEnumInput(input as EnumInput);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown input type: ${input.type}`);
|
throw new Error(`Unknown input type: ${input.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderNode(node: Node): JSX.Element {
|
private renderNode(node: Node): JSX.Element {
|
||||||
const containerStackTokens: IStackTokens = { childrenGap: 15 };
|
const containerStackTokens: IStackTokens = { childrenGap: 10 };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
|
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
|
||||||
<Stack.Item>
|
{node.info && this.renderInfo(node.info)}
|
||||||
{node.info && this.renderInfo(node.info as Info)}
|
{node.input && this.renderInput(node.input)}
|
||||||
{node.input && this.renderInput(node.input)}
|
|
||||||
</Stack.Item>
|
|
||||||
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
|
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const containerStackTokens: IStackTokens = { childrenGap: 20 };
|
return <>{this.renderNode(this.props.descriptor.root)}</>;
|
||||||
return (
|
|
||||||
<Stack tokens={containerStackTokens} styles={{ root: { width: 400, padding: 10 } }}>
|
|
||||||
{this.renderNode(this.props.descriptor.root)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,24 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`SmartUiComponent should render 1`] = `
|
exports[`SmartUiComponent should render 1`] = `
|
||||||
<Stack
|
<Fragment>
|
||||||
styles={
|
|
||||||
Object {
|
|
||||||
"root": Object {
|
|
||||||
"padding": 10,
|
|
||||||
"width": 400,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokens={
|
|
||||||
Object {
|
|
||||||
"childrenGap": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Stack
|
<Stack
|
||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<StyledMessageBarBase>
|
||||||
<StyledMessageBarBase>
|
Start at $24/mo per database
|
||||||
Start at $24/mo per database
|
<StyledLinkBase
|
||||||
<StyledLinkBase
|
href="https://aka.ms/azure-cosmos-db-pricing"
|
||||||
href="https://aka.ms/azure-cosmos-db-pricing"
|
target="_blank"
|
||||||
target="_blank"
|
>
|
||||||
>
|
More Details
|
||||||
More Details
|
</StyledLinkBase>
|
||||||
</StyledLinkBase>
|
</StyledMessageBarBase>
|
||||||
</StyledMessageBarBase>
|
|
||||||
</StackItem>
|
|
||||||
<div
|
<div
|
||||||
key="throughput"
|
key="throughput"
|
||||||
>
|
>
|
||||||
@@ -42,11 +26,11 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<div>
|
||||||
<CustomizedSpinButton
|
<CustomizedSpinButton
|
||||||
ariaLabel="Throughput (input)"
|
ariaLabel="Throughput (input)"
|
||||||
decrementButtonIcon={
|
decrementButtonIcon={
|
||||||
@@ -54,8 +38,8 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
"iconName": "ChevronDownSmall",
|
"iconName": "ChevronDownSmall",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
defaultValue="400"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughput-spinner-input"
|
|
||||||
incrementButtonIcon={
|
incrementButtonIcon={
|
||||||
Object {
|
Object {
|
||||||
"iconName": "ChevronUpSmall",
|
"iconName": "ChevronUpSmall",
|
||||||
@@ -80,7 +64,7 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StackItem>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -90,60 +74,34 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<StyledSliderBase
|
||||||
<div
|
ariaLabel="Throughput (Slider)"
|
||||||
id="throughput2-slider-input"
|
defaultValue={400}
|
||||||
>
|
label="Throughput (Slider)"
|
||||||
<StyledSliderBase
|
max={500}
|
||||||
ariaLabel="Throughput (Slider)"
|
min={400}
|
||||||
label="Throughput (Slider)"
|
onChange={[Function]}
|
||||||
max={500}
|
step={10}
|
||||||
min={400}
|
styles={
|
||||||
onChange={[Function]}
|
Object {
|
||||||
step={10}
|
"titleLabel": Object {
|
||||||
styles={
|
"color": "#393939",
|
||||||
Object {
|
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||||
"titleLabel": Object {
|
"fontSize": 12,
|
||||||
"color": "#393939",
|
"fontWeight": 600,
|
||||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
},
|
||||||
"fontSize": 12,
|
"valueLabel": Object {
|
||||||
"fontWeight": 600,
|
"color": "#393939",
|
||||||
},
|
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||||
"valueLabel": Object {
|
"fontSize": 12,
|
||||||
"color": "#393939",
|
},
|
||||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
}
|
||||||
"fontSize": 12,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</StackItem>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key="throughput3"
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
className="widgetRendererContainer"
|
|
||||||
tokens={
|
|
||||||
Object {
|
|
||||||
"childrenGap": 15,
|
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
>
|
|
||||||
<StackItem>
|
|
||||||
<StyledMessageBarBase
|
|
||||||
messageBarType={1}
|
|
||||||
>
|
|
||||||
Error:
|
|
||||||
label, truelabel and falselabel are required for boolean input 'throughput3'
|
|
||||||
</StyledMessageBarBase>
|
|
||||||
</StackItem>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -153,16 +111,16 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<div
|
||||||
<div
|
className="stringInputContainer"
|
||||||
className="stringInputContainer"
|
>
|
||||||
>
|
<div>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
id="containerId-textBox-input"
|
id="containerId-input"
|
||||||
label="Container id"
|
label="Container id"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
styles={
|
styles={
|
||||||
@@ -182,7 +140,7 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</StackItem>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -192,44 +150,40 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<div>
|
||||||
<div
|
<div
|
||||||
id="analyticalStore-radioSwitch-input"
|
className="inputLabelContainer"
|
||||||
>
|
>
|
||||||
<div
|
<Text
|
||||||
className="inputLabelContainer"
|
className="inputLabel"
|
||||||
|
nowrap={true}
|
||||||
|
variant="small"
|
||||||
>
|
>
|
||||||
<Text
|
Analytical Store
|
||||||
className="inputLabel"
|
</Text>
|
||||||
nowrap={true}
|
|
||||||
variant="small"
|
|
||||||
>
|
|
||||||
Analytical Store
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<RadioSwitchComponent
|
|
||||||
choices={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "false",
|
|
||||||
"label": "Disabled",
|
|
||||||
"onSelect": [Function],
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "true",
|
|
||||||
"label": "Enabled",
|
|
||||||
"onSelect": [Function],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
selectedKey="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</StackItem>
|
<RadioSwitchComponent
|
||||||
|
choices={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"key": "false",
|
||||||
|
"label": "Disabled",
|
||||||
|
"onSelect": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "true",
|
||||||
|
"label": "Enabled",
|
||||||
|
"onSelect": [Function],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
selectedKey="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -239,51 +193,48 @@ exports[`SmartUiComponent should render 1`] = `
|
|||||||
className="widgetRendererContainer"
|
className="widgetRendererContainer"
|
||||||
tokens={
|
tokens={
|
||||||
Object {
|
Object {
|
||||||
"childrenGap": 15,
|
"childrenGap": 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackItem>
|
<StyledWithResponsiveMode
|
||||||
<StyledWithResponsiveMode
|
label="Database"
|
||||||
id="database-dropown-input"
|
onChange={[Function]}
|
||||||
label="Database"
|
options={
|
||||||
onChange={[Function]}
|
Array [
|
||||||
options={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"key": "db1",
|
|
||||||
"text": "Database 1",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "db2",
|
|
||||||
"text": "Database 2",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"key": "db3",
|
|
||||||
"text": "Database 3",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
selectedKey="db2"
|
|
||||||
styles={
|
|
||||||
Object {
|
Object {
|
||||||
"dropdown": Object {
|
"key": "db1",
|
||||||
"color": "#393939",
|
"text": "database1",
|
||||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
},
|
||||||
"fontSize": 12,
|
Object {
|
||||||
},
|
"key": "db2",
|
||||||
"label": Object {
|
"text": "database2",
|
||||||
"color": "#393939",
|
},
|
||||||
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
Object {
|
||||||
"fontSize": 12,
|
"key": "db3",
|
||||||
"fontWeight": 600,
|
"text": "database3",
|
||||||
},
|
},
|
||||||
}
|
]
|
||||||
|
}
|
||||||
|
selectedKey="db2"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"dropdown": Object {
|
||||||
|
"color": "#393939",
|
||||||
|
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
"label": Object {
|
||||||
|
"color": "#393939",
|
||||||
|
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontWeight": 600,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
</StackItem>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutos
|
|||||||
import { KeyCodes } from "../../../Common/Constants";
|
import { KeyCodes } from "../../../Common/Constants";
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||||
|
|
||||||
import { userContext } from "../../../UserContext";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
/**
|
/**
|
||||||
* Throughput Input:
|
* Throughput Input:
|
||||||
*
|
*
|
||||||
@@ -132,8 +129,6 @@ export interface ThroughputInputParams {
|
|||||||
showAutoPilot?: ko.Observable<boolean>;
|
showAutoPilot?: ko.Observable<boolean>;
|
||||||
overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
overrideWithAutoPilotSettings: ko.Observable<boolean>;
|
||||||
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
||||||
freeTierExceedThroughputTooltip?: ko.Observable<string>;
|
|
||||||
freeTierExceedThroughputWarning?: ko.Observable<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
||||||
@@ -170,10 +165,6 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|||||||
public overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
public overrideWithProvisionedThroughputSettings: ko.Observable<boolean>;
|
||||||
public isManualThroughputInputFieldRequired: ko.Computed<boolean>;
|
public isManualThroughputInputFieldRequired: ko.Computed<boolean>;
|
||||||
public isAutoscaleThroughputInputFieldRequired: ko.Computed<boolean>;
|
public isAutoscaleThroughputInputFieldRequired: ko.Computed<boolean>;
|
||||||
public freeTierExceedThroughputTooltip: ko.Observable<string>;
|
|
||||||
public freeTierExceedThroughputWarning: ko.Observable<string>;
|
|
||||||
public showFreeTierExceedThroughputTooltip: ko.Computed<boolean>;
|
|
||||||
public showFreeTierExceedThroughputWarning: ko.Computed<boolean>;
|
|
||||||
|
|
||||||
public constructor(options: ThroughputInputParams) {
|
public constructor(options: ThroughputInputParams) {
|
||||||
super();
|
super();
|
||||||
@@ -204,16 +195,6 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|||||||
this.label = options.label || ko.observable<string>();
|
this.label = options.label || ko.observable<string>();
|
||||||
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
|
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
|
||||||
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
|
||||||
this.isAutoPilotSelected.subscribe((value) => {
|
|
||||||
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
|
|
||||||
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
|
|
||||||
databaseAccountName: userContext.databaseAccount?.name,
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
apiKind: userContext.defaultExperience,
|
|
||||||
dataExplorerArea: "Scale Tab V1",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
|
||||||
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
|
||||||
this.throughputModeRadioName = options.throughputModeRadioName;
|
this.throughputModeRadioName = options.throughputModeRadioName;
|
||||||
@@ -238,16 +219,6 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
|
|||||||
this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed(
|
this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed(
|
||||||
() => this.isEnabled() && this.isAutoPilotSelected()
|
() => this.isEnabled() && this.isAutoPilotSelected()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.freeTierExceedThroughputTooltip = options.freeTierExceedThroughputTooltip || ko.observable<string>();
|
|
||||||
this.freeTierExceedThroughputWarning = options.freeTierExceedThroughputWarning || ko.observable<string>();
|
|
||||||
this.showFreeTierExceedThroughputTooltip = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputTooltip() && this.value() > 400
|
|
||||||
);
|
|
||||||
|
|
||||||
this.showFreeTierExceedThroughputWarning = ko.pureComputed<boolean>(
|
|
||||||
() => !!this.freeTierExceedThroughputWarning() && this.value() > 400
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public decreaseThroughput() {
|
public decreaseThroughput() {
|
||||||
|
|||||||
@@ -126,20 +126,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="visible: !isAutoPilotSelected()">
|
<div data-bind="visible: !isAutoPilotSelected()">
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
>Estimate your required throughput with
|
|
||||||
<a target="_blank" href="https://cosmos.azure.com/capacitycalculator/">capacity calculator</a></span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="inputTooltip">
|
|
||||||
<span
|
|
||||||
data-bind="text: freeTierExceedThroughputTooltip, visible: showFreeTierExceedThroughputTooltip"
|
|
||||||
class="inputTooltipText"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-bind="setTemplateReady: true">
|
<div data-bind="setTemplateReady: true">
|
||||||
<input
|
<input
|
||||||
data-bind="
|
data-bind="
|
||||||
@@ -162,11 +148,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="freeTierInlineWarning" data-bind="visible: showFreeTierExceedThroughputWarning">
|
|
||||||
<span class="freeTierWarningIcon"><img src="/warning.svg" alt="Warning" /></span>
|
|
||||||
<span class="freeTierWarningMessage" data-bind="text: freeTierExceedThroughputWarning"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p data-bind="visible: costsVisible">
|
<p data-bind="visible: costsVisible">
|
||||||
<span data-bind="html: requestUnitsUsageCost"></span>
|
<span data-bind="html: requestUnitsUsageCost"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
jest.mock("../../Common/DocumentClientUtilityBase");
|
||||||
jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||||
jest.mock("../../Common/dataAccess/createCollection");
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
jest.mock("../../Common/dataAccess/createDocument");
|
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import GraphTab from ".././Tabs/GraphTab";
|
|||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
@@ -95,15 +95,12 @@ export class ContainerSampleGenerator {
|
|||||||
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
.reduce((previous, current) => previous.then(current), Promise.resolve());
|
||||||
} else {
|
} else {
|
||||||
// For SQL all queries are executed at the same time
|
// For SQL all queries are executed at the same time
|
||||||
await Promise.all(
|
this.sampleDataFile.data.map((doc) => {
|
||||||
this.sampleDataFile.data.map(async (doc) => {
|
const subPromise = createDocument(collection, doc);
|
||||||
try {
|
subPromise.catch((reason) => NotificationConsoleUtils.logConsoleError(reason));
|
||||||
await createDocument(collection, doc);
|
promises.push(subPromise);
|
||||||
} catch (error) {
|
});
|
||||||
NotificationConsoleUtils.logConsoleError(error);
|
await Promise.all(promises);
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPa
|
|||||||
import { readCollection } from "../Common/dataAccess/readCollection";
|
import { readCollection } from "../Common/dataAccess/readCollection";
|
||||||
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
import { readDatabases } from "../Common/dataAccess/readDatabases";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import EnvironmentUtility from "../Common/EnvironmentUtility";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import hasher from "hasher";
|
import hasher from "hasher";
|
||||||
import NewVertexPane from "./Panes/NewVertexPane";
|
import NewVertexPane from "./Panes/NewVertexPane";
|
||||||
@@ -88,10 +88,6 @@ import { stringToBlob } from "../Utils/BlobUtils";
|
|||||||
import { IChoiceGroupProps } from "office-ui-fabric-react";
|
import { IChoiceGroupProps } from "office-ui-fabric-react";
|
||||||
import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError, getErrorStack } from "../Common/ErrorHandlingUtils";
|
||||||
import { SubscriptionType } from "../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../Contracts/SubscriptionType";
|
||||||
import { appInsights } from "../Shared/appInsights";
|
|
||||||
import { SelfServeLoadingComponentAdapter } from "../SelfServe/SelfServeLoadingComponentAdapter";
|
|
||||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
|
||||||
import { SelfServeComponentAdapter } from "../SelfServe/SelfServeComponentAdapter";
|
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
@@ -125,6 +121,7 @@ export default class Explorer {
|
|||||||
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
public databaseAccount: ko.Observable<DataModels.DatabaseAccount>;
|
||||||
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = SharedConstants.CollectionCreationDefaults;
|
||||||
public subscriptionType: ko.Observable<SubscriptionType>;
|
public subscriptionType: ko.Observable<SubscriptionType>;
|
||||||
|
public quotaId: ko.Observable<string>;
|
||||||
public defaultExperience: ko.Observable<string>;
|
public defaultExperience: ko.Observable<string>;
|
||||||
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
||||||
public isPreferredApiCassandra: ko.Computed<boolean>;
|
public isPreferredApiCassandra: ko.Computed<boolean>;
|
||||||
@@ -135,14 +132,15 @@ export default class Explorer {
|
|||||||
public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
|
public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
|
||||||
public isServerlessEnabled: ko.Computed<boolean>;
|
public isServerlessEnabled: ko.Computed<boolean>;
|
||||||
public isAccountReady: ko.Observable<boolean>;
|
public isAccountReady: ko.Observable<boolean>;
|
||||||
public selfServeType: ko.Observable<SelfServeType>;
|
|
||||||
public canSaveQueries: ko.Computed<boolean>;
|
public canSaveQueries: ko.Computed<boolean>;
|
||||||
public features: ko.Observable<any>;
|
public features: ko.Observable<any>;
|
||||||
public serverId: ko.Observable<string>;
|
public serverId: ko.Observable<string>;
|
||||||
|
public armEndpoint: ko.Observable<string>;
|
||||||
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
public isTryCosmosDBSubscription: ko.Observable<boolean>;
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
public splitter: Splitter;
|
public splitter: Splitter;
|
||||||
|
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>("");
|
||||||
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
public mostRecentActivity: MostRecentActivity.MostRecentActivity;
|
||||||
|
|
||||||
// Notification Console
|
// Notification Console
|
||||||
@@ -161,7 +159,6 @@ export default class Explorer {
|
|||||||
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
||||||
public isRefreshingExplorer: ko.Observable<boolean>;
|
public isRefreshingExplorer: ko.Observable<boolean>;
|
||||||
private resourceTree: ResourceTreeAdapter;
|
private resourceTree: ResourceTreeAdapter;
|
||||||
private selfServeComponentAdapter: SelfServeComponentAdapter;
|
|
||||||
|
|
||||||
// Resource Token
|
// Resource Token
|
||||||
public resourceTokenDatabaseId: ko.Observable<string>;
|
public resourceTokenDatabaseId: ko.Observable<string>;
|
||||||
@@ -207,15 +204,14 @@ export default class Explorer {
|
|||||||
|
|
||||||
// features
|
// features
|
||||||
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
public isGalleryPublishEnabled: ko.Computed<boolean>;
|
||||||
|
public isCodeOfConductEnabled: ko.Computed<boolean>;
|
||||||
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
public isLinkInjectionEnabled: ko.Computed<boolean>;
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||||
public isMongoIndexingEnabled: ko.Observable<boolean>;
|
|
||||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||||
public isAutoscaleDefaultEnabled: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||||
public shareAccessData: ko.Observable<AdHocAccessData>;
|
public shareAccessData: ko.Observable<AdHocAccessData>;
|
||||||
@@ -265,7 +261,6 @@ export default class Explorer {
|
|||||||
private _dialogProps: ko.Observable<DialogProps>;
|
private _dialogProps: ko.Observable<DialogProps>;
|
||||||
private addSynapseLinkDialog: DialogComponentAdapter;
|
private addSynapseLinkDialog: DialogComponentAdapter;
|
||||||
private _addSynapseLinkDialogProps: ko.Observable<DialogProps>;
|
private _addSynapseLinkDialogProps: ko.Observable<DialogProps>;
|
||||||
private selfServeLoadingComponentAdapter: SelfServeLoadingComponentAdapter;
|
|
||||||
|
|
||||||
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
private static readonly MaxNbDatabasesToAutoExpand = 5;
|
||||||
|
|
||||||
@@ -284,6 +279,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
this.databaseAccount = ko.observable<DataModels.DatabaseAccount>();
|
||||||
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
this.subscriptionType = ko.observable<SubscriptionType>(SharedConstants.CollectionCreation.DefaultSubscriptionType);
|
||||||
|
this.quotaId = ko.observable<string>("");
|
||||||
let firstInitialization = true;
|
let firstInitialization = true;
|
||||||
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
this.isRefreshingExplorer = ko.observable<boolean>(true);
|
||||||
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
this.isRefreshingExplorer.subscribe((isRefreshing: boolean) => {
|
||||||
@@ -301,7 +297,6 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.isAccountReady = ko.observable<boolean>(false);
|
this.isAccountReady = ko.observable<boolean>(false);
|
||||||
this.selfServeType = ko.observable<SelfServeType>(undefined);
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
this._isInitializingSparkConnectionInfo = false;
|
this._isInitializingSparkConnectionInfo = false;
|
||||||
this.arcadiaToken = ko.observable<string>();
|
this.arcadiaToken = ko.observable<string>();
|
||||||
@@ -324,9 +319,9 @@ export default class Explorer {
|
|||||||
if (isAccountReady) {
|
if (isAccountReady) {
|
||||||
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
RouteHandler.getInstance().initHandler();
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
|
||||||
this.arcadiaWorkspaces = ko.observableArray();
|
this.arcadiaWorkspaces = ko.observableArray();
|
||||||
this._arcadiaManager = new ArcadiaResourceManager();
|
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
|
||||||
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then((isRegistered) =>
|
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then((isRegistered) =>
|
||||||
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
this.hasStorageAnalyticsAfecFeature(isRegistered)
|
||||||
);
|
);
|
||||||
@@ -362,15 +357,6 @@ export default class Explorer {
|
|||||||
this.isFeatureEnabled(Constants.Features.enableSpark)
|
this.isFeatureEnabled(Constants.Features.enableSpark)
|
||||||
);
|
);
|
||||||
if (this.isSparkEnabled()) {
|
if (this.isSparkEnabled()) {
|
||||||
appInsights.trackEvent(
|
|
||||||
{ name: "LoadedWithSparkEnabled" },
|
|
||||||
{
|
|
||||||
subscriptionId: userContext.subscriptionId,
|
|
||||||
accountName: userContext.databaseAccount?.name,
|
|
||||||
accountId: userContext.databaseAccount?.id,
|
|
||||||
platform: configContext.platform,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const pollArcadiaTokenRefresh = async () => {
|
const pollArcadiaTokenRefresh = async () => {
|
||||||
this.arcadiaToken(await this.getArcadiaToken());
|
this.arcadiaToken(await this.getArcadiaToken());
|
||||||
setTimeout(() => pollArcadiaTokenRefresh(), this.getTokenRefreshInterval(this.arcadiaToken()));
|
setTimeout(() => pollArcadiaTokenRefresh(), this.getTokenRefreshInterval(this.arcadiaToken()));
|
||||||
@@ -385,6 +371,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.features = ko.observable();
|
this.features = ko.observable();
|
||||||
this.serverId = ko.observable<string>();
|
this.serverId = ko.observable<string>();
|
||||||
|
this.armEndpoint = ko.observable<string>(undefined);
|
||||||
this.queriesClient = new QueriesClient(this);
|
this.queriesClient = new QueriesClient(this);
|
||||||
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
||||||
|
|
||||||
@@ -417,11 +404,13 @@ export default class Explorer {
|
|||||||
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
|
||||||
);
|
);
|
||||||
|
this.isCodeOfConductEnabled = ko.computed<boolean>(() =>
|
||||||
|
this.isFeatureEnabled(Constants.Features.enableCodeOfConduct)
|
||||||
|
);
|
||||||
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
|
||||||
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
|
||||||
);
|
);
|
||||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
|
|
||||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
|
|
||||||
@@ -432,8 +421,6 @@ export default class Explorer {
|
|||||||
this.isSchemaEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSchema));
|
this.isSchemaEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSchema));
|
||||||
this.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
this.isNotificationConsoleExpanded = ko.observable<boolean>(false);
|
||||||
|
|
||||||
this.isAutoscaleDefaultEnabled = ko.observable<boolean>(false);
|
|
||||||
|
|
||||||
this.databases = ko.observableArray<ViewModels.Database>();
|
this.databases = ko.observableArray<ViewModels.Database>();
|
||||||
this.canSaveQueries = ko.computed<boolean>(() => {
|
this.canSaveQueries = ko.computed<boolean>(() => {
|
||||||
const savedQueriesDatabase: ViewModels.Database = _.find(
|
const savedQueriesDatabase: ViewModels.Database = _.find(
|
||||||
@@ -715,7 +702,6 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this);
|
this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this);
|
||||||
this.selfServeComponentAdapter = new SelfServeComponentAdapter(this);
|
|
||||||
|
|
||||||
this.loadQueryPane = new LoadQueryPane({
|
this.loadQueryPane = new LoadQueryPane({
|
||||||
id: "loadquerypane",
|
id: "loadquerypane",
|
||||||
@@ -891,7 +877,6 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);
|
this.commandBarComponentAdapter = new CommandBarComponentAdapter(this);
|
||||||
this.selfServeLoadingComponentAdapter = new SelfServeLoadingComponentAdapter();
|
|
||||||
this.notificationConsoleComponentAdapter = new NotificationConsoleComponentAdapter(this);
|
this.notificationConsoleComponentAdapter = new NotificationConsoleComponentAdapter(this);
|
||||||
|
|
||||||
this._initSettings();
|
this._initSettings();
|
||||||
@@ -1031,7 +1016,9 @@ export default class Explorer {
|
|||||||
this.isSynapseLinkUpdating(true);
|
this.isSynapseLinkUpdating(true);
|
||||||
this._closeSynapseLinkModalDialog();
|
this._closeSynapseLinkModalDialog();
|
||||||
|
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(this.databaseAccount().id);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(
|
||||||
|
this.databaseAccount().id
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
const databaseAccount: DataModels.DatabaseAccount = await resourceProviderClient.patchAsync(
|
||||||
@@ -1768,59 +1755,61 @@ export default class Explorer {
|
|||||||
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
inputs.extensionEndpoint = configContext.PROXY_PATH;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initDataExplorerWithFrameInputs(inputs);
|
const initPromise: Q.Promise<void> = inputs ? this.initDataExplorerWithFrameInputs(inputs) : Q();
|
||||||
|
|
||||||
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
initPromise.then(() => {
|
||||||
if (!!openAction) {
|
const openAction: ActionContracts.DataExplorerAction = message.openAction;
|
||||||
if (this.isRefreshingExplorer()) {
|
if (!!openAction) {
|
||||||
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
if (this.isRefreshingExplorer()) {
|
||||||
|
const subscription = this.databases.subscribe((databases: ViewModels.Database[]) => {
|
||||||
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
|
subscription.dispose();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
||||||
subscription.dispose();
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
handleOpenAction(openAction, this.nonSystemDatabases(), this);
|
|
||||||
}
|
}
|
||||||
}
|
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
||||||
if (message.actionType === ActionContracts.ActionType.TransmitCachedData) {
|
handleCachedDataMessage(message);
|
||||||
handleCachedDataMessage(message);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (message.type) {
|
||||||
if (message.type) {
|
switch (message.type) {
|
||||||
switch (message.type) {
|
case MessageTypes.UpdateLocationHash:
|
||||||
case MessageTypes.UpdateLocationHash:
|
if (!message.locationHash) {
|
||||||
if (!message.locationHash) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
hasher.replaceHash(message.locationHash);
|
||||||
hasher.replaceHash(message.locationHash);
|
RouteHandler.getInstance().parseHash(message.locationHash);
|
||||||
RouteHandler.getInstance().parseHash(message.locationHash);
|
break;
|
||||||
break;
|
case MessageTypes.SendNotification:
|
||||||
case MessageTypes.SendNotification:
|
if (!message.message) {
|
||||||
if (!message.message) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
NotificationConsoleUtils.logConsoleMessage(
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
message.consoleDataType || ConsoleDataType.Info,
|
||||||
message.consoleDataType || ConsoleDataType.Info,
|
message.message,
|
||||||
message.message,
|
message.id
|
||||||
message.id
|
);
|
||||||
);
|
break;
|
||||||
break;
|
case MessageTypes.ClearNotification:
|
||||||
case MessageTypes.ClearNotification:
|
if (!message.id) {
|
||||||
if (!message.id) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(message.id);
|
break;
|
||||||
break;
|
case MessageTypes.LoadingStatus:
|
||||||
case MessageTypes.LoadingStatus:
|
if (!message.text) {
|
||||||
if (!message.text) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
this._setLoadingStatusText(message.text, message.title);
|
||||||
this._setLoadingStatusText(message.text, message.title);
|
break;
|
||||||
break;
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.splashScreenAdapter.forceRender();
|
this.splashScreenAdapter.forceRender();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedDatabase(): ViewModels.Database {
|
public findSelectedDatabase(): ViewModels.Database {
|
||||||
@@ -1860,28 +1849,8 @@ export default class Explorer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSelfServeType(inputs: ViewModels.DataExplorerInputsFrame): void {
|
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
|
||||||
const selfServeFeature = inputs.features[Constants.Features.selfServeType];
|
|
||||||
if (selfServeFeature) {
|
|
||||||
// self serve type received from query string
|
|
||||||
const selfServeType = SelfServeType[selfServeFeature?.toLowerCase() as keyof typeof SelfServeType];
|
|
||||||
this.selfServeType(selfServeType ? selfServeType : SelfServeType.invalid);
|
|
||||||
} else if (inputs.selfServeType) {
|
|
||||||
// self serve type received from portal
|
|
||||||
this.selfServeType(inputs.selfServeType);
|
|
||||||
} else {
|
|
||||||
this.selfServeType(SelfServeType.none);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
|
|
||||||
if (inputs != null) {
|
if (inputs != null) {
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
|
||||||
// This allows webpack hot reload to funciton properly
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorizationToken = inputs.authorizationToken || "";
|
const authorizationToken = inputs.authorizationToken || "";
|
||||||
const masterKey = inputs.masterKey || "";
|
const masterKey = inputs.masterKey || "";
|
||||||
const databaseAccount = inputs.databaseAccount || null;
|
const databaseAccount = inputs.databaseAccount || null;
|
||||||
@@ -1890,19 +1859,25 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
this.features(inputs.features);
|
this.features(inputs.features);
|
||||||
this.serverId(inputs.serverId);
|
this.serverId(inputs.serverId);
|
||||||
|
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
this.subscriptionType(inputs.subscriptionType);
|
this.subscriptionType(inputs.subscriptionType);
|
||||||
|
this.quotaId(inputs.quotaId);
|
||||||
this.hasWriteAccess(inputs.hasWriteAccess);
|
this.hasWriteAccess(inputs.hasWriteAccess);
|
||||||
this.flight(inputs.addCollectionDefaultFlight);
|
this.flight(inputs.addCollectionDefaultFlight);
|
||||||
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
|
||||||
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
|
||||||
this.setFeatureFlagsFromFlights(inputs.flights);
|
this.setFeatureFlagsFromFlights(inputs.flights);
|
||||||
this.setSelfServeType(inputs);
|
|
||||||
|
if (!!inputs.dataExplorerVersion) {
|
||||||
|
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
|
||||||
|
}
|
||||||
|
|
||||||
this._importExplorerConfigComplete = true;
|
this._importExplorerConfigComplete = true;
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
|
||||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
ARM_ENDPOINT: this.armEndpoint(),
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -1912,7 +1887,6 @@ export default class Explorer {
|
|||||||
resourceGroup: inputs.resourceGroup,
|
resourceGroup: inputs.resourceGroup,
|
||||||
subscriptionId: inputs.subscriptionId,
|
subscriptionId: inputs.subscriptionId,
|
||||||
subscriptionType: inputs.subscriptionType,
|
subscriptionType: inputs.subscriptionType,
|
||||||
quotaId: inputs.quotaId,
|
|
||||||
});
|
});
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.LoadDatabaseAccount,
|
Action.LoadDatabaseAccount,
|
||||||
@@ -1926,18 +1900,13 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.isAccountReady(true);
|
this.isAccountReady(true);
|
||||||
}
|
}
|
||||||
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
public setFeatureFlagsFromFlights(flights: readonly string[]): void {
|
||||||
if (!flights) {
|
if (!flights) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (flights.indexOf(Constants.Flights.AutoscaleTest) !== -1) {
|
|
||||||
this.isAutoscaleDefaultEnabled(true);
|
|
||||||
}
|
|
||||||
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
|
|
||||||
this.isMongoIndexingEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public findSelectedCollection(): ViewModels.Collection {
|
public findSelectedCollection(): ViewModels.Collection {
|
||||||
@@ -2304,6 +2273,7 @@ export default class Explorer {
|
|||||||
name,
|
name,
|
||||||
content,
|
content,
|
||||||
parentDomElement,
|
parentDomElement,
|
||||||
|
this.isCodeOfConductEnabled(),
|
||||||
this.isLinkInjectionEnabled()
|
this.isLinkInjectionEnabled()
|
||||||
);
|
);
|
||||||
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
this.publishNotebookPaneAdapter = this.notebookManager.publishNotebookPaneAdapter;
|
||||||
@@ -2594,7 +2564,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
public _refreshSparkEnabledStateForAccount = async (): Promise<void> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = configContext.ARM_ENDPOINT;
|
const armEndpoint = this.armEndpoint();
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2603,7 +2573,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${Constants.AfecFeatures.Spark}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const sparkNotebooksFeature: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -2623,7 +2593,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
public _isAfecFeatureRegistered = async (featureName: string): Promise<boolean> => {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const armEndpoint = configContext.ARM_ENDPOINT;
|
const armEndpoint = this.armEndpoint();
|
||||||
const authType = window.authType as AuthType;
|
const authType = window.authType as AuthType;
|
||||||
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
if (!featureName || !subscriptionId || !armEndpoint || authType === AuthType.EncryptedToken) {
|
||||||
// explorer is not aware of the database account yet
|
// explorer is not aware of the database account yet
|
||||||
@@ -2631,7 +2601,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
const featureUri = `subscriptions/${subscriptionId}/providers/Microsoft.Features/providers/Microsoft.DocumentDb/features/${featureName}`;
|
||||||
const resourceProviderClient = new ResourceProviderClientFactory().getOrCreate(featureUri);
|
const resourceProviderClient = new ResourceProviderClientFactory(this.armEndpoint()).getOrCreate(featureUri);
|
||||||
try {
|
try {
|
||||||
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
const featureStatus: DataModels.AfecFeature = await resourceProviderClient.getAsync(
|
||||||
featureUri,
|
featureUri,
|
||||||
@@ -3056,25 +3026,4 @@ export default class Explorer {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFirstResourceCreated(): boolean {
|
|
||||||
const databases: ViewModels.Database[] = this.databases();
|
|
||||||
|
|
||||||
if (!databases || databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases.some((database) => {
|
|
||||||
// user has created at least one collection
|
|
||||||
if (database.collections()?.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// user has created a database with shared throughput
|
|
||||||
if (database.offer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// use has created an empty database without shared throughput
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ export class ArraysByKeyCache<T> {
|
|||||||
|
|
||||||
public constructor(maxNbElements: number) {
|
public constructor(maxNbElements: number) {
|
||||||
this.maxNbElements = maxNbElements;
|
this.maxNbElements = maxNbElements;
|
||||||
this.keyQueue = [];
|
this.clear();
|
||||||
this.cache = {};
|
|
||||||
this.totalElements = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
@@ -60,7 +58,7 @@ export class ArraysByKeyCache<T> {
|
|||||||
* @param startIndex
|
* @param startIndex
|
||||||
* @param pageSize
|
* @param pageSize
|
||||||
*/
|
*/
|
||||||
public retrieve(key: string, startIndex: number, pageSize: number): T[] | null {
|
public retrieve(key: string, startIndex: number, pageSize: number): T[] {
|
||||||
if (!this.cache.hasOwnProperty(key)) {
|
if (!this.cache.hasOwnProperty(key)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -79,10 +77,8 @@ export class ArraysByKeyCache<T> {
|
|||||||
private reduceCacheSize(): void {
|
private reduceCacheSize(): void {
|
||||||
// remove an key and its array
|
// remove an key and its array
|
||||||
const oldKey = this.keyQueue.shift();
|
const oldKey = this.keyQueue.shift();
|
||||||
if (oldKey) {
|
this.totalElements -= this.cache[oldKey].length;
|
||||||
this.totalElements -= this.cache[oldKey].length;
|
delete this.cache[oldKey];
|
||||||
delete this.cache[oldKey];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -413,13 +413,13 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
|
|||||||
* @param node
|
* @param node
|
||||||
* @param prop
|
* @param prop
|
||||||
*/
|
*/
|
||||||
public static getNodePropValue(node: D3Node, prop: string): undefined | string | number | boolean {
|
public static getNodePropValue(node: D3Node, prop: string): string | number | boolean {
|
||||||
if (node.hasOwnProperty(prop)) {
|
if (node.hasOwnProperty(prop)) {
|
||||||
return (node as any)[prop];
|
return (node as any)[prop];
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is DocDB specific
|
// This is DocDB specific
|
||||||
if (node.properties && node.properties.hasOwnProperty(prop)) {
|
if (node.hasOwnProperty("properties") && node.properties.hasOwnProperty(prop)) {
|
||||||
return node.properties[prop][0]["value"];
|
return node.properties[prop][0]["value"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
|
|||||||
* Get list of children ids of a given vertex
|
* Get list of children ids of a given vertex
|
||||||
* @param vertex
|
* @param vertex
|
||||||
*/
|
*/
|
||||||
public static getChildrenId(vertex: GremlinVertex): string[] {
|
private static getChildrenId(vertex: GremlinVertex): string[] {
|
||||||
const ids = <any>{}; // HashSet
|
const ids = <any>{}; // HashSet
|
||||||
if (vertex.hasOwnProperty("outE")) {
|
if (vertex.hasOwnProperty("outE")) {
|
||||||
let outE = vertex.outE;
|
let outE = vertex.outE;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
jest.mock("../../../Common/dataAccess/queryDocuments");
|
jest.mock("../../../Common/DocumentClientUtilityBase");
|
||||||
jest.mock("../../../Common/dataAccess/queryDocumentsPage");
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import { mount, ReactWrapper } from "enzyme";
|
import { mount, ReactWrapper } from "enzyme";
|
||||||
@@ -13,8 +12,7 @@ import * as DataModels from "../../../Contracts/DataModels";
|
|||||||
import * as StorageUtility from "../../../Shared/StorageUtility";
|
import * as StorageUtility from "../../../Shared/StorageUtility";
|
||||||
import GraphTab from "../../Tabs/GraphTab";
|
import GraphTab from "../../Tabs/GraphTab";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
||||||
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
|
||||||
|
|
||||||
describe("Check whether query result is vertex array", () => {
|
describe("Check whether query result is vertex array", () => {
|
||||||
it("should reject null as vertex array", () => {
|
it("should reject null as vertex array", () => {
|
||||||
@@ -301,12 +299,12 @@ describe("GraphExplorer", () => {
|
|||||||
ignoreD3Update: boolean
|
ignoreD3Update: boolean
|
||||||
): GraphExplorer => {
|
): GraphExplorer => {
|
||||||
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
(queryDocuments as jest.Mock).mockImplementation((container: any, query: string, options: any) => {
|
||||||
return {
|
return Q.resolve({
|
||||||
_query: query,
|
_query: query,
|
||||||
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
|
||||||
hasMoreResults: () => false,
|
hasMoreResults: () => false,
|
||||||
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {},
|
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {},
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
(queryDocumentsPage as jest.Mock).mockImplementation(
|
(queryDocumentsPage as jest.Mock).mockImplementation(
|
||||||
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
(rid: string, iterator: any, firstItemIndex: number, options: any) => {
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ import * as Constants from "../../../Common/Constants";
|
|||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
|
||||||
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { FeedOptions } from "@azure/cosmos";
|
|
||||||
|
|
||||||
export interface GraphAccessor {
|
export interface GraphAccessor {
|
||||||
applyFilter: () => void;
|
applyFilter: () => void;
|
||||||
@@ -727,32 +725,26 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Execute DocDB query and get all results
|
* Execute DocDB query and get all results
|
||||||
*/
|
*/
|
||||||
public async executeNonPagedDocDbQuery(query: string): Promise<DataModels.DocumentId[]> {
|
public executeNonPagedDocDbQuery(query: string): Q.Promise<DataModels.DocumentId[]> {
|
||||||
try {
|
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
||||||
// TODO maxItemCount: this reduces throttling, but won't cap the # of results
|
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
||||||
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
maxItemCount: GraphExplorer.PAGE_ALL,
|
||||||
this.props.databaseId,
|
enableCrossPartitionQuery:
|
||||||
this.props.collectionId,
|
StorageUtility.LocalStorageUtility.getEntryString(StorageUtility.StorageKey.IsCrossPartitionQueryEnabled) ===
|
||||||
query,
|
"true",
|
||||||
{
|
}).then(
|
||||||
maxItemCount: GraphExplorer.PAGE_ALL,
|
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
||||||
enableCrossPartitionQuery:
|
return iterator.fetchNext().then((response) => response.resources);
|
||||||
StorageUtility.LocalStorageUtility.getEntryString(
|
},
|
||||||
StorageUtility.StorageKey.IsCrossPartitionQueryEnabled
|
(reason: any) => {
|
||||||
) === "true",
|
GraphExplorer.reportToConsole(
|
||||||
} as FeedOptions
|
ConsoleDataType.Error,
|
||||||
);
|
`Failed to execute non-paged query ${query}. Reason:${reason}`,
|
||||||
const response = await iterator.fetchNext();
|
reason
|
||||||
|
);
|
||||||
return response?.resources;
|
return null;
|
||||||
} catch (error) {
|
}
|
||||||
GraphExplorer.reportToConsole(
|
);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to execute non-paged query ${query}. Reason:${error}`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -872,7 +864,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* User executes query
|
* User executes query
|
||||||
*/
|
*/
|
||||||
public async submitQuery(query: string): Promise<void> {
|
public submitQuery(query: string): void {
|
||||||
// Clear any progress indicator
|
// Clear any progress indicator
|
||||||
this.executeCounter = 0;
|
this.executeCounter = 0;
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -890,22 +882,24 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
// Remember query
|
// Remember query
|
||||||
this.pushToLatestQueryFragments(query);
|
this.pushToLatestQueryFragments(query);
|
||||||
|
|
||||||
try {
|
let backendPromise;
|
||||||
let result: UserQueryResult;
|
|
||||||
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
|
||||||
result = await this.executeDocDbGVQuery();
|
|
||||||
} else {
|
|
||||||
result = await this.executeGremlinQuery(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queryTotalRequestCharge = result.requestCharge;
|
if (query.toLocaleLowerCase() === "g.V()".toLocaleLowerCase()) {
|
||||||
} catch (error) {
|
backendPromise = this.executeDocDbGVQuery();
|
||||||
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
} else {
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
backendPromise = this.executeGremlinQuery(query);
|
||||||
this.setState({
|
|
||||||
filterQueryError: errorMsg,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backendPromise.then(
|
||||||
|
(result: UserQueryResult) => (this.queryTotalRequestCharge = result.requestCharge),
|
||||||
|
(error: any) => {
|
||||||
|
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
|
||||||
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
|
this.setState({
|
||||||
|
filterQueryError: errorMsg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1396,7 +1390,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
/**
|
/**
|
||||||
* Update possible vertices to display in UI
|
* Update possible vertices to display in UI
|
||||||
*/
|
*/
|
||||||
private updatePossibleVertices(): Promise<PossibleVertex[]> {
|
private updatePossibleVertices(): Q.Promise<PossibleVertex[]> {
|
||||||
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
|
||||||
|
|
||||||
const q = `SELECT c.id, c["${
|
const q = `SELECT c.id, c["${
|
||||||
@@ -1728,82 +1722,86 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeDocDbGVQuery(): Promise<UserQueryResult> {
|
private executeDocDbGVQuery(): Q.Promise<UserQueryResult> {
|
||||||
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
let query = "select root.id from root where IS_DEFINED(root._isEdge) = false order by root._ts desc";
|
||||||
if (this.props.collectionPartitionKeyProperty) {
|
if (this.props.collectionPartitionKeyProperty) {
|
||||||
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
query = `select root.id, root.${this.props.collectionPartitionKeyProperty} from root where IS_DEFINED(root._isEdge) = false order by root._ts asc`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return queryDocuments(this.props.databaseId, this.props.collectionId, query, {
|
||||||
const iterator: QueryIterator<ItemDefinition & Resource> = queryDocuments(
|
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
||||||
this.props.databaseId,
|
enableCrossPartitionQuery: LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true",
|
||||||
this.props.collectionId,
|
})
|
||||||
query,
|
.then(
|
||||||
{
|
(iterator: QueryIterator<ItemDefinition & Resource>) => {
|
||||||
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
|
this.currentDocDBQueryInfo = {
|
||||||
enableCrossPartitionQuery:
|
iterator: iterator,
|
||||||
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true",
|
index: 0,
|
||||||
} as FeedOptions
|
query: query,
|
||||||
);
|
};
|
||||||
this.currentDocDBQueryInfo = {
|
},
|
||||||
iterator: iterator,
|
(reason: any) => {
|
||||||
index: 0,
|
GraphExplorer.reportToConsole(
|
||||||
query: query,
|
ConsoleDataType.Error,
|
||||||
};
|
`Failed to execute CosmosDB query: ${query} reason:${reason}`
|
||||||
return await this.loadMoreRootNodes();
|
);
|
||||||
} catch (error) {
|
}
|
||||||
GraphExplorer.reportToConsole(
|
)
|
||||||
ConsoleDataType.Error,
|
.then(() => this.loadMoreRootNodes());
|
||||||
`Failed to execute CosmosDB query: ${query} reason:${error}`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadMoreRootNodes(): Promise<UserQueryResult> {
|
private loadMoreRootNodes(): Q.Promise<UserQueryResult> {
|
||||||
if (!this.currentDocDBQueryInfo) {
|
if (!this.currentDocDBQueryInfo) {
|
||||||
return undefined;
|
return Q.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
|
||||||
|
|
||||||
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${
|
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${
|
||||||
this.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE
|
this.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE
|
||||||
})`;
|
})`;
|
||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
||||||
|
|
||||||
try {
|
return queryDocumentsPage(
|
||||||
const results: ViewModels.QueryResults = await queryDocumentsPage(
|
this.props.collectionId,
|
||||||
this.props.collectionId,
|
this.currentDocDBQueryInfo.iterator,
|
||||||
this.currentDocDBQueryInfo.iterator,
|
this.currentDocDBQueryInfo.index,
|
||||||
this.currentDocDBQueryInfo.index
|
{
|
||||||
);
|
enableCrossPartitionQuery:
|
||||||
|
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true",
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
}
|
||||||
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
)
|
||||||
this.setState({ hasMoreRoots: results.hasMoreResults });
|
.then((results: ViewModels.QueryResults) => {
|
||||||
RU = results.requestCharge.toString();
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
GraphExplorer.reportToConsole(
|
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
||||||
ConsoleDataType.Info,
|
this.setState({ hasMoreRoots: results.hasMoreResults });
|
||||||
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
RU = results.requestCharge.toString();
|
||||||
);
|
GraphExplorer.reportToConsole(
|
||||||
const pkIds: string[] = (results.documents || []).map((item: DataModels.DocumentId) =>
|
ConsoleDataType.Info,
|
||||||
GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty)
|
`Executed: ${queryInfoStr} ${GremlinClient.GremlinClient.getRequestChargeString(RU)}`
|
||||||
);
|
);
|
||||||
|
const documents = results.documents || [];
|
||||||
const arg = pkIds.join(",");
|
return documents.map(
|
||||||
await this.executeGremlinQuery(`g.V(${arg})`);
|
(item: DataModels.DocumentId) => {
|
||||||
|
return GraphExplorer.getPkIdFromDocumentId(item, this.props.collectionPartitionKeyProperty);
|
||||||
return { requestCharge: RU };
|
},
|
||||||
} catch (error) {
|
(reason: any) => {
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
// Failure
|
||||||
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
|
GraphExplorer.clearConsoleProgress(id);
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${reason}`;
|
||||||
this.setState({
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
filterQueryError: errorMsg,
|
this.setState({
|
||||||
});
|
filterQueryError: errorMsg,
|
||||||
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
});
|
||||||
throw error;
|
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
|
||||||
}
|
throw reason;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then((pkIds: string[]) => {
|
||||||
|
const arg = pkIds.join(",");
|
||||||
|
return this.executeGremlinQuery(`g.V(${arg})`);
|
||||||
|
})
|
||||||
|
.then(() => ({ requestCharge: RU }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
private executeGremlinQuery(query: string): Q.Promise<UserQueryResult> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
let mockExplorer: Explorer;
|
let mockExplorer: Explorer;
|
||||||
|
|
||||||
describe("Enable Azure Synapse Link Button", () => {
|
describe("Enable Azure Synapse Link Button", () => {
|
||||||
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link";
|
const enableAzureSynapseLinkBtnLabel = "Enable Azure Synapse Link (Preview)";
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ export class CommandBarComponentButtonFactory {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = "Enable Azure Synapse Link";
|
const label = "Enable Azure Synapse Link (Preview)";
|
||||||
return {
|
return {
|
||||||
iconSrc: SynapseIcon,
|
iconSrc: SynapseIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
|
|||||||
108
src/Explorer/Menus/NavBar/MeControlComponent.test.tsx
Normal file
108
src/Explorer/Menus/NavBar/MeControlComponent.test.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { shallow, mount } from "enzyme";
|
||||||
|
import { MeControlComponent, MeControlComponentProps } from "./MeControlComponent";
|
||||||
|
|
||||||
|
const createNotSignedInProps = (): MeControlComponentProps => {
|
||||||
|
return {
|
||||||
|
isUserSignedIn: false,
|
||||||
|
user: null,
|
||||||
|
onSignInClick: jest.fn(),
|
||||||
|
onSignOutClick: jest.fn(),
|
||||||
|
onSwitchDirectoryClick: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSignedInProps = (): MeControlComponentProps => {
|
||||||
|
return {
|
||||||
|
isUserSignedIn: true,
|
||||||
|
user: {
|
||||||
|
name: "Test User",
|
||||||
|
email: "testuser@contoso.com",
|
||||||
|
tenantName: "Contoso",
|
||||||
|
imageUrl: "../../../../images/dotnet.png",
|
||||||
|
},
|
||||||
|
onSignInClick: jest.fn(),
|
||||||
|
onSignOutClick: jest.fn(),
|
||||||
|
onSwitchDirectoryClick: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("test render", () => {
|
||||||
|
it("renders not signed in", () => {
|
||||||
|
const props = createNotSignedInProps();
|
||||||
|
|
||||||
|
const wrapper = shallow(<MeControlComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders signed in with full info", () => {
|
||||||
|
const props = createSignedInProps();
|
||||||
|
|
||||||
|
const wrapper = shallow(<MeControlComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("change not signed in to signed in", () => {
|
||||||
|
const notSignInProps = createNotSignedInProps();
|
||||||
|
|
||||||
|
const wrapper = mount(<MeControlComponent {...notSignInProps} />);
|
||||||
|
expect(wrapper.exists(".mecontrolSigninButton")).toBe(true);
|
||||||
|
expect(wrapper.exists(".mecontrolHeaderButton")).toBe(false);
|
||||||
|
|
||||||
|
const signInProps = createSignedInProps();
|
||||||
|
|
||||||
|
wrapper.setProps(signInProps);
|
||||||
|
expect(wrapper.exists(".mecontrolSigninButton")).toBe(false);
|
||||||
|
expect(wrapper.exists(".mecontrolHeaderButton")).toBe(true);
|
||||||
|
|
||||||
|
wrapper.unmount;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("render contextual menu", () => {
|
||||||
|
const signInProps = createSignedInProps();
|
||||||
|
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||||
|
|
||||||
|
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||||
|
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||||
|
|
||||||
|
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||||
|
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(false);
|
||||||
|
|
||||||
|
wrapper.unmount;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("test function got called", () => {
|
||||||
|
it("sign in click", () => {
|
||||||
|
const notSignInProps = createNotSignedInProps();
|
||||||
|
const wrapper = mount(<MeControlComponent {...notSignInProps} />);
|
||||||
|
|
||||||
|
wrapper.find("button.mecontrolSigninButton").simulate("click");
|
||||||
|
expect(notSignInProps.onSignInClick).toBeCalled();
|
||||||
|
expect(notSignInProps.onSignInClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sign out click", () => {
|
||||||
|
const signInProps = createSignedInProps();
|
||||||
|
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||||
|
|
||||||
|
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||||
|
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||||
|
|
||||||
|
wrapper.find("div.signOutLink").simulate("click");
|
||||||
|
expect(signInProps.onSignOutClick).toBeCalled();
|
||||||
|
expect(signInProps.onSignOutClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("switch directory", () => {
|
||||||
|
const signInProps = createSignedInProps();
|
||||||
|
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||||
|
|
||||||
|
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||||
|
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||||
|
|
||||||
|
wrapper.find("div.switchDirectoryLink").simulate("click");
|
||||||
|
expect(signInProps.onSwitchDirectoryClick).toBeCalled();
|
||||||
|
expect(signInProps.onSwitchDirectoryClick).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
167
src/Explorer/Menus/NavBar/MeControlComponent.tsx
Normal file
167
src/Explorer/Menus/NavBar/MeControlComponent.tsx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { DefaultButton, BaseButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||||
|
import { DirectionalHint, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
|
import { FocusZone } from "office-ui-fabric-react/lib/FocusZone";
|
||||||
|
import { IPersonaSharedProps, Persona, PersonaInitialsColor, PersonaSize } from "office-ui-fabric-react/lib/Persona";
|
||||||
|
|
||||||
|
export interface MeControlComponentProps {
|
||||||
|
/**
|
||||||
|
* Wheather user is signed in or not
|
||||||
|
*/
|
||||||
|
isUserSignedIn: boolean;
|
||||||
|
/**
|
||||||
|
* User info
|
||||||
|
*/
|
||||||
|
user: MeControlUser;
|
||||||
|
/**
|
||||||
|
* Click handler for sign in click
|
||||||
|
*/
|
||||||
|
onSignInClick: (e: React.MouseEvent<BaseButton>) => void;
|
||||||
|
/**
|
||||||
|
* Click handler for sign out click
|
||||||
|
*/
|
||||||
|
onSignOutClick: (e: React.SyntheticEvent) => void;
|
||||||
|
/**
|
||||||
|
* Click handler for switch directory click
|
||||||
|
*/
|
||||||
|
onSwitchDirectoryClick: (e: React.SyntheticEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeControlUser {
|
||||||
|
/**
|
||||||
|
* Display name for user
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Display email for user
|
||||||
|
*/
|
||||||
|
email: string;
|
||||||
|
/**
|
||||||
|
* Display tenant for user
|
||||||
|
*/
|
||||||
|
tenantName: string;
|
||||||
|
/**
|
||||||
|
* image source for the profic photo
|
||||||
|
*/
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MeControlComponent extends React.Component<MeControlComponentProps> {
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return this.props.isUserSignedIn ? this._renderProfileComponent() : this._renderSignInComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderProfileComponent(): JSX.Element {
|
||||||
|
const { user } = this.props;
|
||||||
|
|
||||||
|
const menuProps: IContextualMenuProps = {
|
||||||
|
className: "mecontrolContextualMenu",
|
||||||
|
isBeakVisible: false,
|
||||||
|
directionalHintFixed: true,
|
||||||
|
directionalHint: DirectionalHint.bottomRightEdge,
|
||||||
|
calloutProps: {
|
||||||
|
minPagePadding: 0,
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: "Persona",
|
||||||
|
onRender: this._renderPersonaComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "SwitchDirectory",
|
||||||
|
onRender: this._renderSwitchDirectory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "SignOut",
|
||||||
|
onRender: this._renderSignOut,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const personaProps: IPersonaSharedProps = {
|
||||||
|
imageUrl: user.imageUrl,
|
||||||
|
text: user.email,
|
||||||
|
secondaryText: user.tenantName,
|
||||||
|
showSecondaryText: true,
|
||||||
|
showInitialsUntilImageLoads: true,
|
||||||
|
initialsColor: PersonaInitialsColor.teal,
|
||||||
|
size: PersonaSize.size28,
|
||||||
|
className: "mecontrolHeaderPersona",
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonProps: IButtonProps = {
|
||||||
|
id: "mecontrolHeader",
|
||||||
|
className: "mecontrolHeaderButton",
|
||||||
|
menuProps: menuProps,
|
||||||
|
onRenderMenuIcon: () => <span />,
|
||||||
|
styles: {
|
||||||
|
rootHovered: { backgroundColor: "#393939" },
|
||||||
|
rootFocused: { backgroundColor: "#393939" },
|
||||||
|
rootPressed: { backgroundColor: "#393939" },
|
||||||
|
rootExpanded: { backgroundColor: "#393939" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FocusZone>
|
||||||
|
<DefaultButton {...buttonProps}>
|
||||||
|
<Persona {...personaProps} />
|
||||||
|
</DefaultButton>
|
||||||
|
</FocusZone>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderPersonaComponent = (): JSX.Element => {
|
||||||
|
const { user } = this.props;
|
||||||
|
const personaProps: IPersonaSharedProps = {
|
||||||
|
imageUrl: user.imageUrl,
|
||||||
|
text: user.name,
|
||||||
|
secondaryText: user.email,
|
||||||
|
showSecondaryText: true,
|
||||||
|
showInitialsUntilImageLoads: true,
|
||||||
|
initialsColor: PersonaInitialsColor.teal,
|
||||||
|
size: PersonaSize.size72,
|
||||||
|
className: "mecontrolContextualMenuPersona",
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Persona {...personaProps} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _renderSwitchDirectory = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="switchDirectoryLink"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) =>
|
||||||
|
this.props.onSwitchDirectoryClick(e)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Switch Directory
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _renderSignOut = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="signOutLink"
|
||||||
|
onClick={(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => this.props.onSignOutClick(e)}
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _renderSignInComponent = (): JSX.Element => {
|
||||||
|
const buttonProps: IButtonProps = {
|
||||||
|
className: "mecontrolSigninButton",
|
||||||
|
text: "Sign In",
|
||||||
|
onClick: (e: React.MouseEvent<BaseButton>) => this.props.onSignInClick(e),
|
||||||
|
styles: {
|
||||||
|
rootHovered: { backgroundColor: "#393939", color: "#fff" },
|
||||||
|
rootFocused: { backgroundColor: "#393939", color: "#fff" },
|
||||||
|
rootPressed: { backgroundColor: "#393939", color: "#fff" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return <DefaultButton {...buttonProps} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
16
src/Explorer/Menus/NavBar/MeControlComponentAdapter.tsx
Normal file
16
src/Explorer/Menus/NavBar/MeControlComponentAdapter.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* This adapter is responsible to render the React component
|
||||||
|
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
|
||||||
|
* and update any knockout observables passed from the parent.
|
||||||
|
*/
|
||||||
|
import * as React from "react";
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import { MeControlComponent, MeControlComponentProps } from "./MeControlComponent";
|
||||||
|
|
||||||
|
export class MeControlComponentAdapter implements ReactAdapter {
|
||||||
|
public parameters: ko.Observable<MeControlComponentProps>;
|
||||||
|
|
||||||
|
public renderComponent(): JSX.Element {
|
||||||
|
return <MeControlComponent {...this.parameters()} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`test render renders not signed in 1`] = `
|
||||||
|
<CustomizedDefaultButton
|
||||||
|
className="mecontrolSigninButton"
|
||||||
|
onClick={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"rootFocused": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
"color": "#fff",
|
||||||
|
},
|
||||||
|
"rootHovered": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
"color": "#fff",
|
||||||
|
},
|
||||||
|
"rootPressed": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
"color": "#fff",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text="Sign In"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`test render renders signed in with full info 1`] = `
|
||||||
|
<FocusZone
|
||||||
|
direction={2}
|
||||||
|
isCircularNavigation={false}
|
||||||
|
shouldRaiseClicks={true}
|
||||||
|
>
|
||||||
|
<CustomizedDefaultButton
|
||||||
|
className="mecontrolHeaderButton"
|
||||||
|
id="mecontrolHeader"
|
||||||
|
menuProps={
|
||||||
|
Object {
|
||||||
|
"calloutProps": Object {
|
||||||
|
"minPagePadding": 0,
|
||||||
|
},
|
||||||
|
"className": "mecontrolContextualMenu",
|
||||||
|
"directionalHint": 6,
|
||||||
|
"directionalHintFixed": true,
|
||||||
|
"isBeakVisible": false,
|
||||||
|
"items": Array [
|
||||||
|
Object {
|
||||||
|
"key": "Persona",
|
||||||
|
"onRender": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "SwitchDirectory",
|
||||||
|
"onRender": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"key": "SignOut",
|
||||||
|
"onRender": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onRenderMenuIcon={[Function]}
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"rootExpanded": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
},
|
||||||
|
"rootFocused": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
},
|
||||||
|
"rootHovered": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
},
|
||||||
|
"rootPressed": Object {
|
||||||
|
"backgroundColor": "#393939",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledPersonaBase
|
||||||
|
className="mecontrolHeaderPersona"
|
||||||
|
imageUrl="../../../../images/dotnet.png"
|
||||||
|
initialsColor={3}
|
||||||
|
secondaryText="Contoso"
|
||||||
|
showInitialsUntilImageLoads={true}
|
||||||
|
showSecondaryText={true}
|
||||||
|
size={7}
|
||||||
|
text="testuser@contoso.com"
|
||||||
|
/>
|
||||||
|
</CustomizedDefaultButton>
|
||||||
|
</FocusZone>
|
||||||
|
`;
|
||||||
@@ -11,8 +11,8 @@ import ErrorBlackIcon from "../../../../images/error_black.svg";
|
|||||||
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
||||||
import InfoIcon from "../../../../images/info_color.svg";
|
import InfoIcon from "../../../../images/info_color.svg";
|
||||||
import ErrorRedIcon from "../../../../images/error_red.svg";
|
import ErrorRedIcon from "../../../../images/error_red.svg";
|
||||||
import ClearIcon from "../../../../images/Clear.svg";
|
|
||||||
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
|
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
|
||||||
|
import ClearIcon from "../../../../images/Clear.svg";
|
||||||
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
||||||
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
|
||||||
@@ -59,9 +59,9 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
{ key: "Info", text: "Info" },
|
{ key: "Info", text: "Info" },
|
||||||
{ key: "Error", text: "Error" },
|
{ key: "Error", text: "Error" },
|
||||||
];
|
];
|
||||||
private headerTimeoutId?: number;
|
private headerTimeoutId: number;
|
||||||
private prevHeaderStatus: string | null;
|
private prevHeaderStatus: string;
|
||||||
private consoleHeaderElement?: HTMLElement;
|
private consoleHeaderElement: HTMLElement;
|
||||||
|
|
||||||
constructor(props: NotificationConsoleComponentProps) {
|
constructor(props: NotificationConsoleComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -99,10 +99,6 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setElememntRef = (element: HTMLElement) => {
|
|
||||||
this.consoleHeaderElement = element;
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const numInProgress = this.props.consoleData.filter((data: ConsoleData) => data.type === ConsoleDataType.InProgress)
|
const numInProgress = this.props.consoleData.filter((data: ConsoleData) => data.type === ConsoleDataType.InProgress)
|
||||||
.length;
|
.length;
|
||||||
@@ -114,7 +110,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
<div className="notificationConsoleContainer">
|
<div className="notificationConsoleContainer">
|
||||||
<div
|
<div
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
ref={this.setElememntRef}
|
ref={(element: HTMLElement) => (this.consoleHeaderElement = element)}
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.expandCollapseConsole()}
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.expandCollapseConsole()}
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -224,12 +220,12 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFilterSelected = (event: React.ChangeEvent<HTMLSelectElement>, option: IDropdownOption): void => {
|
private onFilterSelected(event: React.ChangeEvent<HTMLSelectElement>, option: IDropdownOption): void {
|
||||||
this.setState({ selectedFilter: String(option.key) });
|
this.setState({ selectedFilter: String(option.key) });
|
||||||
};
|
}
|
||||||
|
|
||||||
private getFilteredConsoleData(): ConsoleData[] {
|
private getFilteredConsoleData(): ConsoleData[] {
|
||||||
let filterType: ConsoleDataType | null = null;
|
let filterType: ConsoleDataType = null;
|
||||||
|
|
||||||
switch (this.state.selectedFilter) {
|
switch (this.state.selectedFilter) {
|
||||||
case "All":
|
case "All":
|
||||||
@@ -276,7 +272,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
|
|
||||||
private onConsoleWasExpanded = (): void => {
|
private onConsoleWasExpanded = (): void => {
|
||||||
this.props.onConsoleExpandedChange(this.state.isExpanded);
|
this.props.onConsoleExpandedChange(this.state.isExpanded);
|
||||||
if (this.state.isExpanded && this.consoleHeaderElement) {
|
if (this.state.isExpanded) {
|
||||||
this.consoleHeaderElement.focus();
|
this.consoleHeaderElement.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user